syncable_cli/analyzer/display/
color_adapter.rs1use colored::*;
7use std::env;
8
9#[derive(Debug, Clone, Copy, PartialEq)]
11pub enum ColorScheme {
12 Dark,
14 Light,
16}
17
18#[derive(Debug, Clone)]
20pub struct ColorAdapter {
21 scheme: ColorScheme,
22}
23
24impl ColorAdapter {
25 pub fn new() -> Self {
27 Self {
28 scheme: Self::detect_terminal_background(),
29 }
30 }
31
32 pub fn with_scheme(scheme: ColorScheme) -> Self {
34 Self { scheme }
35 }
36
37 fn detect_terminal_background() -> ColorScheme {
39 if let Ok(colorfgbg) = env::var("COLORFGBG")
41 && let Some(bg_str) = colorfgbg.split(';').nth(1)
42 && let Ok(bg_code) = bg_str.parse::<u8>()
43 {
44 return if bg_code >= 7 {
47 ColorScheme::Light
48 } else {
49 ColorScheme::Dark
50 };
51 }
52
53 if let Ok(term_program) = env::var("TERM_PROGRAM") {
55 match term_program.as_str() {
56 "Apple_Terminal" => {
57 if let Ok(_term_session_id) = env::var("TERM_SESSION_ID") {
60 return ColorScheme::Light;
63 }
64 return ColorScheme::Light; }
66 "iTerm.app" => {
67 if let Ok(_iterm_session_id) = env::var("ITERM_SESSION_ID") {
69 return ColorScheme::Dark;
71 }
72 }
73 "vscode" | "code" => {
74 return ColorScheme::Light;
77 }
78 _ => {}
79 }
80 }
81
82 if let Ok(term) = env::var("TERM") {
84 match term.as_str() {
85 term if term.contains("light") => return ColorScheme::Light,
86 term if term.contains("256color") => {
87 if env::var("TERM_PROGRAM")
90 .unwrap_or_default()
91 .contains("Terminal")
92 {
93 return ColorScheme::Light; }
95 }
96 _ => {}
97 }
98 }
99
100 if env::var("SSH_CONNECTION").is_ok() || env::var("SSH_CLIENT").is_ok() {
102 return ColorScheme::Dark;
103 }
104
105 if let Ok(bg_hint) = env::var("BACKGROUND") {
107 match bg_hint.to_lowercase().as_str() {
108 "light" | "white" => return ColorScheme::Light,
109 "dark" | "black" => return ColorScheme::Dark,
110 _ => {}
111 }
112 }
113
114 if let Ok(desktop) = env::var("XDG_CURRENT_DESKTOP") {
116 match desktop.to_lowercase().as_str() {
117 "gnome" | "kde" | "xfce" => {
118 return ColorScheme::Light;
120 }
121 _ => {}
122 }
123 }
124
125 if env::var("DISPLAY").is_ok() || env::var("WAYLAND_DISPLAY").is_ok() {
127 if env::var("TERM_PROGRAM").is_err() {
130 return ColorScheme::Light;
131 }
132 }
133
134 #[cfg(target_os = "macos")]
137 {
138 ColorScheme::Light }
140
141 #[cfg(not(target_os = "macos"))]
142 {
143 ColorScheme::Dark }
145 }
146
147 pub fn scheme(&self) -> ColorScheme {
149 self.scheme
150 }
151
152 pub fn header_text(&self, text: &str) -> ColoredString {
154 match self.scheme {
155 ColorScheme::Dark => text.bright_white().bold(),
156 ColorScheme::Light => text.black().bold(),
157 }
158 }
159
160 pub fn border(&self, text: &str) -> ColoredString {
161 match self.scheme {
162 ColorScheme::Dark => text.bright_blue(),
163 ColorScheme::Light => text.blue(),
164 }
165 }
166
167 pub fn primary(&self, text: &str) -> ColoredString {
169 match self.scheme {
170 ColorScheme::Dark => text.yellow(),
171 ColorScheme::Light => text.red().bold(),
172 }
173 }
174
175 pub fn secondary(&self, text: &str) -> ColoredString {
176 match self.scheme {
177 ColorScheme::Dark => text.green(),
178 ColorScheme::Light => text.green().bold(),
179 }
180 }
181
182 pub fn language(&self, text: &str) -> ColoredString {
184 match self.scheme {
185 ColorScheme::Dark => text.blue(),
186 ColorScheme::Light => text.blue().bold(),
187 }
188 }
189
190 pub fn framework(&self, text: &str) -> ColoredString {
191 match self.scheme {
192 ColorScheme::Dark => text.magenta(),
193 ColorScheme::Light => text.magenta().bold(),
194 }
195 }
196
197 pub fn database(&self, text: &str) -> ColoredString {
198 match self.scheme {
199 ColorScheme::Dark => text.cyan(),
200 ColorScheme::Light => text.cyan().bold(),
201 }
202 }
203
204 pub fn technology(&self, text: &str) -> ColoredString {
205 match self.scheme {
206 ColorScheme::Dark => text.magenta(),
207 ColorScheme::Light => text.purple().bold(),
208 }
209 }
210
211 pub fn info(&self, text: &str) -> ColoredString {
213 match self.scheme {
214 ColorScheme::Dark => text.cyan(),
215 ColorScheme::Light => text.blue().bold(),
216 }
217 }
218
219 pub fn success(&self, text: &str) -> ColoredString {
220 match self.scheme {
221 ColorScheme::Dark => text.green(),
222 ColorScheme::Light => text.green().bold(),
223 }
224 }
225
226 pub fn warning(&self, text: &str) -> ColoredString {
227 match self.scheme {
228 ColorScheme::Dark => text.yellow(),
229 ColorScheme::Light => text.red(),
230 }
231 }
232
233 pub fn error(&self, text: &str) -> ColoredString {
234 match self.scheme {
235 ColorScheme::Dark => text.red(),
236 ColorScheme::Light => text.red().bold(),
237 }
238 }
239
240 pub fn label(&self, text: &str) -> ColoredString {
242 match self.scheme {
243 ColorScheme::Dark => text.bright_white(),
244 ColorScheme::Light => text.black().bold(),
245 }
246 }
247
248 pub fn value(&self, text: &str) -> ColoredString {
249 match self.scheme {
250 ColorScheme::Dark => text.white(),
251 ColorScheme::Light => text.black(),
252 }
253 }
254
255 pub fn dimmed(&self, text: &str) -> ColoredString {
257 match self.scheme {
258 ColorScheme::Dark => text.dimmed(),
259 ColorScheme::Light => text.dimmed(),
260 }
261 }
262
263 pub fn architecture_pattern(&self, text: &str) -> ColoredString {
265 match self.scheme {
266 ColorScheme::Dark => text.green(),
267 ColorScheme::Light => text.green().bold(),
268 }
269 }
270
271 pub fn project_type(&self, text: &str) -> ColoredString {
273 match self.scheme {
274 ColorScheme::Dark => text.yellow(),
275 ColorScheme::Light => text.red().bold(),
276 }
277 }
278
279 pub fn metric(&self, text: &str) -> ColoredString {
281 match self.scheme {
282 ColorScheme::Dark => text.cyan(),
283 ColorScheme::Light => text.blue().bold(),
284 }
285 }
286
287 pub fn path(&self, text: &str) -> ColoredString {
289 match self.scheme {
290 ColorScheme::Dark => text.cyan().bold(),
291 ColorScheme::Light => text.blue().bold(),
292 }
293 }
294
295 pub fn confidence_high(&self, text: &str) -> ColoredString {
297 match self.scheme {
298 ColorScheme::Dark => text.green(),
299 ColorScheme::Light => text.green().bold(),
300 }
301 }
302
303 pub fn confidence_medium(&self, text: &str) -> ColoredString {
304 match self.scheme {
305 ColorScheme::Dark => text.yellow(),
306 ColorScheme::Light => text.red(),
307 }
308 }
309
310 pub fn confidence_low(&self, text: &str) -> ColoredString {
311 match self.scheme {
312 ColorScheme::Dark => text.red(),
313 ColorScheme::Light => text.red().bold(),
314 }
315 }
316}
317
318impl Default for ColorAdapter {
319 fn default() -> Self {
320 Self::new()
321 }
322}
323
324static COLOR_ADAPTER: std::sync::OnceLock<ColorAdapter> = std::sync::OnceLock::new();
326
327pub fn get_color_adapter() -> &'static ColorAdapter {
329 COLOR_ADAPTER.get_or_init(ColorAdapter::new)
330}
331
332pub fn init_color_adapter(scheme: ColorScheme) {
334 let _ = COLOR_ADAPTER.set(ColorAdapter::with_scheme(scheme));
335}
336
337#[macro_export]
339macro_rules! color {
340 ($method:ident, $text:expr) => {
341 $crate::analyzer::display::color_adapter::get_color_adapter().$method($text)
342 };
343}
344
345#[cfg(test)]
346mod tests {
347 use super::*;
348
349 #[test]
350 fn test_color_adapter_creation() {
351 let adapter = ColorAdapter::new();
352 assert!(matches!(
353 adapter.scheme(),
354 ColorScheme::Dark | ColorScheme::Light
355 ));
356 }
357
358 #[test]
359 #[ignore] fn test_color_scheme_specific() {
361 let dark_adapter = ColorAdapter::with_scheme(ColorScheme::Dark);
362 let light_adapter = ColorAdapter::with_scheme(ColorScheme::Light);
363
364 assert_eq!(dark_adapter.scheme(), ColorScheme::Dark);
365 assert_eq!(light_adapter.scheme(), ColorScheme::Light);
366
367 let test_text = "test";
369 let dark_result = dark_adapter.header_text(test_text).to_string();
370 let light_result = light_adapter.header_text(test_text).to_string();
371
372 assert_ne!(dark_result, light_result);
374 }
375
376 #[test]
377 fn test_color_methods() {
378 let adapter = ColorAdapter::with_scheme(ColorScheme::Dark);
379 let text = "test";
380
381 let _ = adapter.header_text(text);
383 let _ = adapter.border(text);
384 let _ = adapter.primary(text);
385 let _ = adapter.secondary(text);
386 let _ = adapter.language(text);
387 let _ = adapter.framework(text);
388 let _ = adapter.database(text);
389 let _ = adapter.technology(text);
390 let _ = adapter.info(text);
391 let _ = adapter.success(text);
392 let _ = adapter.warning(text);
393 let _ = adapter.error(text);
394 let _ = adapter.label(text);
395 let _ = adapter.value(text);
396 let _ = adapter.dimmed(text);
397 let _ = adapter.architecture_pattern(text);
398 let _ = adapter.project_type(text);
399 let _ = adapter.metric(text);
400 let _ = adapter.path(text);
401 let _ = adapter.confidence_high(text);
402 let _ = adapter.confidence_medium(text);
403 let _ = adapter.confidence_low(text);
404 }
405}