Skip to main content

rush_sync_server/ui/
color.rs

1use crate::core::prelude::*;
2use log::Level;
3use std::collections::HashMap;
4use std::sync::LazyLock;
5
6#[derive(Debug, Clone, Copy, PartialEq)]
7pub struct AppColor(Color);
8
9static COLOR_MAP: LazyLock<HashMap<&'static str, Color>> = LazyLock::new(|| {
10    let mut map = HashMap::new();
11
12    // Standard colors
13    map.insert("black", Color::Black);
14    map.insert("red", Color::Red);
15    map.insert("green", Color::Green);
16    map.insert("yellow", Color::Yellow);
17    map.insert("blue", Color::Blue);
18    map.insert("magenta", Color::Magenta);
19    map.insert("cyan", Color::Cyan);
20    map.insert("gray", Color::Gray);
21    map.insert("darkgray", Color::DarkGray);
22    map.insert("lightred", Color::LightRed);
23    map.insert("lightgreen", Color::LightGreen);
24    map.insert("lightyellow", Color::LightYellow);
25    map.insert("lightblue", Color::LightBlue);
26    map.insert("lightmagenta", Color::LightMagenta);
27    map.insert("lightcyan", Color::LightCyan);
28    map.insert("white", Color::White);
29
30    // Categories
31    map.insert("error", Color::Red);
32    map.insert("warning", Color::Yellow);
33    map.insert("warn", Color::Yellow);
34    map.insert("info", Color::Green);
35    map.insert("debug", Color::Blue);
36    map.insert("trace", Color::White);
37    map.insert("lang", Color::Cyan);
38    map.insert("version", Color::LightBlue);
39    map.insert("startup", Color::Magenta);
40    map.insert("theme", Color::LightMagenta);
41
42    map
43});
44
45// Pre-compiled display text to color map for anti-flicker rendering
46static DISPLAY_COLOR_MAP: LazyLock<HashMap<&'static str, Color>> = LazyLock::new(|| {
47    let mut map = HashMap::new();
48
49    // Confirmations
50    map.insert("CONFIRM", Color::Yellow);
51    map.insert("BESTÄTIGEN", Color::Yellow);
52
53    // Errors
54    map.insert("ERROR", Color::Red);
55    map.insert("FEHLER", Color::Red);
56    map.insert("RENDER", Color::Red);
57
58    // Warnings
59    map.insert("WARN", Color::Yellow);
60    map.insert("WARNING", Color::Yellow);
61    map.insert("TERMINAL", Color::Yellow);
62
63    // Info
64    map.insert("INFO", Color::Green);
65    map.insert("CLIPBOARD", Color::Green);
66    map.insert("HISTORY", Color::Green);
67    map.insert("HISTORIE", Color::Green);
68    map.insert("LOG_LEVEL", Color::Green);
69    map.insert("SYSTEM", Color::Green);
70
71    // Debug
72    map.insert("DEBUG", Color::Blue);
73
74    // Trace
75    map.insert("TRACE", Color::White);
76
77    // Special categories
78    map.insert("THEME", Color::LightMagenta);
79    map.insert("LANG", Color::Cyan);
80    map.insert("SPRACHE", Color::Cyan);
81    map.insert("VERSION", Color::LightBlue);
82    map.insert("READY", Color::Magenta);
83    map.insert("BEREIT", Color::Magenta);
84
85    map
86});
87
88impl AppColor {
89    pub fn new(color: Color) -> Self {
90        Self(color)
91    }
92
93    /// O(1) lookup from display text to color, no computation needed.
94    pub fn from_display_text(display_text: &str) -> Self {
95        let normalized = display_text.trim().to_uppercase();
96        let color = DISPLAY_COLOR_MAP
97            .get(normalized.as_str())
98            .copied()
99            .unwrap_or(Color::Green); // info fallback
100
101        Self(color)
102    }
103
104    pub fn from_category(category: &str) -> Self {
105        let normalized = category.trim().to_lowercase();
106        let color = COLOR_MAP
107            .get(normalized.as_str())
108            .copied()
109            .unwrap_or(Color::Green);
110        Self(color)
111    }
112
113    /// Legacy fallback: resolve color from any string key.
114    pub fn from_any<T: Into<String>>(source: T) -> Self {
115        let key = source.into().to_lowercase();
116        let color = COLOR_MAP.get(key.as_str()).copied().unwrap_or(Color::Green);
117        Self(color)
118    }
119
120    pub fn from_log_level(level: Level) -> Self {
121        Self::from_category(&level.to_string())
122    }
123
124    pub fn from_string(color_str: &str) -> crate::core::error::Result<Self> {
125        let normalized = color_str.trim().to_lowercase();
126        let color = COLOR_MAP
127            .get(normalized.as_str())
128            .copied()
129            .ok_or_else(|| AppError::Validation(format!("Invalid color: {}", color_str)))?;
130        Ok(Self(color))
131    }
132
133    /// Returns the resolved color along with the lookup duration for profiling.
134    pub fn from_display_text_with_timing(display_text: &str) -> (Self, std::time::Duration) {
135        let start = std::time::Instant::now();
136        let color = Self::from_display_text(display_text);
137        let duration = start.elapsed();
138        (color, duration)
139    }
140
141    pub fn available_display_texts() -> Vec<&'static str> {
142        DISPLAY_COLOR_MAP.keys().copied().collect()
143    }
144
145    pub fn available_categories() -> Vec<&'static str> {
146        COLOR_MAP.keys().copied().collect()
147    }
148
149    pub fn format_message(&self, level: &str, message: &str) -> String {
150        if level.is_empty() {
151            format!("\x1B[{}m{}\x1B[0m", self.to_ansi_code(), message)
152        } else {
153            format!(
154                "\x1B[{}m[{}] {}\x1B[0m",
155                self.to_ansi_code(),
156                level,
157                message
158            )
159        }
160    }
161
162    pub fn to_ansi_code(&self) -> u8 {
163        match self.0 {
164            Color::Black => 30,
165            Color::Red => 31,
166            Color::Green => 32,
167            Color::Yellow => 33,
168            Color::Blue => 34,
169            Color::Magenta => 35,
170            Color::Cyan => 36,
171            Color::Gray => 37,
172            Color::DarkGray => 90,
173            Color::LightRed => 91,
174            Color::LightGreen => 92,
175            Color::LightYellow => 93,
176            Color::LightBlue => 94,
177            Color::LightMagenta => 95,
178            Color::LightCyan => 96,
179            Color::White => 97,
180            _ => 37,
181        }
182    }
183
184    pub fn to_name(&self) -> &'static str {
185        COLOR_MAP
186            .iter()
187            .find(|(_, &v)| v == self.0)
188            .map(|(k, _)| *k)
189            .unwrap_or("gray")
190    }
191}
192
193impl From<AppColor> for Color {
194    fn from(app_color: AppColor) -> Self {
195        app_color.0
196    }
197}
198
199impl From<&AppColor> for Color {
200    fn from(app_color: &AppColor) -> Self {
201        app_color.0
202    }
203}
204
205impl Default for AppColor {
206    fn default() -> Self {
207        Self(Color::Gray)
208    }
209}