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
135pub fn named_color_from_str(name: &str) -> Option<Color> {
138 match name {
139 "Black" => Some(Color::Black),
140 "Red" => Some(Color::Red),
141 "Green" => Some(Color::Green),
142 "Yellow" => Some(Color::Yellow),
143 "Blue" => Some(Color::Blue),
144 "Magenta" => Some(Color::Magenta),
145 "Cyan" => Some(Color::Cyan),
146 "Gray" => Some(Color::Gray),
147 "DarkGray" => Some(Color::DarkGray),
148 "LightRed" => Some(Color::LightRed),
149 "LightGreen" => Some(Color::LightGreen),
150 "LightYellow" => Some(Color::LightYellow),
151 "LightBlue" => Some(Color::LightBlue),
152 "LightMagenta" => Some(Color::LightMagenta),
153 "LightCyan" => Some(Color::LightCyan),
154 "White" => Some(Color::White),
155 "Default" | "Reset" => Some(Color::Reset),
156 _ => None,
157 }
158}
159
160impl From<Color> for ColorDef {
161 fn from(color: Color) -> Self {
162 match color {
163 Color::Rgb(r, g, b) => ColorDef::Rgb(r, g, b),
164 Color::White => ColorDef::Named("White".to_string()),
165 Color::Black => ColorDef::Named("Black".to_string()),
166 Color::Red => ColorDef::Named("Red".to_string()),
167 Color::Green => ColorDef::Named("Green".to_string()),
168 Color::Blue => ColorDef::Named("Blue".to_string()),
169 Color::Yellow => ColorDef::Named("Yellow".to_string()),
170 Color::Magenta => ColorDef::Named("Magenta".to_string()),
171 Color::Cyan => ColorDef::Named("Cyan".to_string()),
172 Color::Gray => ColorDef::Named("Gray".to_string()),
173 Color::DarkGray => ColorDef::Named("DarkGray".to_string()),
174 Color::LightRed => ColorDef::Named("LightRed".to_string()),
175 Color::LightGreen => ColorDef::Named("LightGreen".to_string()),
176 Color::LightBlue => ColorDef::Named("LightBlue".to_string()),
177 Color::LightYellow => ColorDef::Named("LightYellow".to_string()),
178 Color::LightMagenta => ColorDef::Named("LightMagenta".to_string()),
179 Color::LightCyan => ColorDef::Named("LightCyan".to_string()),
180 Color::Reset => ColorDef::Named("Default".to_string()),
181 Color::Indexed(_) => {
182 if let Some((r, g, b)) = color_to_rgb(color) {
184 ColorDef::Rgb(r, g, b)
185 } else {
186 ColorDef::Named("Default".to_string())
187 }
188 }
189 }
190 }
191}
192
193#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
195pub struct ThemeFile {
196 pub name: String,
198 pub editor: EditorColors,
200 pub ui: UiColors,
202 pub search: SearchColors,
204 pub diagnostic: DiagnosticColors,
206 pub syntax: SyntaxColors,
208}
209
210#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
212pub struct EditorColors {
213 #[serde(default = "default_editor_bg")]
215 pub bg: ColorDef,
216 #[serde(default = "default_editor_fg")]
218 pub fg: ColorDef,
219 #[serde(default = "default_cursor")]
221 pub cursor: ColorDef,
222 #[serde(default = "default_inactive_cursor")]
224 pub inactive_cursor: ColorDef,
225 #[serde(default = "default_selection_bg")]
227 pub selection_bg: ColorDef,
228 #[serde(default = "default_current_line_bg")]
230 pub current_line_bg: ColorDef,
231 #[serde(default = "default_line_number_fg")]
233 pub line_number_fg: ColorDef,
234 #[serde(default = "default_line_number_bg")]
236 pub line_number_bg: ColorDef,
237 #[serde(default = "default_diff_add_bg")]
239 pub diff_add_bg: ColorDef,
240 #[serde(default = "default_diff_remove_bg")]
242 pub diff_remove_bg: ColorDef,
243 #[serde(default = "default_diff_modify_bg")]
245 pub diff_modify_bg: ColorDef,
246 #[serde(default = "default_ruler_bg")]
248 pub ruler_bg: ColorDef,
249 #[serde(default = "default_whitespace_indicator_fg")]
251 pub whitespace_indicator_fg: ColorDef,
252}
253
254fn default_editor_bg() -> ColorDef {
256 ColorDef::Rgb(30, 30, 30)
257}
258fn default_editor_fg() -> ColorDef {
259 ColorDef::Rgb(212, 212, 212)
260}
261fn default_cursor() -> ColorDef {
262 ColorDef::Rgb(255, 255, 255)
263}
264fn default_inactive_cursor() -> ColorDef {
265 ColorDef::Named("DarkGray".to_string())
266}
267fn default_selection_bg() -> ColorDef {
268 ColorDef::Rgb(38, 79, 120)
269}
270fn default_current_line_bg() -> ColorDef {
271 ColorDef::Rgb(40, 40, 40)
272}
273fn default_line_number_fg() -> ColorDef {
274 ColorDef::Rgb(100, 100, 100)
275}
276fn default_line_number_bg() -> ColorDef {
277 ColorDef::Rgb(30, 30, 30)
278}
279fn default_diff_add_bg() -> ColorDef {
280 ColorDef::Rgb(35, 60, 35) }
282fn default_diff_remove_bg() -> ColorDef {
283 ColorDef::Rgb(70, 35, 35) }
285fn default_diff_modify_bg() -> ColorDef {
286 ColorDef::Rgb(40, 38, 30) }
288fn default_ruler_bg() -> ColorDef {
289 ColorDef::Rgb(50, 50, 50) }
291fn default_whitespace_indicator_fg() -> ColorDef {
292 ColorDef::Rgb(70, 70, 70) }
294
295#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
297pub struct UiColors {
298 #[serde(default = "default_tab_active_fg")]
300 pub tab_active_fg: ColorDef,
301 #[serde(default = "default_tab_active_bg")]
303 pub tab_active_bg: ColorDef,
304 #[serde(default = "default_tab_inactive_fg")]
306 pub tab_inactive_fg: ColorDef,
307 #[serde(default = "default_tab_inactive_bg")]
309 pub tab_inactive_bg: ColorDef,
310 #[serde(default = "default_tab_separator_bg")]
312 pub tab_separator_bg: ColorDef,
313 #[serde(default = "default_tab_close_hover_fg")]
315 pub tab_close_hover_fg: ColorDef,
316 #[serde(default = "default_tab_hover_bg")]
318 pub tab_hover_bg: ColorDef,
319 #[serde(default = "default_menu_bg")]
321 pub menu_bg: ColorDef,
322 #[serde(default = "default_menu_fg")]
324 pub menu_fg: ColorDef,
325 #[serde(default = "default_menu_active_bg")]
327 pub menu_active_bg: ColorDef,
328 #[serde(default = "default_menu_active_fg")]
330 pub menu_active_fg: ColorDef,
331 #[serde(default = "default_menu_dropdown_bg")]
333 pub menu_dropdown_bg: ColorDef,
334 #[serde(default = "default_menu_dropdown_fg")]
336 pub menu_dropdown_fg: ColorDef,
337 #[serde(default = "default_menu_highlight_bg")]
339 pub menu_highlight_bg: ColorDef,
340 #[serde(default = "default_menu_highlight_fg")]
342 pub menu_highlight_fg: ColorDef,
343 #[serde(default = "default_menu_border_fg")]
345 pub menu_border_fg: ColorDef,
346 #[serde(default = "default_menu_separator_fg")]
348 pub menu_separator_fg: ColorDef,
349 #[serde(default = "default_menu_hover_bg")]
351 pub menu_hover_bg: ColorDef,
352 #[serde(default = "default_menu_hover_fg")]
354 pub menu_hover_fg: ColorDef,
355 #[serde(default = "default_menu_disabled_fg")]
357 pub menu_disabled_fg: ColorDef,
358 #[serde(default = "default_menu_disabled_bg")]
360 pub menu_disabled_bg: ColorDef,
361 #[serde(default = "default_status_bar_fg")]
363 pub status_bar_fg: ColorDef,
364 #[serde(default = "default_status_bar_bg")]
366 pub status_bar_bg: ColorDef,
367 #[serde(default = "default_prompt_fg")]
369 pub prompt_fg: ColorDef,
370 #[serde(default = "default_prompt_bg")]
372 pub prompt_bg: ColorDef,
373 #[serde(default = "default_prompt_selection_fg")]
375 pub prompt_selection_fg: ColorDef,
376 #[serde(default = "default_prompt_selection_bg")]
378 pub prompt_selection_bg: ColorDef,
379 #[serde(default = "default_popup_border_fg")]
381 pub popup_border_fg: ColorDef,
382 #[serde(default = "default_popup_bg")]
384 pub popup_bg: ColorDef,
385 #[serde(default = "default_popup_selection_bg")]
387 pub popup_selection_bg: ColorDef,
388 #[serde(default = "default_popup_selection_fg")]
390 pub popup_selection_fg: ColorDef,
391 #[serde(default = "default_popup_text_fg")]
393 pub popup_text_fg: ColorDef,
394 #[serde(default = "default_suggestion_bg")]
396 pub suggestion_bg: ColorDef,
397 #[serde(default = "default_suggestion_selected_bg")]
399 pub suggestion_selected_bg: ColorDef,
400 #[serde(default = "default_help_bg")]
402 pub help_bg: ColorDef,
403 #[serde(default = "default_help_fg")]
405 pub help_fg: ColorDef,
406 #[serde(default = "default_help_key_fg")]
408 pub help_key_fg: ColorDef,
409 #[serde(default = "default_help_separator_fg")]
411 pub help_separator_fg: ColorDef,
412 #[serde(default = "default_help_indicator_fg")]
414 pub help_indicator_fg: ColorDef,
415 #[serde(default = "default_help_indicator_bg")]
417 pub help_indicator_bg: ColorDef,
418 #[serde(default = "default_inline_code_bg")]
420 pub inline_code_bg: ColorDef,
421 #[serde(default = "default_split_separator_fg")]
423 pub split_separator_fg: ColorDef,
424 #[serde(default = "default_split_separator_hover_fg")]
426 pub split_separator_hover_fg: ColorDef,
427 #[serde(default = "default_scrollbar_track_fg")]
429 pub scrollbar_track_fg: ColorDef,
430 #[serde(default = "default_scrollbar_thumb_fg")]
432 pub scrollbar_thumb_fg: ColorDef,
433 #[serde(default = "default_scrollbar_track_hover_fg")]
435 pub scrollbar_track_hover_fg: ColorDef,
436 #[serde(default = "default_scrollbar_thumb_hover_fg")]
438 pub scrollbar_thumb_hover_fg: ColorDef,
439 #[serde(default = "default_compose_margin_bg")]
441 pub compose_margin_bg: ColorDef,
442 #[serde(default = "default_semantic_highlight_bg")]
444 pub semantic_highlight_bg: ColorDef,
445 #[serde(default = "default_terminal_bg")]
447 pub terminal_bg: ColorDef,
448 #[serde(default = "default_terminal_fg")]
450 pub terminal_fg: ColorDef,
451 #[serde(default = "default_status_warning_indicator_bg")]
453 pub status_warning_indicator_bg: ColorDef,
454 #[serde(default = "default_status_warning_indicator_fg")]
456 pub status_warning_indicator_fg: ColorDef,
457 #[serde(default = "default_status_error_indicator_bg")]
459 pub status_error_indicator_bg: ColorDef,
460 #[serde(default = "default_status_error_indicator_fg")]
462 pub status_error_indicator_fg: ColorDef,
463 #[serde(default = "default_status_warning_indicator_hover_bg")]
465 pub status_warning_indicator_hover_bg: ColorDef,
466 #[serde(default = "default_status_warning_indicator_hover_fg")]
468 pub status_warning_indicator_hover_fg: ColorDef,
469 #[serde(default = "default_status_error_indicator_hover_bg")]
471 pub status_error_indicator_hover_bg: ColorDef,
472 #[serde(default = "default_status_error_indicator_hover_fg")]
474 pub status_error_indicator_hover_fg: ColorDef,
475 #[serde(default = "default_tab_drop_zone_bg")]
477 pub tab_drop_zone_bg: ColorDef,
478 #[serde(default = "default_tab_drop_zone_border")]
480 pub tab_drop_zone_border: ColorDef,
481 #[serde(default = "default_settings_selected_bg")]
483 pub settings_selected_bg: ColorDef,
484 #[serde(default = "default_settings_selected_fg")]
486 pub settings_selected_fg: ColorDef,
487}
488
489fn default_tab_active_fg() -> ColorDef {
492 ColorDef::Named("Yellow".to_string())
493}
494fn default_tab_active_bg() -> ColorDef {
495 ColorDef::Named("Blue".to_string())
496}
497fn default_tab_inactive_fg() -> ColorDef {
498 ColorDef::Named("White".to_string())
499}
500fn default_tab_inactive_bg() -> ColorDef {
501 ColorDef::Named("DarkGray".to_string())
502}
503fn default_tab_separator_bg() -> ColorDef {
504 ColorDef::Named("Black".to_string())
505}
506fn default_tab_close_hover_fg() -> ColorDef {
507 ColorDef::Rgb(255, 100, 100) }
509fn default_tab_hover_bg() -> ColorDef {
510 ColorDef::Rgb(70, 70, 75) }
512
513fn default_menu_bg() -> ColorDef {
515 ColorDef::Rgb(60, 60, 65)
516}
517fn default_menu_fg() -> ColorDef {
518 ColorDef::Rgb(220, 220, 220)
519}
520fn default_menu_active_bg() -> ColorDef {
521 ColorDef::Rgb(60, 60, 60)
522}
523fn default_menu_active_fg() -> ColorDef {
524 ColorDef::Rgb(255, 255, 255)
525}
526fn default_menu_dropdown_bg() -> ColorDef {
527 ColorDef::Rgb(50, 50, 50)
528}
529fn default_menu_dropdown_fg() -> ColorDef {
530 ColorDef::Rgb(220, 220, 220)
531}
532fn default_menu_highlight_bg() -> ColorDef {
533 ColorDef::Rgb(70, 130, 180)
534}
535fn default_menu_highlight_fg() -> ColorDef {
536 ColorDef::Rgb(255, 255, 255)
537}
538fn default_menu_border_fg() -> ColorDef {
539 ColorDef::Rgb(100, 100, 100)
540}
541fn default_menu_separator_fg() -> ColorDef {
542 ColorDef::Rgb(80, 80, 80)
543}
544fn default_menu_hover_bg() -> ColorDef {
545 ColorDef::Rgb(55, 55, 55)
546}
547fn default_menu_hover_fg() -> ColorDef {
548 ColorDef::Rgb(255, 255, 255)
549}
550fn default_menu_disabled_fg() -> ColorDef {
551 ColorDef::Rgb(100, 100, 100) }
553fn default_menu_disabled_bg() -> ColorDef {
554 ColorDef::Rgb(50, 50, 50) }
556fn default_status_bar_fg() -> ColorDef {
558 ColorDef::Named("White".to_string())
559}
560fn default_status_bar_bg() -> ColorDef {
561 ColorDef::Named("DarkGray".to_string())
562}
563
564fn default_prompt_fg() -> ColorDef {
566 ColorDef::Named("White".to_string())
567}
568fn default_prompt_bg() -> ColorDef {
569 ColorDef::Named("Black".to_string())
570}
571fn default_prompt_selection_fg() -> ColorDef {
572 ColorDef::Named("White".to_string())
573}
574fn default_prompt_selection_bg() -> ColorDef {
575 ColorDef::Rgb(58, 79, 120)
576}
577
578fn default_popup_border_fg() -> ColorDef {
580 ColorDef::Named("Gray".to_string())
581}
582fn default_popup_bg() -> ColorDef {
583 ColorDef::Rgb(30, 30, 30)
584}
585fn default_popup_selection_bg() -> ColorDef {
586 ColorDef::Rgb(58, 79, 120)
587}
588fn default_popup_selection_fg() -> ColorDef {
589 ColorDef::Rgb(255, 255, 255) }
591fn default_popup_text_fg() -> ColorDef {
592 ColorDef::Named("White".to_string())
593}
594
595fn default_suggestion_bg() -> ColorDef {
597 ColorDef::Rgb(30, 30, 30)
598}
599fn default_suggestion_selected_bg() -> ColorDef {
600 ColorDef::Rgb(58, 79, 120)
601}
602
603fn default_help_bg() -> ColorDef {
605 ColorDef::Named("Black".to_string())
606}
607fn default_help_fg() -> ColorDef {
608 ColorDef::Named("White".to_string())
609}
610fn default_help_key_fg() -> ColorDef {
611 ColorDef::Named("Cyan".to_string())
612}
613fn default_help_separator_fg() -> ColorDef {
614 ColorDef::Named("DarkGray".to_string())
615}
616fn default_help_indicator_fg() -> ColorDef {
617 ColorDef::Named("Red".to_string())
618}
619fn default_help_indicator_bg() -> ColorDef {
620 ColorDef::Named("Black".to_string())
621}
622
623fn default_inline_code_bg() -> ColorDef {
624 ColorDef::Named("DarkGray".to_string())
625}
626
627fn default_split_separator_fg() -> ColorDef {
629 ColorDef::Rgb(100, 100, 100)
630}
631fn default_split_separator_hover_fg() -> ColorDef {
632 ColorDef::Rgb(100, 149, 237) }
634fn default_scrollbar_track_fg() -> ColorDef {
635 ColorDef::Named("DarkGray".to_string())
636}
637fn default_scrollbar_thumb_fg() -> ColorDef {
638 ColorDef::Named("Gray".to_string())
639}
640fn default_scrollbar_track_hover_fg() -> ColorDef {
641 ColorDef::Named("Gray".to_string())
642}
643fn default_scrollbar_thumb_hover_fg() -> ColorDef {
644 ColorDef::Named("White".to_string())
645}
646fn default_compose_margin_bg() -> ColorDef {
647 ColorDef::Rgb(18, 18, 18) }
649fn default_semantic_highlight_bg() -> ColorDef {
650 ColorDef::Rgb(60, 60, 80) }
652fn default_terminal_bg() -> ColorDef {
653 ColorDef::Named("Default".to_string()) }
655fn default_terminal_fg() -> ColorDef {
656 ColorDef::Named("Default".to_string()) }
658fn default_status_warning_indicator_bg() -> ColorDef {
659 ColorDef::Rgb(181, 137, 0) }
661fn default_status_warning_indicator_fg() -> ColorDef {
662 ColorDef::Rgb(0, 0, 0) }
664fn default_status_error_indicator_bg() -> ColorDef {
665 ColorDef::Rgb(220, 50, 47) }
667fn default_status_error_indicator_fg() -> ColorDef {
668 ColorDef::Rgb(255, 255, 255) }
670fn default_status_warning_indicator_hover_bg() -> ColorDef {
671 ColorDef::Rgb(211, 167, 30) }
673fn default_status_warning_indicator_hover_fg() -> ColorDef {
674 ColorDef::Rgb(0, 0, 0) }
676fn default_status_error_indicator_hover_bg() -> ColorDef {
677 ColorDef::Rgb(250, 80, 77) }
679fn default_status_error_indicator_hover_fg() -> ColorDef {
680 ColorDef::Rgb(255, 255, 255) }
682fn default_tab_drop_zone_bg() -> ColorDef {
683 ColorDef::Rgb(70, 130, 180) }
685fn default_tab_drop_zone_border() -> ColorDef {
686 ColorDef::Rgb(100, 149, 237) }
688fn default_settings_selected_bg() -> ColorDef {
689 ColorDef::Rgb(60, 60, 70) }
691fn default_settings_selected_fg() -> ColorDef {
692 ColorDef::Rgb(255, 255, 255) }
694
695#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
697pub struct SearchColors {
698 #[serde(default = "default_search_match_bg")]
700 pub match_bg: ColorDef,
701 #[serde(default = "default_search_match_fg")]
703 pub match_fg: ColorDef,
704}
705
706fn default_search_match_bg() -> ColorDef {
708 ColorDef::Rgb(100, 100, 20)
709}
710fn default_search_match_fg() -> ColorDef {
711 ColorDef::Rgb(255, 255, 255)
712}
713
714#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
716pub struct DiagnosticColors {
717 #[serde(default = "default_diagnostic_error_fg")]
719 pub error_fg: ColorDef,
720 #[serde(default = "default_diagnostic_error_bg")]
722 pub error_bg: ColorDef,
723 #[serde(default = "default_diagnostic_warning_fg")]
725 pub warning_fg: ColorDef,
726 #[serde(default = "default_diagnostic_warning_bg")]
728 pub warning_bg: ColorDef,
729 #[serde(default = "default_diagnostic_info_fg")]
731 pub info_fg: ColorDef,
732 #[serde(default = "default_diagnostic_info_bg")]
734 pub info_bg: ColorDef,
735 #[serde(default = "default_diagnostic_hint_fg")]
737 pub hint_fg: ColorDef,
738 #[serde(default = "default_diagnostic_hint_bg")]
740 pub hint_bg: ColorDef,
741}
742
743fn default_diagnostic_error_fg() -> ColorDef {
745 ColorDef::Named("Red".to_string())
746}
747fn default_diagnostic_error_bg() -> ColorDef {
748 ColorDef::Rgb(60, 20, 20)
749}
750fn default_diagnostic_warning_fg() -> ColorDef {
751 ColorDef::Named("Yellow".to_string())
752}
753fn default_diagnostic_warning_bg() -> ColorDef {
754 ColorDef::Rgb(60, 50, 0)
755}
756fn default_diagnostic_info_fg() -> ColorDef {
757 ColorDef::Named("Blue".to_string())
758}
759fn default_diagnostic_info_bg() -> ColorDef {
760 ColorDef::Rgb(0, 30, 60)
761}
762fn default_diagnostic_hint_fg() -> ColorDef {
763 ColorDef::Named("Gray".to_string())
764}
765fn default_diagnostic_hint_bg() -> ColorDef {
766 ColorDef::Rgb(30, 30, 30)
767}
768
769#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
771pub struct SyntaxColors {
772 #[serde(default = "default_syntax_keyword")]
774 pub keyword: ColorDef,
775 #[serde(default = "default_syntax_string")]
777 pub string: ColorDef,
778 #[serde(default = "default_syntax_comment")]
780 pub comment: ColorDef,
781 #[serde(default = "default_syntax_function")]
783 pub function: ColorDef,
784 #[serde(rename = "type", default = "default_syntax_type")]
786 pub type_: ColorDef,
787 #[serde(default = "default_syntax_variable")]
789 pub variable: ColorDef,
790 #[serde(default = "default_syntax_constant")]
792 pub constant: ColorDef,
793 #[serde(default = "default_syntax_operator")]
795 pub operator: ColorDef,
796 #[serde(default = "default_syntax_punctuation_bracket")]
798 pub punctuation_bracket: ColorDef,
799 #[serde(default = "default_syntax_punctuation_delimiter")]
801 pub punctuation_delimiter: ColorDef,
802}
803
804fn default_syntax_keyword() -> ColorDef {
806 ColorDef::Rgb(86, 156, 214)
807}
808fn default_syntax_string() -> ColorDef {
809 ColorDef::Rgb(206, 145, 120)
810}
811fn default_syntax_comment() -> ColorDef {
812 ColorDef::Rgb(106, 153, 85)
813}
814fn default_syntax_function() -> ColorDef {
815 ColorDef::Rgb(220, 220, 170)
816}
817fn default_syntax_type() -> ColorDef {
818 ColorDef::Rgb(78, 201, 176)
819}
820fn default_syntax_variable() -> ColorDef {
821 ColorDef::Rgb(156, 220, 254)
822}
823fn default_syntax_constant() -> ColorDef {
824 ColorDef::Rgb(79, 193, 255)
825}
826fn default_syntax_operator() -> ColorDef {
827 ColorDef::Rgb(212, 212, 212)
828}
829fn default_syntax_punctuation_bracket() -> ColorDef {
830 ColorDef::Rgb(212, 212, 212) }
832fn default_syntax_punctuation_delimiter() -> ColorDef {
833 ColorDef::Rgb(212, 212, 212) }
835
836#[derive(Debug, Clone)]
838pub struct Theme {
839 pub name: String,
841
842 pub editor_bg: Color,
844 pub editor_fg: Color,
845 pub cursor: Color,
846 pub inactive_cursor: Color,
847 pub selection_bg: Color,
848 pub current_line_bg: Color,
849 pub line_number_fg: Color,
850 pub line_number_bg: Color,
851
852 pub ruler_bg: Color,
854
855 pub whitespace_indicator_fg: Color,
857
858 pub diff_add_bg: Color,
860 pub diff_remove_bg: Color,
861 pub diff_modify_bg: Color,
862 pub diff_add_highlight_bg: Color,
864 pub diff_remove_highlight_bg: Color,
866
867 pub tab_active_fg: Color,
869 pub tab_active_bg: Color,
870 pub tab_inactive_fg: Color,
871 pub tab_inactive_bg: Color,
872 pub tab_separator_bg: Color,
873 pub tab_close_hover_fg: Color,
874 pub tab_hover_bg: Color,
875
876 pub menu_bg: Color,
878 pub menu_fg: Color,
879 pub menu_active_bg: Color,
880 pub menu_active_fg: Color,
881 pub menu_dropdown_bg: Color,
882 pub menu_dropdown_fg: Color,
883 pub menu_highlight_bg: Color,
884 pub menu_highlight_fg: Color,
885 pub menu_border_fg: Color,
886 pub menu_separator_fg: Color,
887 pub menu_hover_bg: Color,
888 pub menu_hover_fg: Color,
889 pub menu_disabled_fg: Color,
890 pub menu_disabled_bg: Color,
891
892 pub status_bar_fg: Color,
893 pub status_bar_bg: Color,
894 pub prompt_fg: Color,
895 pub prompt_bg: Color,
896 pub prompt_selection_fg: Color,
897 pub prompt_selection_bg: Color,
898
899 pub popup_border_fg: Color,
900 pub popup_bg: Color,
901 pub popup_selection_bg: Color,
902 pub popup_selection_fg: Color,
903 pub popup_text_fg: Color,
904
905 pub suggestion_bg: Color,
906 pub suggestion_selected_bg: Color,
907
908 pub help_bg: Color,
909 pub help_fg: Color,
910 pub help_key_fg: Color,
911 pub help_separator_fg: Color,
912
913 pub help_indicator_fg: Color,
914 pub help_indicator_bg: Color,
915
916 pub inline_code_bg: Color,
918
919 pub split_separator_fg: Color,
920 pub split_separator_hover_fg: Color,
921
922 pub scrollbar_track_fg: Color,
924 pub scrollbar_thumb_fg: Color,
925 pub scrollbar_track_hover_fg: Color,
926 pub scrollbar_thumb_hover_fg: Color,
927
928 pub compose_margin_bg: Color,
930
931 pub semantic_highlight_bg: Color,
933
934 pub terminal_bg: Color,
936 pub terminal_fg: Color,
937
938 pub status_warning_indicator_bg: Color,
940 pub status_warning_indicator_fg: Color,
941 pub status_error_indicator_bg: Color,
942 pub status_error_indicator_fg: Color,
943 pub status_warning_indicator_hover_bg: Color,
944 pub status_warning_indicator_hover_fg: Color,
945 pub status_error_indicator_hover_bg: Color,
946 pub status_error_indicator_hover_fg: Color,
947
948 pub tab_drop_zone_bg: Color,
950 pub tab_drop_zone_border: Color,
951
952 pub settings_selected_bg: Color,
954 pub settings_selected_fg: Color,
955
956 pub search_match_bg: Color,
958 pub search_match_fg: Color,
959
960 pub diagnostic_error_fg: Color,
962 pub diagnostic_error_bg: Color,
963 pub diagnostic_warning_fg: Color,
964 pub diagnostic_warning_bg: Color,
965 pub diagnostic_info_fg: Color,
966 pub diagnostic_info_bg: Color,
967 pub diagnostic_hint_fg: Color,
968 pub diagnostic_hint_bg: Color,
969
970 pub syntax_keyword: Color,
972 pub syntax_string: Color,
973 pub syntax_comment: Color,
974 pub syntax_function: Color,
975 pub syntax_type: Color,
976 pub syntax_variable: Color,
977 pub syntax_constant: Color,
978 pub syntax_operator: Color,
979 pub syntax_punctuation_bracket: Color,
980 pub syntax_punctuation_delimiter: Color,
981}
982
983impl From<ThemeFile> for Theme {
984 fn from(file: ThemeFile) -> Self {
985 Self {
986 name: file.name,
987 editor_bg: file.editor.bg.into(),
988 editor_fg: file.editor.fg.into(),
989 cursor: file.editor.cursor.into(),
990 inactive_cursor: file.editor.inactive_cursor.into(),
991 selection_bg: file.editor.selection_bg.into(),
992 current_line_bg: file.editor.current_line_bg.into(),
993 line_number_fg: file.editor.line_number_fg.into(),
994 line_number_bg: file.editor.line_number_bg.into(),
995 ruler_bg: file.editor.ruler_bg.into(),
996 whitespace_indicator_fg: file.editor.whitespace_indicator_fg.into(),
997 diff_add_bg: file.editor.diff_add_bg.clone().into(),
998 diff_remove_bg: file.editor.diff_remove_bg.clone().into(),
999 diff_modify_bg: file.editor.diff_modify_bg.into(),
1000 diff_add_highlight_bg: brighten_color(file.editor.diff_add_bg.into(), 40),
1002 diff_remove_highlight_bg: brighten_color(file.editor.diff_remove_bg.into(), 40),
1003 tab_active_fg: file.ui.tab_active_fg.into(),
1004 tab_active_bg: file.ui.tab_active_bg.into(),
1005 tab_inactive_fg: file.ui.tab_inactive_fg.into(),
1006 tab_inactive_bg: file.ui.tab_inactive_bg.into(),
1007 tab_separator_bg: file.ui.tab_separator_bg.into(),
1008 tab_close_hover_fg: file.ui.tab_close_hover_fg.into(),
1009 tab_hover_bg: file.ui.tab_hover_bg.into(),
1010 menu_bg: file.ui.menu_bg.into(),
1011 menu_fg: file.ui.menu_fg.into(),
1012 menu_active_bg: file.ui.menu_active_bg.into(),
1013 menu_active_fg: file.ui.menu_active_fg.into(),
1014 menu_dropdown_bg: file.ui.menu_dropdown_bg.into(),
1015 menu_dropdown_fg: file.ui.menu_dropdown_fg.into(),
1016 menu_highlight_bg: file.ui.menu_highlight_bg.into(),
1017 menu_highlight_fg: file.ui.menu_highlight_fg.into(),
1018 menu_border_fg: file.ui.menu_border_fg.into(),
1019 menu_separator_fg: file.ui.menu_separator_fg.into(),
1020 menu_hover_bg: file.ui.menu_hover_bg.into(),
1021 menu_hover_fg: file.ui.menu_hover_fg.into(),
1022 menu_disabled_fg: file.ui.menu_disabled_fg.into(),
1023 menu_disabled_bg: file.ui.menu_disabled_bg.into(),
1024 status_bar_fg: file.ui.status_bar_fg.into(),
1025 status_bar_bg: file.ui.status_bar_bg.into(),
1026 prompt_fg: file.ui.prompt_fg.into(),
1027 prompt_bg: file.ui.prompt_bg.into(),
1028 prompt_selection_fg: file.ui.prompt_selection_fg.into(),
1029 prompt_selection_bg: file.ui.prompt_selection_bg.into(),
1030 popup_border_fg: file.ui.popup_border_fg.into(),
1031 popup_bg: file.ui.popup_bg.into(),
1032 popup_selection_bg: file.ui.popup_selection_bg.into(),
1033 popup_selection_fg: file.ui.popup_selection_fg.into(),
1034 popup_text_fg: file.ui.popup_text_fg.into(),
1035 suggestion_bg: file.ui.suggestion_bg.into(),
1036 suggestion_selected_bg: file.ui.suggestion_selected_bg.into(),
1037 help_bg: file.ui.help_bg.into(),
1038 help_fg: file.ui.help_fg.into(),
1039 help_key_fg: file.ui.help_key_fg.into(),
1040 help_separator_fg: file.ui.help_separator_fg.into(),
1041 help_indicator_fg: file.ui.help_indicator_fg.into(),
1042 help_indicator_bg: file.ui.help_indicator_bg.into(),
1043 inline_code_bg: file.ui.inline_code_bg.into(),
1044 split_separator_fg: file.ui.split_separator_fg.into(),
1045 split_separator_hover_fg: file.ui.split_separator_hover_fg.into(),
1046 scrollbar_track_fg: file.ui.scrollbar_track_fg.into(),
1047 scrollbar_thumb_fg: file.ui.scrollbar_thumb_fg.into(),
1048 scrollbar_track_hover_fg: file.ui.scrollbar_track_hover_fg.into(),
1049 scrollbar_thumb_hover_fg: file.ui.scrollbar_thumb_hover_fg.into(),
1050 compose_margin_bg: file.ui.compose_margin_bg.into(),
1051 semantic_highlight_bg: file.ui.semantic_highlight_bg.into(),
1052 terminal_bg: file.ui.terminal_bg.into(),
1053 terminal_fg: file.ui.terminal_fg.into(),
1054 status_warning_indicator_bg: file.ui.status_warning_indicator_bg.into(),
1055 status_warning_indicator_fg: file.ui.status_warning_indicator_fg.into(),
1056 status_error_indicator_bg: file.ui.status_error_indicator_bg.into(),
1057 status_error_indicator_fg: file.ui.status_error_indicator_fg.into(),
1058 status_warning_indicator_hover_bg: file.ui.status_warning_indicator_hover_bg.into(),
1059 status_warning_indicator_hover_fg: file.ui.status_warning_indicator_hover_fg.into(),
1060 status_error_indicator_hover_bg: file.ui.status_error_indicator_hover_bg.into(),
1061 status_error_indicator_hover_fg: file.ui.status_error_indicator_hover_fg.into(),
1062 tab_drop_zone_bg: file.ui.tab_drop_zone_bg.into(),
1063 tab_drop_zone_border: file.ui.tab_drop_zone_border.into(),
1064 settings_selected_bg: file.ui.settings_selected_bg.into(),
1065 settings_selected_fg: file.ui.settings_selected_fg.into(),
1066 search_match_bg: file.search.match_bg.into(),
1067 search_match_fg: file.search.match_fg.into(),
1068 diagnostic_error_fg: file.diagnostic.error_fg.into(),
1069 diagnostic_error_bg: file.diagnostic.error_bg.into(),
1070 diagnostic_warning_fg: file.diagnostic.warning_fg.into(),
1071 diagnostic_warning_bg: file.diagnostic.warning_bg.into(),
1072 diagnostic_info_fg: file.diagnostic.info_fg.into(),
1073 diagnostic_info_bg: file.diagnostic.info_bg.into(),
1074 diagnostic_hint_fg: file.diagnostic.hint_fg.into(),
1075 diagnostic_hint_bg: file.diagnostic.hint_bg.into(),
1076 syntax_keyword: file.syntax.keyword.into(),
1077 syntax_string: file.syntax.string.into(),
1078 syntax_comment: file.syntax.comment.into(),
1079 syntax_function: file.syntax.function.into(),
1080 syntax_type: file.syntax.type_.into(),
1081 syntax_variable: file.syntax.variable.into(),
1082 syntax_constant: file.syntax.constant.into(),
1083 syntax_operator: file.syntax.operator.into(),
1084 syntax_punctuation_bracket: file.syntax.punctuation_bracket.into(),
1085 syntax_punctuation_delimiter: file.syntax.punctuation_delimiter.into(),
1086 }
1087 }
1088}
1089
1090impl From<Theme> for ThemeFile {
1091 fn from(theme: Theme) -> Self {
1092 Self {
1093 name: theme.name,
1094 editor: EditorColors {
1095 bg: theme.editor_bg.into(),
1096 fg: theme.editor_fg.into(),
1097 cursor: theme.cursor.into(),
1098 inactive_cursor: theme.inactive_cursor.into(),
1099 selection_bg: theme.selection_bg.into(),
1100 current_line_bg: theme.current_line_bg.into(),
1101 line_number_fg: theme.line_number_fg.into(),
1102 line_number_bg: theme.line_number_bg.into(),
1103 diff_add_bg: theme.diff_add_bg.into(),
1104 diff_remove_bg: theme.diff_remove_bg.into(),
1105 diff_modify_bg: theme.diff_modify_bg.into(),
1106 ruler_bg: theme.ruler_bg.into(),
1107 whitespace_indicator_fg: theme.whitespace_indicator_fg.into(),
1108 },
1109 ui: UiColors {
1110 tab_active_fg: theme.tab_active_fg.into(),
1111 tab_active_bg: theme.tab_active_bg.into(),
1112 tab_inactive_fg: theme.tab_inactive_fg.into(),
1113 tab_inactive_bg: theme.tab_inactive_bg.into(),
1114 tab_separator_bg: theme.tab_separator_bg.into(),
1115 tab_close_hover_fg: theme.tab_close_hover_fg.into(),
1116 tab_hover_bg: theme.tab_hover_bg.into(),
1117 menu_bg: theme.menu_bg.into(),
1118 menu_fg: theme.menu_fg.into(),
1119 menu_active_bg: theme.menu_active_bg.into(),
1120 menu_active_fg: theme.menu_active_fg.into(),
1121 menu_dropdown_bg: theme.menu_dropdown_bg.into(),
1122 menu_dropdown_fg: theme.menu_dropdown_fg.into(),
1123 menu_highlight_bg: theme.menu_highlight_bg.into(),
1124 menu_highlight_fg: theme.menu_highlight_fg.into(),
1125 menu_border_fg: theme.menu_border_fg.into(),
1126 menu_separator_fg: theme.menu_separator_fg.into(),
1127 menu_hover_bg: theme.menu_hover_bg.into(),
1128 menu_hover_fg: theme.menu_hover_fg.into(),
1129 menu_disabled_fg: theme.menu_disabled_fg.into(),
1130 menu_disabled_bg: theme.menu_disabled_bg.into(),
1131 status_bar_fg: theme.status_bar_fg.into(),
1132 status_bar_bg: theme.status_bar_bg.into(),
1133 prompt_fg: theme.prompt_fg.into(),
1134 prompt_bg: theme.prompt_bg.into(),
1135 prompt_selection_fg: theme.prompt_selection_fg.into(),
1136 prompt_selection_bg: theme.prompt_selection_bg.into(),
1137 popup_border_fg: theme.popup_border_fg.into(),
1138 popup_bg: theme.popup_bg.into(),
1139 popup_selection_bg: theme.popup_selection_bg.into(),
1140 popup_selection_fg: theme.popup_selection_fg.into(),
1141 popup_text_fg: theme.popup_text_fg.into(),
1142 suggestion_bg: theme.suggestion_bg.into(),
1143 suggestion_selected_bg: theme.suggestion_selected_bg.into(),
1144 help_bg: theme.help_bg.into(),
1145 help_fg: theme.help_fg.into(),
1146 help_key_fg: theme.help_key_fg.into(),
1147 help_separator_fg: theme.help_separator_fg.into(),
1148 help_indicator_fg: theme.help_indicator_fg.into(),
1149 help_indicator_bg: theme.help_indicator_bg.into(),
1150 inline_code_bg: theme.inline_code_bg.into(),
1151 split_separator_fg: theme.split_separator_fg.into(),
1152 split_separator_hover_fg: theme.split_separator_hover_fg.into(),
1153 scrollbar_track_fg: theme.scrollbar_track_fg.into(),
1154 scrollbar_thumb_fg: theme.scrollbar_thumb_fg.into(),
1155 scrollbar_track_hover_fg: theme.scrollbar_track_hover_fg.into(),
1156 scrollbar_thumb_hover_fg: theme.scrollbar_thumb_hover_fg.into(),
1157 compose_margin_bg: theme.compose_margin_bg.into(),
1158 semantic_highlight_bg: theme.semantic_highlight_bg.into(),
1159 terminal_bg: theme.terminal_bg.into(),
1160 terminal_fg: theme.terminal_fg.into(),
1161 status_warning_indicator_bg: theme.status_warning_indicator_bg.into(),
1162 status_warning_indicator_fg: theme.status_warning_indicator_fg.into(),
1163 status_error_indicator_bg: theme.status_error_indicator_bg.into(),
1164 status_error_indicator_fg: theme.status_error_indicator_fg.into(),
1165 status_warning_indicator_hover_bg: theme.status_warning_indicator_hover_bg.into(),
1166 status_warning_indicator_hover_fg: theme.status_warning_indicator_hover_fg.into(),
1167 status_error_indicator_hover_bg: theme.status_error_indicator_hover_bg.into(),
1168 status_error_indicator_hover_fg: theme.status_error_indicator_hover_fg.into(),
1169 tab_drop_zone_bg: theme.tab_drop_zone_bg.into(),
1170 tab_drop_zone_border: theme.tab_drop_zone_border.into(),
1171 settings_selected_bg: theme.settings_selected_bg.into(),
1172 settings_selected_fg: theme.settings_selected_fg.into(),
1173 },
1174 search: SearchColors {
1175 match_bg: theme.search_match_bg.into(),
1176 match_fg: theme.search_match_fg.into(),
1177 },
1178 diagnostic: DiagnosticColors {
1179 error_fg: theme.diagnostic_error_fg.into(),
1180 error_bg: theme.diagnostic_error_bg.into(),
1181 warning_fg: theme.diagnostic_warning_fg.into(),
1182 warning_bg: theme.diagnostic_warning_bg.into(),
1183 info_fg: theme.diagnostic_info_fg.into(),
1184 info_bg: theme.diagnostic_info_bg.into(),
1185 hint_fg: theme.diagnostic_hint_fg.into(),
1186 hint_bg: theme.diagnostic_hint_bg.into(),
1187 },
1188 syntax: SyntaxColors {
1189 keyword: theme.syntax_keyword.into(),
1190 string: theme.syntax_string.into(),
1191 comment: theme.syntax_comment.into(),
1192 function: theme.syntax_function.into(),
1193 type_: theme.syntax_type.into(),
1194 variable: theme.syntax_variable.into(),
1195 constant: theme.syntax_constant.into(),
1196 operator: theme.syntax_operator.into(),
1197 punctuation_bracket: theme.syntax_punctuation_bracket.into(),
1198 punctuation_delimiter: theme.syntax_punctuation_delimiter.into(),
1199 },
1200 }
1201 }
1202}
1203
1204impl Theme {
1205 pub fn is_light(&self) -> bool {
1211 if let Some((r, g, b)) = color_to_rgb(self.editor_bg) {
1212 let lum = 0.2126 * (r as f64 / 255.0)
1214 + 0.7152 * (g as f64 / 255.0)
1215 + 0.0722 * (b as f64 / 255.0);
1216 lum > 0.5
1217 } else {
1218 false
1219 }
1220 }
1221
1222 pub fn load_builtin(name: &str) -> Option<Self> {
1224 BUILTIN_THEMES
1225 .iter()
1226 .find(|t| t.name == name)
1227 .and_then(|t| serde_json::from_str::<ThemeFile>(t.json).ok())
1228 .map(|tf| tf.into())
1229 }
1230
1231 pub fn from_json(json: &str) -> Result<Self, String> {
1233 let theme_file: ThemeFile =
1234 serde_json::from_str(json).map_err(|e| format!("Failed to parse theme JSON: {}", e))?;
1235 Ok(theme_file.into())
1236 }
1237
1238 pub fn resolve_theme_key(&self, key: &str) -> Option<Color> {
1249 let parts: Vec<&str> = key.split('.').collect();
1251 if parts.len() != 2 {
1252 return None;
1253 }
1254
1255 let (section, field) = (parts[0], parts[1]);
1256
1257 match section {
1258 "editor" => match field {
1259 "bg" => Some(self.editor_bg),
1260 "fg" => Some(self.editor_fg),
1261 "cursor" => Some(self.cursor),
1262 "inactive_cursor" => Some(self.inactive_cursor),
1263 "selection_bg" => Some(self.selection_bg),
1264 "current_line_bg" => Some(self.current_line_bg),
1265 "line_number_fg" => Some(self.line_number_fg),
1266 "line_number_bg" => Some(self.line_number_bg),
1267 "diff_add_bg" => Some(self.diff_add_bg),
1268 "diff_remove_bg" => Some(self.diff_remove_bg),
1269 "diff_modify_bg" => Some(self.diff_modify_bg),
1270 "ruler_bg" => Some(self.ruler_bg),
1271 "whitespace_indicator_fg" => Some(self.whitespace_indicator_fg),
1272 _ => None,
1273 },
1274 "ui" => match field {
1275 "tab_active_fg" => Some(self.tab_active_fg),
1276 "tab_active_bg" => Some(self.tab_active_bg),
1277 "tab_inactive_fg" => Some(self.tab_inactive_fg),
1278 "tab_inactive_bg" => Some(self.tab_inactive_bg),
1279 "status_bar_fg" => Some(self.status_bar_fg),
1280 "status_bar_bg" => Some(self.status_bar_bg),
1281 "prompt_fg" => Some(self.prompt_fg),
1282 "prompt_bg" => Some(self.prompt_bg),
1283 "prompt_selection_fg" => Some(self.prompt_selection_fg),
1284 "prompt_selection_bg" => Some(self.prompt_selection_bg),
1285 "popup_bg" => Some(self.popup_bg),
1286 "popup_border_fg" => Some(self.popup_border_fg),
1287 "popup_selection_bg" => Some(self.popup_selection_bg),
1288 "popup_selection_fg" => Some(self.popup_selection_fg),
1289 "popup_text_fg" => Some(self.popup_text_fg),
1290 "menu_bg" => Some(self.menu_bg),
1291 "menu_fg" => Some(self.menu_fg),
1292 "menu_active_bg" => Some(self.menu_active_bg),
1293 "menu_active_fg" => Some(self.menu_active_fg),
1294 "help_bg" => Some(self.help_bg),
1295 "help_fg" => Some(self.help_fg),
1296 "help_key_fg" => Some(self.help_key_fg),
1297 "split_separator_fg" => Some(self.split_separator_fg),
1298 "scrollbar_thumb_fg" => Some(self.scrollbar_thumb_fg),
1299 "semantic_highlight_bg" => Some(self.semantic_highlight_bg),
1300 _ => None,
1301 },
1302 "syntax" => match field {
1303 "keyword" => Some(self.syntax_keyword),
1304 "string" => Some(self.syntax_string),
1305 "comment" => Some(self.syntax_comment),
1306 "function" => Some(self.syntax_function),
1307 "type" => Some(self.syntax_type),
1308 "variable" => Some(self.syntax_variable),
1309 "constant" => Some(self.syntax_constant),
1310 "operator" => Some(self.syntax_operator),
1311 "punctuation_bracket" => Some(self.syntax_punctuation_bracket),
1312 "punctuation_delimiter" => Some(self.syntax_punctuation_delimiter),
1313 _ => None,
1314 },
1315 "diagnostic" => match field {
1316 "error_fg" => Some(self.diagnostic_error_fg),
1317 "error_bg" => Some(self.diagnostic_error_bg),
1318 "warning_fg" => Some(self.diagnostic_warning_fg),
1319 "warning_bg" => Some(self.diagnostic_warning_bg),
1320 "info_fg" => Some(self.diagnostic_info_fg),
1321 "info_bg" => Some(self.diagnostic_info_bg),
1322 "hint_fg" => Some(self.diagnostic_hint_fg),
1323 "hint_bg" => Some(self.diagnostic_hint_bg),
1324 _ => None,
1325 },
1326 "search" => match field {
1327 "match_bg" => Some(self.search_match_bg),
1328 "match_fg" => Some(self.search_match_fg),
1329 _ => None,
1330 },
1331 _ => None,
1332 }
1333 }
1334}
1335
1336pub fn get_theme_schema() -> serde_json::Value {
1344 use schemars::schema_for;
1345 let schema = schema_for!(ThemeFile);
1346 serde_json::to_value(&schema).unwrap_or_default()
1347}
1348
1349pub fn get_builtin_themes() -> serde_json::Value {
1351 let mut map = serde_json::Map::new();
1352 for theme in BUILTIN_THEMES {
1353 map.insert(
1354 theme.name.to_string(),
1355 serde_json::Value::String(theme.json.to_string()),
1356 );
1357 }
1358 serde_json::Value::Object(map)
1359}
1360
1361#[cfg(test)]
1362mod tests {
1363 use super::*;
1364
1365 #[test]
1366 fn test_load_builtin_theme() {
1367 let dark = Theme::load_builtin(THEME_DARK).expect("Dark theme must exist");
1368 assert_eq!(dark.name, THEME_DARK);
1369
1370 let light = Theme::load_builtin(THEME_LIGHT).expect("Light theme must exist");
1371 assert_eq!(light.name, THEME_LIGHT);
1372
1373 let high_contrast =
1374 Theme::load_builtin(THEME_HIGH_CONTRAST).expect("High contrast theme must exist");
1375 assert_eq!(high_contrast.name, THEME_HIGH_CONTRAST);
1376 }
1377
1378 #[test]
1379 fn test_builtin_themes_match_schema() {
1380 for theme in BUILTIN_THEMES {
1381 let _: ThemeFile = serde_json::from_str(theme.json)
1382 .unwrap_or_else(|_| panic!("Theme '{}' does not match schema", theme.name));
1383 }
1384 }
1385
1386 #[test]
1387 fn test_from_json() {
1388 let json = r#"{"name":"test","editor":{},"ui":{},"search":{},"diagnostic":{},"syntax":{}}"#;
1389 let theme = Theme::from_json(json).expect("Should parse minimal theme");
1390 assert_eq!(theme.name, "test");
1391 }
1392
1393 #[test]
1394 fn test_default_reset_color() {
1395 let color: Color = ColorDef::Named("Default".to_string()).into();
1397 assert_eq!(color, Color::Reset);
1398
1399 let color: Color = ColorDef::Named("Reset".to_string()).into();
1401 assert_eq!(color, Color::Reset);
1402 }
1403}