1use crate::colors::SectionColors;
2use crate::config::Config;
3use crate::types::Section;
4use crate::utils::RESET;
5
6pub 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(§ion.content, §ion.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 = effective_background(§ion.colors, config);
17 let right_bg = effective_background(&next.colors, config);
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 if config.display.use_powerline {
27 if let Some(last) = sections.last() {
28 let tail = match effective_background(&last.colors, config) {
29 Some(bg) => format!(
30 "\x1b[38;2;{};{};{}m\x1b[49m{}",
31 bg.0, bg.1, bg.2, config.display.arrow
32 ),
33 None => make_plain_arrow(config),
34 };
35 result.push_str(&tail);
36 }
37 }
38
39 result.push_str(RESET);
40 result
41}
42
43fn effective_background(colors: &SectionColors, config: &Config) -> Option<(u8, u8, u8)> {
44 if config.display.show_background {
45 colors.background
46 } else {
47 None
48 }
49}
50
51fn render_section(content: &str, colors: &SectionColors, config: &Config) -> String {
53 let bg_code = match effective_background(colors, config) {
54 Some(bg) => format!("\x1b[48;2;{};{};{}m", bg.0, bg.1, bg.2),
55 None => "\x1b[49m".to_string(), };
57
58 let padding = " ".repeat(config.display.section_padding);
59
60 format!(
61 "{}\x1b[38;2;{};{};{}m{}{}{}",
62 bg_code,
63 colors.foreground.0,
64 colors.foreground.1,
65 colors.foreground.2,
66 padding,
67 content,
68 padding
69 )
70}
71
72fn make_powerline_arrow(
74 left_bg: Option<(u8, u8, u8)>,
75 right_bg: Option<(u8, u8, u8)>,
76 config: &Config,
77) -> String {
78 match (left_bg, right_bg) {
79 (Some(left_bg), Some(right_bg)) => format!(
80 "\x1b[38;2;{};{};{}m\x1b[48;2;{};{};{}m{}",
81 left_bg.0,
82 left_bg.1,
83 left_bg.2,
84 right_bg.0,
85 right_bg.1,
86 right_bg.2,
87 config.display.arrow
88 ),
89 _ => make_plain_arrow(config),
90 }
91}
92
93fn make_plain_arrow(config: &Config) -> String {
94 let color = config.theme.separator;
95 format!(
96 "{}\x1b[38;2;{};{};{}m{}",
97 RESET, color.0, color.1, color.2, config.display.arrow
98 )
99}
100
101fn make_details_from_section(colors: &SectionColors) -> String {
103 format!(
104 "\x1b[38;2;{};{};{}m",
105 colors.details.0, colors.details.1, colors.details.2
106 )
107}
108
109fn restore_fg_from_section(colors: &SectionColors) -> String {
111 format!(
112 "\x1b[38;2;{};{};{}m",
113 colors.foreground.0, colors.foreground.1, colors.foreground.2
114 )
115}
116
117pub fn get_details_and_fg_codes(colors: &SectionColors) -> (String, String) {
119 (
120 make_details_from_section(colors),
121 restore_fg_from_section(colors),
122 )
123}
124
125fn make_separator(config: &Config) -> String {
127 let color = config.theme.separator;
128 format!(
129 "{}\x1b[38;2;{};{};{}m{}\x1b[0m",
130 RESET, color.0, color.1, color.2, config.display.segment_separator
131 )
132}
133
134#[cfg(test)]
135mod tests {
136 use super::*;
137
138 const TEST_COLORS: SectionColors = SectionColors {
139 background: Some((100, 100, 100)),
140 foreground: (200, 200, 200),
141 details: (150, 150, 150),
142 };
143
144 const TEST_COLORS_NO_BG: SectionColors = SectionColors {
145 background: None,
146 foreground: (255, 255, 255),
147 details: (128, 128, 128),
148 };
149
150 #[test]
151 fn test_render_segments_empty() {
152 let config = Config::default();
153 let sections: Vec<&Section> = vec![];
154 let result = render_segments(§ions, &config);
155 assert_eq!(result, RESET);
156 }
157
158 #[test]
159 fn test_render_segments_single() {
160 let config = Config::default();
161 let section = Section::new("test".to_string(), 0, TEST_COLORS);
162 let result = render_segments(&[§ion], &config);
163
164 assert!(result.contains("test"));
166 assert!(result.ends_with(RESET));
168 }
169
170 #[test]
171 fn test_render_segments_with_powerline() {
172 let mut config = Config::default();
173 config.display.use_powerline = true;
174 config.display.arrow = "".to_string();
175
176 let section1 = Section::new("test1".to_string(), 0, TEST_COLORS);
177 let section2 = Section::new("test2".to_string(), 1, TEST_COLORS);
178
179 let result = render_segments(&[§ion1, §ion2], &config);
180
181 assert!(result.contains("test1"));
183 assert!(result.contains("test2"));
184 assert!(result.contains(""));
186 }
187
188 #[test]
189 fn test_render_segments_with_separator() {
190 let mut config = Config::default();
191 config.display.use_powerline = false;
192 config.display.segment_separator = " | ".to_string();
193
194 let section1 = Section::new("test1".to_string(), 0, TEST_COLORS);
195 let section2 = Section::new("test2".to_string(), 1, TEST_COLORS);
196
197 let result = render_segments(&[§ion1, §ion2], &config);
198
199 assert!(result.contains("|"));
201 }
202
203 #[test]
204 fn test_make_details_from_section() {
205 let result = make_details_from_section(&TEST_COLORS);
206 assert!(result.starts_with("\x1b[38;2;"));
208 assert!(result.contains("150"));
209 }
210
211 #[test]
212 fn test_restore_fg_from_section() {
213 let result = restore_fg_from_section(&TEST_COLORS);
214 assert!(result.starts_with("\x1b[38;2;"));
216 assert!(result.contains("200"));
217 }
218
219 #[test]
220 fn test_get_details_and_fg_codes() {
221 let (details, fg) = get_details_and_fg_codes(&TEST_COLORS);
222
223 assert!(details.starts_with("\x1b["));
224 assert!(fg.starts_with("\x1b["));
225 assert_ne!(details, fg);
226 }
227
228 #[test]
229 fn test_render_section_no_background() {
230 let config = Config::default();
231 let result = render_section("test", &TEST_COLORS_NO_BG, &config);
232
233 assert!(result.contains("\x1b[49m"));
235 assert!(result.contains("test"));
237 }
238
239 #[test]
240 fn test_render_section_with_background() {
241 let config = Config::default();
242 let result = render_section("test", &TEST_COLORS, &config);
243
244 assert!(result.contains("\x1b[48;2;"));
246 assert!(result.contains("100"));
248 }
249
250 #[test]
251 fn test_make_powerline_arrow() {
252 let mut config = Config::default();
253 config.display.arrow = "".to_string();
254
255 let left = Some((100, 100, 100));
256 let right = Some((200, 200, 200));
257 let result = make_powerline_arrow(left, right, &config);
258
259 assert!(result.contains("100"));
261 assert!(result.contains("200"));
263 assert!(result.contains(""));
265 }
266
267 #[test]
268 fn test_render_section_without_background_when_disabled() {
269 let mut config = Config::default();
270 config.display.show_background = false;
271
272 let result = render_section("test", &TEST_COLORS, &config);
273 assert!(result.contains("\x1b[49m"));
274 assert!(!result.contains("\x1b[48;2;100;100;100m"));
275 }
276
277 #[test]
278 fn test_make_powerline_arrow_without_backgrounds() {
279 let mut config = Config::default();
280 config.display.arrow = ">".to_string();
281
282 let result = make_powerline_arrow(None, None, &config);
283 assert!(result.contains(">"));
284 assert!(!result.contains("\x1b[48;2;"));
285 }
286
287 #[test]
288 fn test_rgb_values_boundary() {
289 let colors_min = SectionColors {
291 background: Some((0, 0, 0)),
292 foreground: (0, 0, 0),
293 details: (0, 0, 0),
294 };
295 let result = make_details_from_section(&colors_min);
296 assert!(result.contains("0"));
297
298 let colors_max = SectionColors {
300 background: Some((255, 255, 255)),
301 foreground: (255, 255, 255),
302 details: (255, 255, 255),
303 };
304 let result = make_details_from_section(&colors_max);
305 assert!(result.contains("255"));
306 }
307}