Skip to main content

fastmcp_console/
theme.rs

1//! FastMCP color theme
2
3use rich_rust::prelude::*;
4
5/// FastMCP color palette
6#[derive(Debug)]
7pub struct FastMcpTheme {
8    // Primary colors
9    pub primary: Color,   // Vibrant cyan (#00d4ff)
10    pub secondary: Color, // Soft purple (#a855f7)
11    pub accent: Color,    // Electric green (#22c55e)
12
13    // Semantic colors
14    pub success: Color, // Green (#22c55e)
15    pub warning: Color, // Amber (#f59e0b)
16    pub error: Color,   // Red (#ef4444)
17    pub info: Color,    // Blue (#3b82f6)
18
19    // Neutral palette
20    pub text: Color,       // Light gray (#e5e7eb)
21    pub text_muted: Color, // Medium gray (#9ca3af)
22    pub text_dim: Color,   // Dark gray (#6b7280)
23    pub border: Color,     // Border gray (#374151)
24    pub background: Color, // Dark background (#1f2937)
25
26    // Styles
27    pub header_style: Style,
28    pub subheader_style: Style,
29    pub label_style: Style,
30    pub value_style: Style,
31    pub key_style: Style,
32    pub muted_style: Style,
33    pub success_style: Style,
34    pub warning_style: Style,
35    pub error_style: Style,
36    pub info_style: Style,
37    pub border_style: Style,
38}
39
40impl Default for FastMcpTheme {
41    fn default() -> Self {
42        Self {
43            // Colors
44            primary: Color::from_rgb(0, 212, 255),
45            secondary: Color::from_rgb(168, 85, 247),
46            accent: Color::from_rgb(34, 197, 94),
47            success: Color::from_rgb(34, 197, 94),
48            warning: Color::from_rgb(245, 158, 11),
49            error: Color::from_rgb(239, 68, 68),
50            info: Color::from_rgb(59, 130, 246),
51            text: Color::from_rgb(229, 231, 235),
52            text_muted: Color::from_rgb(156, 163, 175),
53            text_dim: Color::from_rgb(107, 114, 128),
54            border: Color::from_rgb(55, 65, 81),
55            background: Color::from_rgb(31, 41, 55),
56
57            // Styles
58            header_style: Style::new().bold().color(Color::from_rgb(0, 212, 255)),
59            subheader_style: Style::new().color(Color::from_rgb(156, 163, 175)),
60            label_style: Style::new().color(Color::from_rgb(107, 114, 128)),
61            value_style: Style::new().color(Color::from_rgb(229, 231, 235)),
62            key_style: Style::new().bold().color(Color::from_rgb(168, 85, 247)),
63            muted_style: Style::new().dim().color(Color::from_rgb(107, 114, 128)),
64            success_style: Style::new().bold().color(Color::from_rgb(34, 197, 94)),
65            warning_style: Style::new().bold().color(Color::from_rgb(245, 158, 11)),
66            error_style: Style::new().bold().color(Color::from_rgb(239, 68, 68)),
67            info_style: Style::new().color(Color::from_rgb(59, 130, 246)),
68            border_style: Style::new().color(Color::from_rgb(55, 65, 81)),
69        }
70    }
71}
72
73/// Global theme access
74pub fn theme() -> &'static FastMcpTheme {
75    static THEME: std::sync::OnceLock<FastMcpTheme> = std::sync::OnceLock::new();
76    THEME.get_or_init(FastMcpTheme::default)
77}
78
79#[cfg(test)]
80mod tests {
81    use super::*;
82
83    #[test]
84    fn test_theme_default_colors() {
85        let t = FastMcpTheme::default();
86        // Verify primary cyan color
87        assert_eq!(t.primary.triplet.map(|tr| tr.red), Some(0));
88        assert_eq!(t.primary.triplet.map(|tr| tr.green), Some(212));
89        assert_eq!(t.primary.triplet.map(|tr| tr.blue), Some(255));
90    }
91
92    #[test]
93    fn test_theme_semantic_colors() {
94        let t = FastMcpTheme::default();
95        // Success should be green
96        assert_eq!(t.success.triplet.map(|tr| tr.green), Some(197));
97        // Error should be red
98        assert_eq!(t.error.triplet.map(|tr| tr.red), Some(239));
99        // Warning should be amber
100        assert_eq!(t.warning.triplet.map(|tr| tr.red), Some(245));
101    }
102
103    #[test]
104    fn test_theme_global_singleton() {
105        let t1 = theme();
106        let t2 = theme();
107        // Should return the same static reference
108        assert!(std::ptr::eq(t1, t2));
109    }
110
111    #[test]
112    fn test_theme_styles_exist() {
113        let t = FastMcpTheme::default();
114        // Just verify styles are accessible without panicking
115        let _ = t.header_style.clone();
116        let _ = t.error_style.clone();
117        let _ = t.success_style.clone();
118        let _ = t.warning_style.clone();
119    }
120
121    // =========================================================================
122    // Additional coverage tests (bd-1p24)
123    // =========================================================================
124
125    #[test]
126    fn theme_neutral_palette() {
127        let t = FastMcpTheme::default();
128        // text: #e5e7eb (229, 231, 235)
129        assert_eq!(t.text.triplet.map(|tr| tr.red), Some(229));
130        // text_muted: #9ca3af (156, 163, 175)
131        assert_eq!(t.text_muted.triplet.map(|tr| tr.red), Some(156));
132        // text_dim: #6b7280 (107, 114, 128)
133        assert_eq!(t.text_dim.triplet.map(|tr| tr.red), Some(107));
134        // border: #374151 (55, 65, 81)
135        assert_eq!(t.border.triplet.map(|tr| tr.red), Some(55));
136        // background: #1f2937 (31, 41, 55)
137        assert_eq!(t.background.triplet.map(|tr| tr.red), Some(31));
138    }
139
140    #[test]
141    fn theme_info_and_secondary_colors() {
142        let t = FastMcpTheme::default();
143        // info: #3b82f6 (59, 130, 246)
144        assert_eq!(t.info.triplet.map(|tr| tr.blue), Some(246));
145        // secondary: #a855f7 (168, 85, 247)
146        assert_eq!(t.secondary.triplet.map(|tr| tr.red), Some(168));
147        // accent matches success
148        assert_eq!(t.accent.triplet.map(|tr| tr.green), Some(197));
149    }
150
151    #[test]
152    fn theme_all_styles_accessible() {
153        let t = FastMcpTheme::default();
154        // Verify all remaining styles are accessible
155        let _ = t.subheader_style.clone();
156        let _ = t.label_style.clone();
157        let _ = t.value_style.clone();
158        let _ = t.key_style.clone();
159        let _ = t.muted_style.clone();
160        let _ = t.info_style.clone();
161        let _ = t.border_style.clone();
162    }
163
164    #[test]
165    fn theme_debug_impl() {
166        let t = FastMcpTheme::default();
167        let debug = format!("{t:?}");
168        assert!(debug.contains("FastMcpTheme"));
169    }
170}