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 if let Some(bg_str) = colorfgbg.split(';').nth(1) {
42 if let Ok(bg_code) = bg_str.parse::<u8>() {
43 return if bg_code >= 7 {
46 ColorScheme::Light
47 } else {
48 ColorScheme::Dark
49 };
50 }
51 }
52 }
53
54 if let Ok(term_program) = env::var("TERM_PROGRAM") {
56 match term_program.as_str() {
57 "Apple_Terminal" => {
58 if let Ok(_term_session_id) = env::var("TERM_SESSION_ID") {
61 return ColorScheme::Light;
64 }
65 return ColorScheme::Light; }
67 "iTerm.app" => {
68 if let Ok(_iterm_session_id) = env::var("ITERM_SESSION_ID") {
70 return ColorScheme::Dark;
72 }
73 }
74 "vscode" | "code" => {
75 return ColorScheme::Light;
78 }
79 _ => {}
80 }
81 }
82
83 if let Ok(term) = env::var("TERM") {
85 match term.as_str() {
86 term if term.contains("light") => return ColorScheme::Light,
87 term if term.contains("256color") => {
88 if env::var("TERM_PROGRAM")
91 .unwrap_or_default()
92 .contains("Terminal")
93 {
94 return ColorScheme::Light; }
96 }
97 _ => {}
98 }
99 }
100
101 if env::var("SSH_CONNECTION").is_ok() || env::var("SSH_CLIENT").is_ok() {
103 return ColorScheme::Dark;
104 }
105
106 if let Ok(bg_hint) = env::var("BACKGROUND") {
108 match bg_hint.to_lowercase().as_str() {
109 "light" | "white" => return ColorScheme::Light,
110 "dark" | "black" => return ColorScheme::Dark,
111 _ => {}
112 }
113 }
114
115 if let Ok(desktop) = env::var("XDG_CURRENT_DESKTOP") {
117 match desktop.to_lowercase().as_str() {
118 "gnome" | "kde" | "xfce" => {
119 return ColorScheme::Light;
121 }
122 _ => {}
123 }
124 }
125
126 if env::var("DISPLAY").is_ok() || env::var("WAYLAND_DISPLAY").is_ok() {
128 if env::var("TERM_PROGRAM").is_err() {
131 return ColorScheme::Light;
132 }
133 }
134
135 #[cfg(target_os = "macos")]
138 {
139 return ColorScheme::Light; }
141
142 #[cfg(not(target_os = "macos"))]
143 {
144 return ColorScheme::Dark; }
146 }
147
148 pub fn scheme(&self) -> ColorScheme {
150 self.scheme
151 }
152
153 pub fn header_text(&self, text: &str) -> ColoredString {
155 match self.scheme {
156 ColorScheme::Dark => text.bright_white().bold(),
157 ColorScheme::Light => text.black().bold(),
158 }
159 }
160
161 pub fn border(&self, text: &str) -> ColoredString {
162 match self.scheme {
163 ColorScheme::Dark => text.bright_blue(),
164 ColorScheme::Light => text.blue(),
165 }
166 }
167
168 pub fn primary(&self, text: &str) -> ColoredString {
170 match self.scheme {
171 ColorScheme::Dark => text.yellow(),
172 ColorScheme::Light => text.red().bold(),
173 }
174 }
175
176 pub fn secondary(&self, text: &str) -> ColoredString {
177 match self.scheme {
178 ColorScheme::Dark => text.green(),
179 ColorScheme::Light => text.green().bold(),
180 }
181 }
182
183 pub fn language(&self, text: &str) -> ColoredString {
185 match self.scheme {
186 ColorScheme::Dark => text.blue(),
187 ColorScheme::Light => text.blue().bold(),
188 }
189 }
190
191 pub fn framework(&self, text: &str) -> ColoredString {
192 match self.scheme {
193 ColorScheme::Dark => text.magenta(),
194 ColorScheme::Light => text.magenta().bold(),
195 }
196 }
197
198 pub fn database(&self, text: &str) -> ColoredString {
199 match self.scheme {
200 ColorScheme::Dark => text.cyan(),
201 ColorScheme::Light => text.cyan().bold(),
202 }
203 }
204
205 pub fn technology(&self, text: &str) -> ColoredString {
206 match self.scheme {
207 ColorScheme::Dark => text.magenta(),
208 ColorScheme::Light => text.purple().bold(),
209 }
210 }
211
212 pub fn info(&self, text: &str) -> ColoredString {
214 match self.scheme {
215 ColorScheme::Dark => text.cyan(),
216 ColorScheme::Light => text.blue().bold(),
217 }
218 }
219
220 pub fn success(&self, text: &str) -> ColoredString {
221 match self.scheme {
222 ColorScheme::Dark => text.green(),
223 ColorScheme::Light => text.green().bold(),
224 }
225 }
226
227 pub fn warning(&self, text: &str) -> ColoredString {
228 match self.scheme {
229 ColorScheme::Dark => text.yellow(),
230 ColorScheme::Light => text.red(),
231 }
232 }
233
234 pub fn error(&self, text: &str) -> ColoredString {
235 match self.scheme {
236 ColorScheme::Dark => text.red(),
237 ColorScheme::Light => text.red().bold(),
238 }
239 }
240
241 pub fn label(&self, text: &str) -> ColoredString {
243 match self.scheme {
244 ColorScheme::Dark => text.bright_white(),
245 ColorScheme::Light => text.black().bold(),
246 }
247 }
248
249 pub fn value(&self, text: &str) -> ColoredString {
250 match self.scheme {
251 ColorScheme::Dark => text.white(),
252 ColorScheme::Light => text.black(),
253 }
254 }
255
256 pub fn dimmed(&self, text: &str) -> ColoredString {
258 match self.scheme {
259 ColorScheme::Dark => text.dimmed(),
260 ColorScheme::Light => text.dimmed(),
261 }
262 }
263
264 pub fn architecture_pattern(&self, text: &str) -> ColoredString {
266 match self.scheme {
267 ColorScheme::Dark => text.green(),
268 ColorScheme::Light => text.green().bold(),
269 }
270 }
271
272 pub fn project_type(&self, text: &str) -> ColoredString {
274 match self.scheme {
275 ColorScheme::Dark => text.yellow(),
276 ColorScheme::Light => text.red().bold(),
277 }
278 }
279
280 pub fn metric(&self, text: &str) -> ColoredString {
282 match self.scheme {
283 ColorScheme::Dark => text.cyan(),
284 ColorScheme::Light => text.blue().bold(),
285 }
286 }
287
288 pub fn path(&self, text: &str) -> ColoredString {
290 match self.scheme {
291 ColorScheme::Dark => text.cyan().bold(),
292 ColorScheme::Light => text.blue().bold(),
293 }
294 }
295
296 pub fn confidence_high(&self, text: &str) -> ColoredString {
298 match self.scheme {
299 ColorScheme::Dark => text.green(),
300 ColorScheme::Light => text.green().bold(),
301 }
302 }
303
304 pub fn confidence_medium(&self, text: &str) -> ColoredString {
305 match self.scheme {
306 ColorScheme::Dark => text.yellow(),
307 ColorScheme::Light => text.red(),
308 }
309 }
310
311 pub fn confidence_low(&self, text: &str) -> ColoredString {
312 match self.scheme {
313 ColorScheme::Dark => text.red(),
314 ColorScheme::Light => text.red().bold(),
315 }
316 }
317}
318
319impl Default for ColorAdapter {
320 fn default() -> Self {
321 Self::new()
322 }
323}
324
325static COLOR_ADAPTER: std::sync::OnceLock<ColorAdapter> = std::sync::OnceLock::new();
327
328pub fn get_color_adapter() -> &'static ColorAdapter {
330 COLOR_ADAPTER.get_or_init(ColorAdapter::new)
331}
332
333pub fn init_color_adapter(scheme: ColorScheme) {
335 let _ = COLOR_ADAPTER.set(ColorAdapter::with_scheme(scheme));
336}
337
338#[macro_export]
340macro_rules! color {
341 ($method:ident, $text:expr) => {
342 $crate::analyzer::display::color_adapter::get_color_adapter().$method($text)
343 };
344}
345
346#[cfg(test)]
347mod tests {
348 use super::*;
349
350 #[test]
351 fn test_color_adapter_creation() {
352 let adapter = ColorAdapter::new();
353 assert!(matches!(
354 adapter.scheme(),
355 ColorScheme::Dark | ColorScheme::Light
356 ));
357 }
358
359 #[test]
360 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}