1use ratatui::style::Color;
7use schemars::JsonSchema;
8use serde::{Deserialize, Serialize};
9
10pub const THEME_DARK: &str = "dark";
11pub const THEME_LIGHT: &str = "light";
12pub const THEME_HIGH_CONTRAST: &str = "high-contrast";
13pub const THEME_NOSTALGIA: &str = "nostalgia";
14pub const THEME_DRACULA: &str = "dracula";
15pub const THEME_NORD: &str = "nord";
16pub const THEME_SOLARIZED_DARK: &str = "solarized-dark";
17
18pub struct BuiltinTheme {
20 pub name: &'static str,
21 pub pack: &'static str,
23 pub json: &'static str,
24}
25
26include!(concat!(env!("OUT_DIR"), "/builtin_themes.rs"));
28
29#[derive(Debug, Clone, PartialEq, Eq)]
31pub struct ThemeInfo {
32 pub name: String,
34 pub pack: String,
36}
37
38impl ThemeInfo {
39 pub fn new(name: impl Into<String>, pack: impl Into<String>) -> Self {
41 Self {
42 name: name.into(),
43 pack: pack.into(),
44 }
45 }
46
47 pub fn display_name(&self) -> String {
49 if self.pack.is_empty() {
50 self.name.clone()
51 } else {
52 format!("{} ({})", self.name, self.pack)
53 }
54 }
55}
56
57pub fn color_to_rgb(color: Color) -> Option<(u8, u8, u8)> {
60 match color {
61 Color::Rgb(r, g, b) => Some((r, g, b)),
62 Color::White => Some((255, 255, 255)),
63 Color::Black => Some((0, 0, 0)),
64 Color::Red => Some((205, 0, 0)),
65 Color::Green => Some((0, 205, 0)),
66 Color::Blue => Some((0, 0, 238)),
67 Color::Yellow => Some((205, 205, 0)),
68 Color::Magenta => Some((205, 0, 205)),
69 Color::Cyan => Some((0, 205, 205)),
70 Color::Gray => Some((229, 229, 229)),
71 Color::DarkGray => Some((127, 127, 127)),
72 Color::LightRed => Some((255, 0, 0)),
73 Color::LightGreen => Some((0, 255, 0)),
74 Color::LightBlue => Some((92, 92, 255)),
75 Color::LightYellow => Some((255, 255, 0)),
76 Color::LightMagenta => Some((255, 0, 255)),
77 Color::LightCyan => Some((0, 255, 255)),
78 Color::Reset | Color::Indexed(_) => None,
79 }
80}
81
82pub fn brighten_color(color: Color, amount: u8) -> Color {
85 if let Some((r, g, b)) = color_to_rgb(color) {
86 Color::Rgb(
87 r.saturating_add(amount),
88 g.saturating_add(amount),
89 b.saturating_add(amount),
90 )
91 } else {
92 color
93 }
94}
95
96#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
98#[serde(untagged)]
99pub enum ColorDef {
100 Rgb(u8, u8, u8),
102 Named(String),
104}
105
106impl From<ColorDef> for Color {
107 fn from(def: ColorDef) -> Self {
108 match def {
109 ColorDef::Rgb(r, g, b) => Color::Rgb(r, g, b),
110 ColorDef::Named(name) => match name.as_str() {
111 "Black" => Color::Black,
112 "Red" => Color::Red,
113 "Green" => Color::Green,
114 "Yellow" => Color::Yellow,
115 "Blue" => Color::Blue,
116 "Magenta" => Color::Magenta,
117 "Cyan" => Color::Cyan,
118 "Gray" => Color::Gray,
119 "DarkGray" => Color::DarkGray,
120 "LightRed" => Color::LightRed,
121 "LightGreen" => Color::LightGreen,
122 "LightYellow" => Color::LightYellow,
123 "LightBlue" => Color::LightBlue,
124 "LightMagenta" => Color::LightMagenta,
125 "LightCyan" => Color::LightCyan,
126 "White" => Color::White,
127 "Default" | "Reset" => Color::Reset,
129 _ => Color::White, },
131 }
132 }
133}
134
135impl From<Color> for ColorDef {
136 fn from(color: Color) -> Self {
137 match color {
138 Color::Rgb(r, g, b) => ColorDef::Rgb(r, g, b),
139 Color::White => ColorDef::Named("White".to_string()),
140 Color::Black => ColorDef::Named("Black".to_string()),
141 Color::Red => ColorDef::Named("Red".to_string()),
142 Color::Green => ColorDef::Named("Green".to_string()),
143 Color::Blue => ColorDef::Named("Blue".to_string()),
144 Color::Yellow => ColorDef::Named("Yellow".to_string()),
145 Color::Magenta => ColorDef::Named("Magenta".to_string()),
146 Color::Cyan => ColorDef::Named("Cyan".to_string()),
147 Color::Gray => ColorDef::Named("Gray".to_string()),
148 Color::DarkGray => ColorDef::Named("DarkGray".to_string()),
149 Color::LightRed => ColorDef::Named("LightRed".to_string()),
150 Color::LightGreen => ColorDef::Named("LightGreen".to_string()),
151 Color::LightBlue => ColorDef::Named("LightBlue".to_string()),
152 Color::LightYellow => ColorDef::Named("LightYellow".to_string()),
153 Color::LightMagenta => ColorDef::Named("LightMagenta".to_string()),
154 Color::LightCyan => ColorDef::Named("LightCyan".to_string()),
155 Color::Reset => ColorDef::Named("Default".to_string()),
156 Color::Indexed(_) => {
157 if let Some((r, g, b)) = color_to_rgb(color) {
159 ColorDef::Rgb(r, g, b)
160 } else {
161 ColorDef::Named("Default".to_string())
162 }
163 }
164 }
165 }
166}
167
168#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
170pub struct ThemeFile {
171 pub name: String,
173 pub editor: EditorColors,
175 pub ui: UiColors,
177 pub search: SearchColors,
179 pub diagnostic: DiagnosticColors,
181 pub syntax: SyntaxColors,
183}
184
185#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
187pub struct EditorColors {
188 #[serde(default = "default_editor_bg")]
190 pub bg: ColorDef,
191 #[serde(default = "default_editor_fg")]
193 pub fg: ColorDef,
194 #[serde(default = "default_cursor")]
196 pub cursor: ColorDef,
197 #[serde(default = "default_inactive_cursor")]
199 pub inactive_cursor: ColorDef,
200 #[serde(default = "default_selection_bg")]
202 pub selection_bg: ColorDef,
203 #[serde(default = "default_current_line_bg")]
205 pub current_line_bg: ColorDef,
206 #[serde(default = "default_line_number_fg")]
208 pub line_number_fg: ColorDef,
209 #[serde(default = "default_line_number_bg")]
211 pub line_number_bg: ColorDef,
212 #[serde(default = "default_diff_add_bg")]
214 pub diff_add_bg: ColorDef,
215 #[serde(default = "default_diff_remove_bg")]
217 pub diff_remove_bg: ColorDef,
218 #[serde(default = "default_diff_modify_bg")]
220 pub diff_modify_bg: ColorDef,
221 #[serde(default = "default_ruler_bg")]
223 pub ruler_bg: ColorDef,
224 #[serde(default = "default_whitespace_indicator_fg")]
226 pub whitespace_indicator_fg: ColorDef,
227}
228
229fn default_editor_bg() -> ColorDef {
231 ColorDef::Rgb(30, 30, 30)
232}
233fn default_editor_fg() -> ColorDef {
234 ColorDef::Rgb(212, 212, 212)
235}
236fn default_cursor() -> ColorDef {
237 ColorDef::Rgb(255, 255, 255)
238}
239fn default_inactive_cursor() -> ColorDef {
240 ColorDef::Named("DarkGray".to_string())
241}
242fn default_selection_bg() -> ColorDef {
243 ColorDef::Rgb(38, 79, 120)
244}
245fn default_current_line_bg() -> ColorDef {
246 ColorDef::Rgb(40, 40, 40)
247}
248fn default_line_number_fg() -> ColorDef {
249 ColorDef::Rgb(100, 100, 100)
250}
251fn default_line_number_bg() -> ColorDef {
252 ColorDef::Rgb(30, 30, 30)
253}
254fn default_diff_add_bg() -> ColorDef {
255 ColorDef::Rgb(35, 60, 35) }
257fn default_diff_remove_bg() -> ColorDef {
258 ColorDef::Rgb(70, 35, 35) }
260fn default_diff_modify_bg() -> ColorDef {
261 ColorDef::Rgb(40, 38, 30) }
263fn default_ruler_bg() -> ColorDef {
264 ColorDef::Rgb(50, 50, 50) }
266fn default_whitespace_indicator_fg() -> ColorDef {
267 ColorDef::Rgb(70, 70, 70) }
269
270#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
272pub struct UiColors {
273 #[serde(default = "default_tab_active_fg")]
275 pub tab_active_fg: ColorDef,
276 #[serde(default = "default_tab_active_bg")]
278 pub tab_active_bg: ColorDef,
279 #[serde(default = "default_tab_inactive_fg")]
281 pub tab_inactive_fg: ColorDef,
282 #[serde(default = "default_tab_inactive_bg")]
284 pub tab_inactive_bg: ColorDef,
285 #[serde(default = "default_tab_separator_bg")]
287 pub tab_separator_bg: ColorDef,
288 #[serde(default = "default_tab_close_hover_fg")]
290 pub tab_close_hover_fg: ColorDef,
291 #[serde(default = "default_tab_hover_bg")]
293 pub tab_hover_bg: ColorDef,
294 #[serde(default = "default_menu_bg")]
296 pub menu_bg: ColorDef,
297 #[serde(default = "default_menu_fg")]
299 pub menu_fg: ColorDef,
300 #[serde(default = "default_menu_active_bg")]
302 pub menu_active_bg: ColorDef,
303 #[serde(default = "default_menu_active_fg")]
305 pub menu_active_fg: ColorDef,
306 #[serde(default = "default_menu_dropdown_bg")]
308 pub menu_dropdown_bg: ColorDef,
309 #[serde(default = "default_menu_dropdown_fg")]
311 pub menu_dropdown_fg: ColorDef,
312 #[serde(default = "default_menu_highlight_bg")]
314 pub menu_highlight_bg: ColorDef,
315 #[serde(default = "default_menu_highlight_fg")]
317 pub menu_highlight_fg: ColorDef,
318 #[serde(default = "default_menu_border_fg")]
320 pub menu_border_fg: ColorDef,
321 #[serde(default = "default_menu_separator_fg")]
323 pub menu_separator_fg: ColorDef,
324 #[serde(default = "default_menu_hover_bg")]
326 pub menu_hover_bg: ColorDef,
327 #[serde(default = "default_menu_hover_fg")]
329 pub menu_hover_fg: ColorDef,
330 #[serde(default = "default_menu_disabled_fg")]
332 pub menu_disabled_fg: ColorDef,
333 #[serde(default = "default_menu_disabled_bg")]
335 pub menu_disabled_bg: ColorDef,
336 #[serde(default = "default_status_bar_fg")]
338 pub status_bar_fg: ColorDef,
339 #[serde(default = "default_status_bar_bg")]
341 pub status_bar_bg: ColorDef,
342 #[serde(default = "default_prompt_fg")]
344 pub prompt_fg: ColorDef,
345 #[serde(default = "default_prompt_bg")]
347 pub prompt_bg: ColorDef,
348 #[serde(default = "default_prompt_selection_fg")]
350 pub prompt_selection_fg: ColorDef,
351 #[serde(default = "default_prompt_selection_bg")]
353 pub prompt_selection_bg: ColorDef,
354 #[serde(default = "default_popup_border_fg")]
356 pub popup_border_fg: ColorDef,
357 #[serde(default = "default_popup_bg")]
359 pub popup_bg: ColorDef,
360 #[serde(default = "default_popup_selection_bg")]
362 pub popup_selection_bg: ColorDef,
363 #[serde(default = "default_popup_selection_fg")]
365 pub popup_selection_fg: ColorDef,
366 #[serde(default = "default_popup_text_fg")]
368 pub popup_text_fg: ColorDef,
369 #[serde(default = "default_suggestion_bg")]
371 pub suggestion_bg: ColorDef,
372 #[serde(default = "default_suggestion_selected_bg")]
374 pub suggestion_selected_bg: ColorDef,
375 #[serde(default = "default_help_bg")]
377 pub help_bg: ColorDef,
378 #[serde(default = "default_help_fg")]
380 pub help_fg: ColorDef,
381 #[serde(default = "default_help_key_fg")]
383 pub help_key_fg: ColorDef,
384 #[serde(default = "default_help_separator_fg")]
386 pub help_separator_fg: ColorDef,
387 #[serde(default = "default_help_indicator_fg")]
389 pub help_indicator_fg: ColorDef,
390 #[serde(default = "default_help_indicator_bg")]
392 pub help_indicator_bg: ColorDef,
393 #[serde(default = "default_inline_code_bg")]
395 pub inline_code_bg: ColorDef,
396 #[serde(default = "default_split_separator_fg")]
398 pub split_separator_fg: ColorDef,
399 #[serde(default = "default_split_separator_hover_fg")]
401 pub split_separator_hover_fg: ColorDef,
402 #[serde(default = "default_scrollbar_track_fg")]
404 pub scrollbar_track_fg: ColorDef,
405 #[serde(default = "default_scrollbar_thumb_fg")]
407 pub scrollbar_thumb_fg: ColorDef,
408 #[serde(default = "default_scrollbar_track_hover_fg")]
410 pub scrollbar_track_hover_fg: ColorDef,
411 #[serde(default = "default_scrollbar_thumb_hover_fg")]
413 pub scrollbar_thumb_hover_fg: ColorDef,
414 #[serde(default = "default_compose_margin_bg")]
416 pub compose_margin_bg: ColorDef,
417 #[serde(default = "default_semantic_highlight_bg")]
419 pub semantic_highlight_bg: ColorDef,
420 #[serde(default = "default_terminal_bg")]
422 pub terminal_bg: ColorDef,
423 #[serde(default = "default_terminal_fg")]
425 pub terminal_fg: ColorDef,
426 #[serde(default = "default_status_warning_indicator_bg")]
428 pub status_warning_indicator_bg: ColorDef,
429 #[serde(default = "default_status_warning_indicator_fg")]
431 pub status_warning_indicator_fg: ColorDef,
432 #[serde(default = "default_status_error_indicator_bg")]
434 pub status_error_indicator_bg: ColorDef,
435 #[serde(default = "default_status_error_indicator_fg")]
437 pub status_error_indicator_fg: ColorDef,
438 #[serde(default = "default_status_warning_indicator_hover_bg")]
440 pub status_warning_indicator_hover_bg: ColorDef,
441 #[serde(default = "default_status_warning_indicator_hover_fg")]
443 pub status_warning_indicator_hover_fg: ColorDef,
444 #[serde(default = "default_status_error_indicator_hover_bg")]
446 pub status_error_indicator_hover_bg: ColorDef,
447 #[serde(default = "default_status_error_indicator_hover_fg")]
449 pub status_error_indicator_hover_fg: ColorDef,
450 #[serde(default = "default_tab_drop_zone_bg")]
452 pub tab_drop_zone_bg: ColorDef,
453 #[serde(default = "default_tab_drop_zone_border")]
455 pub tab_drop_zone_border: ColorDef,
456 #[serde(default = "default_settings_selected_bg")]
458 pub settings_selected_bg: ColorDef,
459 #[serde(default = "default_settings_selected_fg")]
461 pub settings_selected_fg: ColorDef,
462}
463
464fn default_tab_active_fg() -> ColorDef {
467 ColorDef::Named("Yellow".to_string())
468}
469fn default_tab_active_bg() -> ColorDef {
470 ColorDef::Named("Blue".to_string())
471}
472fn default_tab_inactive_fg() -> ColorDef {
473 ColorDef::Named("White".to_string())
474}
475fn default_tab_inactive_bg() -> ColorDef {
476 ColorDef::Named("DarkGray".to_string())
477}
478fn default_tab_separator_bg() -> ColorDef {
479 ColorDef::Named("Black".to_string())
480}
481fn default_tab_close_hover_fg() -> ColorDef {
482 ColorDef::Rgb(255, 100, 100) }
484fn default_tab_hover_bg() -> ColorDef {
485 ColorDef::Rgb(70, 70, 75) }
487
488fn default_menu_bg() -> ColorDef {
490 ColorDef::Rgb(60, 60, 65)
491}
492fn default_menu_fg() -> ColorDef {
493 ColorDef::Rgb(220, 220, 220)
494}
495fn default_menu_active_bg() -> ColorDef {
496 ColorDef::Rgb(60, 60, 60)
497}
498fn default_menu_active_fg() -> ColorDef {
499 ColorDef::Rgb(255, 255, 255)
500}
501fn default_menu_dropdown_bg() -> ColorDef {
502 ColorDef::Rgb(50, 50, 50)
503}
504fn default_menu_dropdown_fg() -> ColorDef {
505 ColorDef::Rgb(220, 220, 220)
506}
507fn default_menu_highlight_bg() -> ColorDef {
508 ColorDef::Rgb(70, 130, 180)
509}
510fn default_menu_highlight_fg() -> ColorDef {
511 ColorDef::Rgb(255, 255, 255)
512}
513fn default_menu_border_fg() -> ColorDef {
514 ColorDef::Rgb(100, 100, 100)
515}
516fn default_menu_separator_fg() -> ColorDef {
517 ColorDef::Rgb(80, 80, 80)
518}
519fn default_menu_hover_bg() -> ColorDef {
520 ColorDef::Rgb(55, 55, 55)
521}
522fn default_menu_hover_fg() -> ColorDef {
523 ColorDef::Rgb(255, 255, 255)
524}
525fn default_menu_disabled_fg() -> ColorDef {
526 ColorDef::Rgb(100, 100, 100) }
528fn default_menu_disabled_bg() -> ColorDef {
529 ColorDef::Rgb(50, 50, 50) }
531fn default_status_bar_fg() -> ColorDef {
533 ColorDef::Named("White".to_string())
534}
535fn default_status_bar_bg() -> ColorDef {
536 ColorDef::Named("DarkGray".to_string())
537}
538
539fn default_prompt_fg() -> ColorDef {
541 ColorDef::Named("White".to_string())
542}
543fn default_prompt_bg() -> ColorDef {
544 ColorDef::Named("Black".to_string())
545}
546fn default_prompt_selection_fg() -> ColorDef {
547 ColorDef::Named("White".to_string())
548}
549fn default_prompt_selection_bg() -> ColorDef {
550 ColorDef::Rgb(58, 79, 120)
551}
552
553fn default_popup_border_fg() -> ColorDef {
555 ColorDef::Named("Gray".to_string())
556}
557fn default_popup_bg() -> ColorDef {
558 ColorDef::Rgb(30, 30, 30)
559}
560fn default_popup_selection_bg() -> ColorDef {
561 ColorDef::Rgb(58, 79, 120)
562}
563fn default_popup_selection_fg() -> ColorDef {
564 ColorDef::Rgb(255, 255, 255) }
566fn default_popup_text_fg() -> ColorDef {
567 ColorDef::Named("White".to_string())
568}
569
570fn default_suggestion_bg() -> ColorDef {
572 ColorDef::Rgb(30, 30, 30)
573}
574fn default_suggestion_selected_bg() -> ColorDef {
575 ColorDef::Rgb(58, 79, 120)
576}
577
578fn default_help_bg() -> ColorDef {
580 ColorDef::Named("Black".to_string())
581}
582fn default_help_fg() -> ColorDef {
583 ColorDef::Named("White".to_string())
584}
585fn default_help_key_fg() -> ColorDef {
586 ColorDef::Named("Cyan".to_string())
587}
588fn default_help_separator_fg() -> ColorDef {
589 ColorDef::Named("DarkGray".to_string())
590}
591fn default_help_indicator_fg() -> ColorDef {
592 ColorDef::Named("Red".to_string())
593}
594fn default_help_indicator_bg() -> ColorDef {
595 ColorDef::Named("Black".to_string())
596}
597
598fn default_inline_code_bg() -> ColorDef {
599 ColorDef::Named("DarkGray".to_string())
600}
601
602fn default_split_separator_fg() -> ColorDef {
604 ColorDef::Rgb(100, 100, 100)
605}
606fn default_split_separator_hover_fg() -> ColorDef {
607 ColorDef::Rgb(100, 149, 237) }
609fn default_scrollbar_track_fg() -> ColorDef {
610 ColorDef::Named("DarkGray".to_string())
611}
612fn default_scrollbar_thumb_fg() -> ColorDef {
613 ColorDef::Named("Gray".to_string())
614}
615fn default_scrollbar_track_hover_fg() -> ColorDef {
616 ColorDef::Named("Gray".to_string())
617}
618fn default_scrollbar_thumb_hover_fg() -> ColorDef {
619 ColorDef::Named("White".to_string())
620}
621fn default_compose_margin_bg() -> ColorDef {
622 ColorDef::Rgb(18, 18, 18) }
624fn default_semantic_highlight_bg() -> ColorDef {
625 ColorDef::Rgb(60, 60, 80) }
627fn default_terminal_bg() -> ColorDef {
628 ColorDef::Named("Default".to_string()) }
630fn default_terminal_fg() -> ColorDef {
631 ColorDef::Named("Default".to_string()) }
633fn default_status_warning_indicator_bg() -> ColorDef {
634 ColorDef::Rgb(181, 137, 0) }
636fn default_status_warning_indicator_fg() -> ColorDef {
637 ColorDef::Rgb(0, 0, 0) }
639fn default_status_error_indicator_bg() -> ColorDef {
640 ColorDef::Rgb(220, 50, 47) }
642fn default_status_error_indicator_fg() -> ColorDef {
643 ColorDef::Rgb(255, 255, 255) }
645fn default_status_warning_indicator_hover_bg() -> ColorDef {
646 ColorDef::Rgb(211, 167, 30) }
648fn default_status_warning_indicator_hover_fg() -> ColorDef {
649 ColorDef::Rgb(0, 0, 0) }
651fn default_status_error_indicator_hover_bg() -> ColorDef {
652 ColorDef::Rgb(250, 80, 77) }
654fn default_status_error_indicator_hover_fg() -> ColorDef {
655 ColorDef::Rgb(255, 255, 255) }
657fn default_tab_drop_zone_bg() -> ColorDef {
658 ColorDef::Rgb(70, 130, 180) }
660fn default_tab_drop_zone_border() -> ColorDef {
661 ColorDef::Rgb(100, 149, 237) }
663fn default_settings_selected_bg() -> ColorDef {
664 ColorDef::Rgb(60, 60, 70) }
666fn default_settings_selected_fg() -> ColorDef {
667 ColorDef::Rgb(255, 255, 255) }
669
670#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
672pub struct SearchColors {
673 #[serde(default = "default_search_match_bg")]
675 pub match_bg: ColorDef,
676 #[serde(default = "default_search_match_fg")]
678 pub match_fg: ColorDef,
679}
680
681fn default_search_match_bg() -> ColorDef {
683 ColorDef::Rgb(100, 100, 20)
684}
685fn default_search_match_fg() -> ColorDef {
686 ColorDef::Rgb(255, 255, 255)
687}
688
689#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
691pub struct DiagnosticColors {
692 #[serde(default = "default_diagnostic_error_fg")]
694 pub error_fg: ColorDef,
695 #[serde(default = "default_diagnostic_error_bg")]
697 pub error_bg: ColorDef,
698 #[serde(default = "default_diagnostic_warning_fg")]
700 pub warning_fg: ColorDef,
701 #[serde(default = "default_diagnostic_warning_bg")]
703 pub warning_bg: ColorDef,
704 #[serde(default = "default_diagnostic_info_fg")]
706 pub info_fg: ColorDef,
707 #[serde(default = "default_diagnostic_info_bg")]
709 pub info_bg: ColorDef,
710 #[serde(default = "default_diagnostic_hint_fg")]
712 pub hint_fg: ColorDef,
713 #[serde(default = "default_diagnostic_hint_bg")]
715 pub hint_bg: ColorDef,
716}
717
718fn default_diagnostic_error_fg() -> ColorDef {
720 ColorDef::Named("Red".to_string())
721}
722fn default_diagnostic_error_bg() -> ColorDef {
723 ColorDef::Rgb(60, 20, 20)
724}
725fn default_diagnostic_warning_fg() -> ColorDef {
726 ColorDef::Named("Yellow".to_string())
727}
728fn default_diagnostic_warning_bg() -> ColorDef {
729 ColorDef::Rgb(60, 50, 0)
730}
731fn default_diagnostic_info_fg() -> ColorDef {
732 ColorDef::Named("Blue".to_string())
733}
734fn default_diagnostic_info_bg() -> ColorDef {
735 ColorDef::Rgb(0, 30, 60)
736}
737fn default_diagnostic_hint_fg() -> ColorDef {
738 ColorDef::Named("Gray".to_string())
739}
740fn default_diagnostic_hint_bg() -> ColorDef {
741 ColorDef::Rgb(30, 30, 30)
742}
743
744#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
746pub struct SyntaxColors {
747 #[serde(default = "default_syntax_keyword")]
749 pub keyword: ColorDef,
750 #[serde(default = "default_syntax_string")]
752 pub string: ColorDef,
753 #[serde(default = "default_syntax_comment")]
755 pub comment: ColorDef,
756 #[serde(default = "default_syntax_function")]
758 pub function: ColorDef,
759 #[serde(rename = "type", default = "default_syntax_type")]
761 pub type_: ColorDef,
762 #[serde(default = "default_syntax_variable")]
764 pub variable: ColorDef,
765 #[serde(default = "default_syntax_constant")]
767 pub constant: ColorDef,
768 #[serde(default = "default_syntax_operator")]
770 pub operator: ColorDef,
771}
772
773fn default_syntax_keyword() -> ColorDef {
775 ColorDef::Rgb(86, 156, 214)
776}
777fn default_syntax_string() -> ColorDef {
778 ColorDef::Rgb(206, 145, 120)
779}
780fn default_syntax_comment() -> ColorDef {
781 ColorDef::Rgb(106, 153, 85)
782}
783fn default_syntax_function() -> ColorDef {
784 ColorDef::Rgb(220, 220, 170)
785}
786fn default_syntax_type() -> ColorDef {
787 ColorDef::Rgb(78, 201, 176)
788}
789fn default_syntax_variable() -> ColorDef {
790 ColorDef::Rgb(156, 220, 254)
791}
792fn default_syntax_constant() -> ColorDef {
793 ColorDef::Rgb(79, 193, 255)
794}
795fn default_syntax_operator() -> ColorDef {
796 ColorDef::Rgb(212, 212, 212)
797}
798
799#[derive(Debug, Clone)]
801pub struct Theme {
802 pub name: String,
804
805 pub editor_bg: Color,
807 pub editor_fg: Color,
808 pub cursor: Color,
809 pub inactive_cursor: Color,
810 pub selection_bg: Color,
811 pub current_line_bg: Color,
812 pub line_number_fg: Color,
813 pub line_number_bg: Color,
814
815 pub ruler_bg: Color,
817
818 pub whitespace_indicator_fg: Color,
820
821 pub diff_add_bg: Color,
823 pub diff_remove_bg: Color,
824 pub diff_modify_bg: Color,
825 pub diff_add_highlight_bg: Color,
827 pub diff_remove_highlight_bg: Color,
829
830 pub tab_active_fg: Color,
832 pub tab_active_bg: Color,
833 pub tab_inactive_fg: Color,
834 pub tab_inactive_bg: Color,
835 pub tab_separator_bg: Color,
836 pub tab_close_hover_fg: Color,
837 pub tab_hover_bg: Color,
838
839 pub menu_bg: Color,
841 pub menu_fg: Color,
842 pub menu_active_bg: Color,
843 pub menu_active_fg: Color,
844 pub menu_dropdown_bg: Color,
845 pub menu_dropdown_fg: Color,
846 pub menu_highlight_bg: Color,
847 pub menu_highlight_fg: Color,
848 pub menu_border_fg: Color,
849 pub menu_separator_fg: Color,
850 pub menu_hover_bg: Color,
851 pub menu_hover_fg: Color,
852 pub menu_disabled_fg: Color,
853 pub menu_disabled_bg: Color,
854
855 pub status_bar_fg: Color,
856 pub status_bar_bg: Color,
857 pub prompt_fg: Color,
858 pub prompt_bg: Color,
859 pub prompt_selection_fg: Color,
860 pub prompt_selection_bg: Color,
861
862 pub popup_border_fg: Color,
863 pub popup_bg: Color,
864 pub popup_selection_bg: Color,
865 pub popup_selection_fg: Color,
866 pub popup_text_fg: Color,
867
868 pub suggestion_bg: Color,
869 pub suggestion_selected_bg: Color,
870
871 pub help_bg: Color,
872 pub help_fg: Color,
873 pub help_key_fg: Color,
874 pub help_separator_fg: Color,
875
876 pub help_indicator_fg: Color,
877 pub help_indicator_bg: Color,
878
879 pub inline_code_bg: Color,
881
882 pub split_separator_fg: Color,
883 pub split_separator_hover_fg: Color,
884
885 pub scrollbar_track_fg: Color,
887 pub scrollbar_thumb_fg: Color,
888 pub scrollbar_track_hover_fg: Color,
889 pub scrollbar_thumb_hover_fg: Color,
890
891 pub compose_margin_bg: Color,
893
894 pub semantic_highlight_bg: Color,
896
897 pub terminal_bg: Color,
899 pub terminal_fg: Color,
900
901 pub status_warning_indicator_bg: Color,
903 pub status_warning_indicator_fg: Color,
904 pub status_error_indicator_bg: Color,
905 pub status_error_indicator_fg: Color,
906 pub status_warning_indicator_hover_bg: Color,
907 pub status_warning_indicator_hover_fg: Color,
908 pub status_error_indicator_hover_bg: Color,
909 pub status_error_indicator_hover_fg: Color,
910
911 pub tab_drop_zone_bg: Color,
913 pub tab_drop_zone_border: Color,
914
915 pub settings_selected_bg: Color,
917 pub settings_selected_fg: Color,
918
919 pub search_match_bg: Color,
921 pub search_match_fg: Color,
922
923 pub diagnostic_error_fg: Color,
925 pub diagnostic_error_bg: Color,
926 pub diagnostic_warning_fg: Color,
927 pub diagnostic_warning_bg: Color,
928 pub diagnostic_info_fg: Color,
929 pub diagnostic_info_bg: Color,
930 pub diagnostic_hint_fg: Color,
931 pub diagnostic_hint_bg: Color,
932
933 pub syntax_keyword: Color,
935 pub syntax_string: Color,
936 pub syntax_comment: Color,
937 pub syntax_function: Color,
938 pub syntax_type: Color,
939 pub syntax_variable: Color,
940 pub syntax_constant: Color,
941 pub syntax_operator: Color,
942}
943
944impl From<ThemeFile> for Theme {
945 fn from(file: ThemeFile) -> Self {
946 Self {
947 name: file.name,
948 editor_bg: file.editor.bg.into(),
949 editor_fg: file.editor.fg.into(),
950 cursor: file.editor.cursor.into(),
951 inactive_cursor: file.editor.inactive_cursor.into(),
952 selection_bg: file.editor.selection_bg.into(),
953 current_line_bg: file.editor.current_line_bg.into(),
954 line_number_fg: file.editor.line_number_fg.into(),
955 line_number_bg: file.editor.line_number_bg.into(),
956 ruler_bg: file.editor.ruler_bg.into(),
957 whitespace_indicator_fg: file.editor.whitespace_indicator_fg.into(),
958 diff_add_bg: file.editor.diff_add_bg.clone().into(),
959 diff_remove_bg: file.editor.diff_remove_bg.clone().into(),
960 diff_modify_bg: file.editor.diff_modify_bg.into(),
961 diff_add_highlight_bg: brighten_color(file.editor.diff_add_bg.into(), 40),
963 diff_remove_highlight_bg: brighten_color(file.editor.diff_remove_bg.into(), 40),
964 tab_active_fg: file.ui.tab_active_fg.into(),
965 tab_active_bg: file.ui.tab_active_bg.into(),
966 tab_inactive_fg: file.ui.tab_inactive_fg.into(),
967 tab_inactive_bg: file.ui.tab_inactive_bg.into(),
968 tab_separator_bg: file.ui.tab_separator_bg.into(),
969 tab_close_hover_fg: file.ui.tab_close_hover_fg.into(),
970 tab_hover_bg: file.ui.tab_hover_bg.into(),
971 menu_bg: file.ui.menu_bg.into(),
972 menu_fg: file.ui.menu_fg.into(),
973 menu_active_bg: file.ui.menu_active_bg.into(),
974 menu_active_fg: file.ui.menu_active_fg.into(),
975 menu_dropdown_bg: file.ui.menu_dropdown_bg.into(),
976 menu_dropdown_fg: file.ui.menu_dropdown_fg.into(),
977 menu_highlight_bg: file.ui.menu_highlight_bg.into(),
978 menu_highlight_fg: file.ui.menu_highlight_fg.into(),
979 menu_border_fg: file.ui.menu_border_fg.into(),
980 menu_separator_fg: file.ui.menu_separator_fg.into(),
981 menu_hover_bg: file.ui.menu_hover_bg.into(),
982 menu_hover_fg: file.ui.menu_hover_fg.into(),
983 menu_disabled_fg: file.ui.menu_disabled_fg.into(),
984 menu_disabled_bg: file.ui.menu_disabled_bg.into(),
985 status_bar_fg: file.ui.status_bar_fg.into(),
986 status_bar_bg: file.ui.status_bar_bg.into(),
987 prompt_fg: file.ui.prompt_fg.into(),
988 prompt_bg: file.ui.prompt_bg.into(),
989 prompt_selection_fg: file.ui.prompt_selection_fg.into(),
990 prompt_selection_bg: file.ui.prompt_selection_bg.into(),
991 popup_border_fg: file.ui.popup_border_fg.into(),
992 popup_bg: file.ui.popup_bg.into(),
993 popup_selection_bg: file.ui.popup_selection_bg.into(),
994 popup_selection_fg: file.ui.popup_selection_fg.into(),
995 popup_text_fg: file.ui.popup_text_fg.into(),
996 suggestion_bg: file.ui.suggestion_bg.into(),
997 suggestion_selected_bg: file.ui.suggestion_selected_bg.into(),
998 help_bg: file.ui.help_bg.into(),
999 help_fg: file.ui.help_fg.into(),
1000 help_key_fg: file.ui.help_key_fg.into(),
1001 help_separator_fg: file.ui.help_separator_fg.into(),
1002 help_indicator_fg: file.ui.help_indicator_fg.into(),
1003 help_indicator_bg: file.ui.help_indicator_bg.into(),
1004 inline_code_bg: file.ui.inline_code_bg.into(),
1005 split_separator_fg: file.ui.split_separator_fg.into(),
1006 split_separator_hover_fg: file.ui.split_separator_hover_fg.into(),
1007 scrollbar_track_fg: file.ui.scrollbar_track_fg.into(),
1008 scrollbar_thumb_fg: file.ui.scrollbar_thumb_fg.into(),
1009 scrollbar_track_hover_fg: file.ui.scrollbar_track_hover_fg.into(),
1010 scrollbar_thumb_hover_fg: file.ui.scrollbar_thumb_hover_fg.into(),
1011 compose_margin_bg: file.ui.compose_margin_bg.into(),
1012 semantic_highlight_bg: file.ui.semantic_highlight_bg.into(),
1013 terminal_bg: file.ui.terminal_bg.into(),
1014 terminal_fg: file.ui.terminal_fg.into(),
1015 status_warning_indicator_bg: file.ui.status_warning_indicator_bg.into(),
1016 status_warning_indicator_fg: file.ui.status_warning_indicator_fg.into(),
1017 status_error_indicator_bg: file.ui.status_error_indicator_bg.into(),
1018 status_error_indicator_fg: file.ui.status_error_indicator_fg.into(),
1019 status_warning_indicator_hover_bg: file.ui.status_warning_indicator_hover_bg.into(),
1020 status_warning_indicator_hover_fg: file.ui.status_warning_indicator_hover_fg.into(),
1021 status_error_indicator_hover_bg: file.ui.status_error_indicator_hover_bg.into(),
1022 status_error_indicator_hover_fg: file.ui.status_error_indicator_hover_fg.into(),
1023 tab_drop_zone_bg: file.ui.tab_drop_zone_bg.into(),
1024 tab_drop_zone_border: file.ui.tab_drop_zone_border.into(),
1025 settings_selected_bg: file.ui.settings_selected_bg.into(),
1026 settings_selected_fg: file.ui.settings_selected_fg.into(),
1027 search_match_bg: file.search.match_bg.into(),
1028 search_match_fg: file.search.match_fg.into(),
1029 diagnostic_error_fg: file.diagnostic.error_fg.into(),
1030 diagnostic_error_bg: file.diagnostic.error_bg.into(),
1031 diagnostic_warning_fg: file.diagnostic.warning_fg.into(),
1032 diagnostic_warning_bg: file.diagnostic.warning_bg.into(),
1033 diagnostic_info_fg: file.diagnostic.info_fg.into(),
1034 diagnostic_info_bg: file.diagnostic.info_bg.into(),
1035 diagnostic_hint_fg: file.diagnostic.hint_fg.into(),
1036 diagnostic_hint_bg: file.diagnostic.hint_bg.into(),
1037 syntax_keyword: file.syntax.keyword.into(),
1038 syntax_string: file.syntax.string.into(),
1039 syntax_comment: file.syntax.comment.into(),
1040 syntax_function: file.syntax.function.into(),
1041 syntax_type: file.syntax.type_.into(),
1042 syntax_variable: file.syntax.variable.into(),
1043 syntax_constant: file.syntax.constant.into(),
1044 syntax_operator: file.syntax.operator.into(),
1045 }
1046 }
1047}
1048
1049impl From<Theme> for ThemeFile {
1050 fn from(theme: Theme) -> Self {
1051 Self {
1052 name: theme.name,
1053 editor: EditorColors {
1054 bg: theme.editor_bg.into(),
1055 fg: theme.editor_fg.into(),
1056 cursor: theme.cursor.into(),
1057 inactive_cursor: theme.inactive_cursor.into(),
1058 selection_bg: theme.selection_bg.into(),
1059 current_line_bg: theme.current_line_bg.into(),
1060 line_number_fg: theme.line_number_fg.into(),
1061 line_number_bg: theme.line_number_bg.into(),
1062 diff_add_bg: theme.diff_add_bg.into(),
1063 diff_remove_bg: theme.diff_remove_bg.into(),
1064 diff_modify_bg: theme.diff_modify_bg.into(),
1065 ruler_bg: theme.ruler_bg.into(),
1066 whitespace_indicator_fg: theme.whitespace_indicator_fg.into(),
1067 },
1068 ui: UiColors {
1069 tab_active_fg: theme.tab_active_fg.into(),
1070 tab_active_bg: theme.tab_active_bg.into(),
1071 tab_inactive_fg: theme.tab_inactive_fg.into(),
1072 tab_inactive_bg: theme.tab_inactive_bg.into(),
1073 tab_separator_bg: theme.tab_separator_bg.into(),
1074 tab_close_hover_fg: theme.tab_close_hover_fg.into(),
1075 tab_hover_bg: theme.tab_hover_bg.into(),
1076 menu_bg: theme.menu_bg.into(),
1077 menu_fg: theme.menu_fg.into(),
1078 menu_active_bg: theme.menu_active_bg.into(),
1079 menu_active_fg: theme.menu_active_fg.into(),
1080 menu_dropdown_bg: theme.menu_dropdown_bg.into(),
1081 menu_dropdown_fg: theme.menu_dropdown_fg.into(),
1082 menu_highlight_bg: theme.menu_highlight_bg.into(),
1083 menu_highlight_fg: theme.menu_highlight_fg.into(),
1084 menu_border_fg: theme.menu_border_fg.into(),
1085 menu_separator_fg: theme.menu_separator_fg.into(),
1086 menu_hover_bg: theme.menu_hover_bg.into(),
1087 menu_hover_fg: theme.menu_hover_fg.into(),
1088 menu_disabled_fg: theme.menu_disabled_fg.into(),
1089 menu_disabled_bg: theme.menu_disabled_bg.into(),
1090 status_bar_fg: theme.status_bar_fg.into(),
1091 status_bar_bg: theme.status_bar_bg.into(),
1092 prompt_fg: theme.prompt_fg.into(),
1093 prompt_bg: theme.prompt_bg.into(),
1094 prompt_selection_fg: theme.prompt_selection_fg.into(),
1095 prompt_selection_bg: theme.prompt_selection_bg.into(),
1096 popup_border_fg: theme.popup_border_fg.into(),
1097 popup_bg: theme.popup_bg.into(),
1098 popup_selection_bg: theme.popup_selection_bg.into(),
1099 popup_selection_fg: theme.popup_selection_fg.into(),
1100 popup_text_fg: theme.popup_text_fg.into(),
1101 suggestion_bg: theme.suggestion_bg.into(),
1102 suggestion_selected_bg: theme.suggestion_selected_bg.into(),
1103 help_bg: theme.help_bg.into(),
1104 help_fg: theme.help_fg.into(),
1105 help_key_fg: theme.help_key_fg.into(),
1106 help_separator_fg: theme.help_separator_fg.into(),
1107 help_indicator_fg: theme.help_indicator_fg.into(),
1108 help_indicator_bg: theme.help_indicator_bg.into(),
1109 inline_code_bg: theme.inline_code_bg.into(),
1110 split_separator_fg: theme.split_separator_fg.into(),
1111 split_separator_hover_fg: theme.split_separator_hover_fg.into(),
1112 scrollbar_track_fg: theme.scrollbar_track_fg.into(),
1113 scrollbar_thumb_fg: theme.scrollbar_thumb_fg.into(),
1114 scrollbar_track_hover_fg: theme.scrollbar_track_hover_fg.into(),
1115 scrollbar_thumb_hover_fg: theme.scrollbar_thumb_hover_fg.into(),
1116 compose_margin_bg: theme.compose_margin_bg.into(),
1117 semantic_highlight_bg: theme.semantic_highlight_bg.into(),
1118 terminal_bg: theme.terminal_bg.into(),
1119 terminal_fg: theme.terminal_fg.into(),
1120 status_warning_indicator_bg: theme.status_warning_indicator_bg.into(),
1121 status_warning_indicator_fg: theme.status_warning_indicator_fg.into(),
1122 status_error_indicator_bg: theme.status_error_indicator_bg.into(),
1123 status_error_indicator_fg: theme.status_error_indicator_fg.into(),
1124 status_warning_indicator_hover_bg: theme.status_warning_indicator_hover_bg.into(),
1125 status_warning_indicator_hover_fg: theme.status_warning_indicator_hover_fg.into(),
1126 status_error_indicator_hover_bg: theme.status_error_indicator_hover_bg.into(),
1127 status_error_indicator_hover_fg: theme.status_error_indicator_hover_fg.into(),
1128 tab_drop_zone_bg: theme.tab_drop_zone_bg.into(),
1129 tab_drop_zone_border: theme.tab_drop_zone_border.into(),
1130 settings_selected_bg: theme.settings_selected_bg.into(),
1131 settings_selected_fg: theme.settings_selected_fg.into(),
1132 },
1133 search: SearchColors {
1134 match_bg: theme.search_match_bg.into(),
1135 match_fg: theme.search_match_fg.into(),
1136 },
1137 diagnostic: DiagnosticColors {
1138 error_fg: theme.diagnostic_error_fg.into(),
1139 error_bg: theme.diagnostic_error_bg.into(),
1140 warning_fg: theme.diagnostic_warning_fg.into(),
1141 warning_bg: theme.diagnostic_warning_bg.into(),
1142 info_fg: theme.diagnostic_info_fg.into(),
1143 info_bg: theme.diagnostic_info_bg.into(),
1144 hint_fg: theme.diagnostic_hint_fg.into(),
1145 hint_bg: theme.diagnostic_hint_bg.into(),
1146 },
1147 syntax: SyntaxColors {
1148 keyword: theme.syntax_keyword.into(),
1149 string: theme.syntax_string.into(),
1150 comment: theme.syntax_comment.into(),
1151 function: theme.syntax_function.into(),
1152 type_: theme.syntax_type.into(),
1153 variable: theme.syntax_variable.into(),
1154 constant: theme.syntax_constant.into(),
1155 operator: theme.syntax_operator.into(),
1156 },
1157 }
1158 }
1159}
1160
1161impl Theme {
1162 pub fn load_builtin(name: &str) -> Option<Self> {
1164 BUILTIN_THEMES
1165 .iter()
1166 .find(|t| t.name == name)
1167 .and_then(|t| serde_json::from_str::<ThemeFile>(t.json).ok())
1168 .map(|tf| tf.into())
1169 }
1170
1171 pub fn from_json(json: &str) -> Result<Self, String> {
1173 let theme_file: ThemeFile =
1174 serde_json::from_str(json).map_err(|e| format!("Failed to parse theme JSON: {}", e))?;
1175 Ok(theme_file.into())
1176 }
1177
1178 pub fn resolve_theme_key(&self, key: &str) -> Option<Color> {
1189 let parts: Vec<&str> = key.split('.').collect();
1191 if parts.len() != 2 {
1192 return None;
1193 }
1194
1195 let (section, field) = (parts[0], parts[1]);
1196
1197 match section {
1198 "editor" => match field {
1199 "bg" => Some(self.editor_bg),
1200 "fg" => Some(self.editor_fg),
1201 "cursor" => Some(self.cursor),
1202 "inactive_cursor" => Some(self.inactive_cursor),
1203 "selection_bg" => Some(self.selection_bg),
1204 "current_line_bg" => Some(self.current_line_bg),
1205 "line_number_fg" => Some(self.line_number_fg),
1206 "line_number_bg" => Some(self.line_number_bg),
1207 "diff_add_bg" => Some(self.diff_add_bg),
1208 "diff_remove_bg" => Some(self.diff_remove_bg),
1209 "diff_modify_bg" => Some(self.diff_modify_bg),
1210 "ruler_bg" => Some(self.ruler_bg),
1211 "whitespace_indicator_fg" => Some(self.whitespace_indicator_fg),
1212 _ => None,
1213 },
1214 "ui" => match field {
1215 "tab_active_fg" => Some(self.tab_active_fg),
1216 "tab_active_bg" => Some(self.tab_active_bg),
1217 "tab_inactive_fg" => Some(self.tab_inactive_fg),
1218 "tab_inactive_bg" => Some(self.tab_inactive_bg),
1219 "status_bar_fg" => Some(self.status_bar_fg),
1220 "status_bar_bg" => Some(self.status_bar_bg),
1221 "prompt_fg" => Some(self.prompt_fg),
1222 "prompt_bg" => Some(self.prompt_bg),
1223 "prompt_selection_fg" => Some(self.prompt_selection_fg),
1224 "prompt_selection_bg" => Some(self.prompt_selection_bg),
1225 "popup_bg" => Some(self.popup_bg),
1226 "popup_border_fg" => Some(self.popup_border_fg),
1227 "popup_selection_bg" => Some(self.popup_selection_bg),
1228 "popup_selection_fg" => Some(self.popup_selection_fg),
1229 "popup_text_fg" => Some(self.popup_text_fg),
1230 "menu_bg" => Some(self.menu_bg),
1231 "menu_fg" => Some(self.menu_fg),
1232 "menu_active_bg" => Some(self.menu_active_bg),
1233 "menu_active_fg" => Some(self.menu_active_fg),
1234 "help_bg" => Some(self.help_bg),
1235 "help_fg" => Some(self.help_fg),
1236 "help_key_fg" => Some(self.help_key_fg),
1237 "split_separator_fg" => Some(self.split_separator_fg),
1238 "scrollbar_thumb_fg" => Some(self.scrollbar_thumb_fg),
1239 "semantic_highlight_bg" => Some(self.semantic_highlight_bg),
1240 _ => None,
1241 },
1242 "syntax" => match field {
1243 "keyword" => Some(self.syntax_keyword),
1244 "string" => Some(self.syntax_string),
1245 "comment" => Some(self.syntax_comment),
1246 "function" => Some(self.syntax_function),
1247 "type" => Some(self.syntax_type),
1248 "variable" => Some(self.syntax_variable),
1249 "constant" => Some(self.syntax_constant),
1250 "operator" => Some(self.syntax_operator),
1251 _ => None,
1252 },
1253 "diagnostic" => match field {
1254 "error_fg" => Some(self.diagnostic_error_fg),
1255 "error_bg" => Some(self.diagnostic_error_bg),
1256 "warning_fg" => Some(self.diagnostic_warning_fg),
1257 "warning_bg" => Some(self.diagnostic_warning_bg),
1258 "info_fg" => Some(self.diagnostic_info_fg),
1259 "info_bg" => Some(self.diagnostic_info_bg),
1260 "hint_fg" => Some(self.diagnostic_hint_fg),
1261 "hint_bg" => Some(self.diagnostic_hint_bg),
1262 _ => None,
1263 },
1264 "search" => match field {
1265 "match_bg" => Some(self.search_match_bg),
1266 "match_fg" => Some(self.search_match_fg),
1267 _ => None,
1268 },
1269 _ => None,
1270 }
1271 }
1272}
1273
1274pub fn get_theme_schema() -> serde_json::Value {
1282 use schemars::schema_for;
1283 let schema = schema_for!(ThemeFile);
1284 serde_json::to_value(&schema).unwrap_or_default()
1285}
1286
1287pub fn get_builtin_themes() -> serde_json::Value {
1289 let mut map = serde_json::Map::new();
1290 for theme in BUILTIN_THEMES {
1291 map.insert(
1292 theme.name.to_string(),
1293 serde_json::Value::String(theme.json.to_string()),
1294 );
1295 }
1296 serde_json::Value::Object(map)
1297}
1298
1299#[cfg(test)]
1300mod tests {
1301 use super::*;
1302
1303 #[test]
1304 fn test_load_builtin_theme() {
1305 let dark = Theme::load_builtin(THEME_DARK).expect("Dark theme must exist");
1306 assert_eq!(dark.name, THEME_DARK);
1307
1308 let light = Theme::load_builtin(THEME_LIGHT).expect("Light theme must exist");
1309 assert_eq!(light.name, THEME_LIGHT);
1310
1311 let high_contrast =
1312 Theme::load_builtin(THEME_HIGH_CONTRAST).expect("High contrast theme must exist");
1313 assert_eq!(high_contrast.name, THEME_HIGH_CONTRAST);
1314 }
1315
1316 #[test]
1317 fn test_builtin_themes_match_schema() {
1318 for theme in BUILTIN_THEMES {
1319 let _: ThemeFile = serde_json::from_str(theme.json)
1320 .unwrap_or_else(|_| panic!("Theme '{}' does not match schema", theme.name));
1321 }
1322 }
1323
1324 #[test]
1325 fn test_from_json() {
1326 let json = r#"{"name":"test","editor":{},"ui":{},"search":{},"diagnostic":{},"syntax":{}}"#;
1327 let theme = Theme::from_json(json).expect("Should parse minimal theme");
1328 assert_eq!(theme.name, "test");
1329 }
1330
1331 #[test]
1332 fn test_default_reset_color() {
1333 let color: Color = ColorDef::Named("Default".to_string()).into();
1335 assert_eq!(color, Color::Reset);
1336
1337 let color: Color = ColorDef::Named("Reset".to_string()).into();
1339 assert_eq!(color, Color::Reset);
1340 }
1341}