neumann_shell/style/
theme.rs1use owo_colors::{OwoColorize, Style};
5
6#[derive(Debug, Clone)]
11pub struct Theme {
12 pub success: Style,
14 pub error: Style,
15 pub warning: Style,
16 pub info: Style,
17
18 pub header: Style,
20 pub border: Style,
21 pub muted: Style,
22
23 pub keyword: Style,
25 pub string: Style,
26 pub number: Style,
27 pub id: Style,
28 pub label: Style,
29 pub null: Style,
30
31 pub highlight: Style,
33 pub link: Style,
34}
35
36#[allow(clippy::missing_const_for_fn)]
37impl Theme {
38 #[must_use]
40 pub fn dark() -> Self {
41 Self {
42 success: Style::new().green(),
44 error: Style::new().red().bold(),
45 warning: Style::new().yellow(),
46 info: Style::new().cyan(),
47
48 header: Style::new().magenta().bold(),
50 border: Style::new().bright_black(),
51 muted: Style::new().bright_black(),
52
53 keyword: Style::new().blue(),
55 string: Style::new().green(),
56 number: Style::new().yellow(),
57 id: Style::new().cyan().bold(),
58 label: Style::new().magenta(),
59 null: Style::new().bright_black().italic(),
60
61 highlight: Style::new().white().bold(),
63 link: Style::new().blue().underline(),
64 }
65 }
66
67 #[must_use]
69 pub fn light() -> Self {
70 Self {
71 success: Style::new().green(),
73 error: Style::new().red().bold(),
74 warning: Style::new().yellow(),
75 info: Style::new().cyan(),
76
77 header: Style::new().magenta().bold(),
79 border: Style::new().bright_black(),
80 muted: Style::new().bright_black(),
81
82 keyword: Style::new().blue(),
84 string: Style::new().green(),
85 number: Style::new().yellow(),
86 id: Style::new().cyan().bold(),
87 label: Style::new().magenta(),
88 null: Style::new().bright_black().italic(),
89
90 highlight: Style::new().black().bold(),
92 link: Style::new().blue().underline(),
93 }
94 }
95
96 #[must_use]
98 pub fn plain() -> Self {
99 Self {
100 success: Style::new(),
101 error: Style::new(),
102 warning: Style::new(),
103 info: Style::new(),
104 header: Style::new(),
105 border: Style::new(),
106 muted: Style::new(),
107 keyword: Style::new(),
108 string: Style::new(),
109 number: Style::new(),
110 id: Style::new(),
111 label: Style::new(),
112 null: Style::new(),
113 highlight: Style::new(),
114 link: Style::new(),
115 }
116 }
117
118 #[must_use]
120 pub fn phosphor() -> Self {
121 Self {
122 success: Style::new().green().bold(),
124 error: Style::new().red().bold(),
125 warning: Style::new().yellow(),
126 info: Style::new().bright_green(),
127
128 header: Style::new().bright_green().bold(),
130 border: Style::new().green(),
131 muted: Style::new().bright_black(),
132
133 keyword: Style::new().bright_green(),
135 string: Style::new().green(),
136 number: Style::new().bright_green(),
137 id: Style::new().green().bold(),
138 label: Style::new().bright_green(),
139 null: Style::new().bright_black().italic(),
140
141 highlight: Style::new().bright_green().bold(),
143 link: Style::new().green().underline(),
144 }
145 }
146
147 #[must_use]
149 pub fn auto() -> Self {
150 if console::Term::stdout().is_term() {
151 Self::phosphor() } else {
153 Self::plain()
154 }
155 }
156}
157
158impl Default for Theme {
159 fn default() -> Self {
160 Self::auto()
161 }
162}
163
164pub fn styled<T: std::fmt::Display>(text: T, style: Style) -> String {
166 text.style(style).to_string()
167}
168
169#[cfg(test)]
170mod tests {
171 use super::*;
172
173 #[test]
174 fn test_dark_theme_has_colors() {
175 let theme = Theme::dark();
176 let _ = theme.success;
178 let _ = theme.error;
179 let _ = theme.warning;
180 let _ = theme.info;
181 let _ = theme.header;
182 let _ = theme.border;
183 let _ = theme.muted;
184 let _ = theme.keyword;
185 let _ = theme.string;
186 let _ = theme.number;
187 let _ = theme.id;
188 let _ = theme.label;
189 let _ = theme.null;
190 let _ = theme.highlight;
191 let _ = theme.link;
192 }
193
194 #[test]
195 fn test_light_theme_has_colors() {
196 let theme = Theme::light();
197 let _ = theme.success;
198 let _ = theme.error;
199 let _ = theme.warning;
200 let _ = theme.info;
201 let _ = theme.header;
202 let _ = theme.border;
203 let _ = theme.muted;
204 let _ = theme.keyword;
205 let _ = theme.string;
206 let _ = theme.number;
207 let _ = theme.id;
208 let _ = theme.label;
209 let _ = theme.null;
210 let _ = theme.highlight;
211 let _ = theme.link;
212 }
213
214 #[test]
215 fn test_plain_theme_no_colors() {
216 let theme = Theme::plain();
217 let _ = theme.success;
218 let _ = theme.error;
219 let _ = theme.warning;
220 let _ = theme.info;
221 let _ = theme.header;
222 let _ = theme.border;
223 let _ = theme.muted;
224 let _ = theme.keyword;
225 let _ = theme.string;
226 let _ = theme.number;
227 let _ = theme.id;
228 let _ = theme.label;
229 let _ = theme.null;
230 let _ = theme.highlight;
231 let _ = theme.link;
232 }
233
234 #[test]
235 fn test_phosphor_theme_has_colors() {
236 let theme = Theme::phosphor();
237 let _ = theme.success;
238 let _ = theme.error;
239 let _ = theme.warning;
240 let _ = theme.info;
241 let _ = theme.header;
242 let _ = theme.border;
243 let _ = theme.muted;
244 let _ = theme.keyword;
245 let _ = theme.string;
246 let _ = theme.number;
247 let _ = theme.id;
248 let _ = theme.label;
249 let _ = theme.null;
250 let _ = theme.highlight;
251 let _ = theme.link;
252 }
253
254 #[test]
255 fn test_styled_applies_style() {
256 let style = Style::new().green();
257 let result = styled("test", style);
258 assert!(!result.is_empty());
259 }
260
261 #[test]
262 fn test_styled_with_number() {
263 let style = Style::new().yellow();
264 let result = styled(42, style);
265 assert!(result.contains("42"));
266 }
267
268 #[test]
269 fn test_default_theme() {
270 let _theme = Theme::default();
271 }
272
273 #[test]
274 fn test_theme_clone() {
275 let theme = Theme::dark();
276 let cloned = theme;
277 let _ = cloned.success;
278 }
279
280 #[test]
281 fn test_theme_debug() {
282 let theme = Theme::dark();
283 let debug = format!("{theme:?}");
284 assert!(debug.contains("Theme"));
285 }
286
287 #[test]
288 fn test_auto_theme() {
289 let _theme = Theme::auto();
290 }
291}