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 pub key: String,
44}
45
46impl ThemeInfo {
47 pub fn new(name: impl Into<String>, pack: impl Into<String>) -> Self {
50 let name = name.into();
51 let pack = pack.into();
52 let key = if pack.is_empty() {
53 name.clone()
54 } else {
55 format!("{}/{}", pack, name)
56 };
57 Self { name, pack, key }
58 }
59
60 pub fn with_key(
62 name: impl Into<String>,
63 pack: impl Into<String>,
64 key: impl Into<String>,
65 ) -> Self {
66 Self {
67 name: name.into(),
68 pack: pack.into(),
69 key: key.into(),
70 }
71 }
72
73 pub fn display_name(&self) -> String {
75 if self.pack.is_empty() {
76 self.name.clone()
77 } else {
78 format!("{} ({})", self.name, self.pack)
79 }
80 }
81}
82
83pub fn color_to_rgb(color: Color) -> Option<(u8, u8, u8)> {
86 match color {
87 Color::Rgb(r, g, b) => Some((r, g, b)),
88 Color::White => Some((255, 255, 255)),
89 Color::Black => Some((0, 0, 0)),
90 Color::Red => Some((205, 0, 0)),
91 Color::Green => Some((0, 205, 0)),
92 Color::Blue => Some((0, 0, 238)),
93 Color::Yellow => Some((205, 205, 0)),
94 Color::Magenta => Some((205, 0, 205)),
95 Color::Cyan => Some((0, 205, 205)),
96 Color::Gray => Some((229, 229, 229)),
97 Color::DarkGray => Some((127, 127, 127)),
98 Color::LightRed => Some((255, 0, 0)),
99 Color::LightGreen => Some((0, 255, 0)),
100 Color::LightBlue => Some((92, 92, 255)),
101 Color::LightYellow => Some((255, 255, 0)),
102 Color::LightMagenta => Some((255, 0, 255)),
103 Color::LightCyan => Some((0, 255, 255)),
104 Color::Reset | Color::Indexed(_) => None,
105 }
106}
107
108pub fn brighten_color(color: Color, amount: u8) -> Color {
111 if let Some((r, g, b)) = color_to_rgb(color) {
112 Color::Rgb(
113 r.saturating_add(amount),
114 g.saturating_add(amount),
115 b.saturating_add(amount),
116 )
117 } else {
118 color
119 }
120}
121
122#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
124#[serde(untagged)]
125pub enum ColorDef {
126 Rgb(u8, u8, u8),
128 Named(String),
130}
131
132impl From<ColorDef> for Color {
133 fn from(def: ColorDef) -> Self {
134 match def {
135 ColorDef::Rgb(r, g, b) => Color::Rgb(r, g, b),
136 ColorDef::Named(name) => match name.as_str() {
137 "Black" => Color::Black,
138 "Red" => Color::Red,
139 "Green" => Color::Green,
140 "Yellow" => Color::Yellow,
141 "Blue" => Color::Blue,
142 "Magenta" => Color::Magenta,
143 "Cyan" => Color::Cyan,
144 "Gray" => Color::Gray,
145 "DarkGray" => Color::DarkGray,
146 "LightRed" => Color::LightRed,
147 "LightGreen" => Color::LightGreen,
148 "LightYellow" => Color::LightYellow,
149 "LightBlue" => Color::LightBlue,
150 "LightMagenta" => Color::LightMagenta,
151 "LightCyan" => Color::LightCyan,
152 "White" => Color::White,
153 "Default" | "Reset" => Color::Reset,
155 _ => Color::White, },
157 }
158 }
159}
160
161pub fn named_color_from_str(name: &str) -> Option<Color> {
164 match name {
165 "Black" => Some(Color::Black),
166 "Red" => Some(Color::Red),
167 "Green" => Some(Color::Green),
168 "Yellow" => Some(Color::Yellow),
169 "Blue" => Some(Color::Blue),
170 "Magenta" => Some(Color::Magenta),
171 "Cyan" => Some(Color::Cyan),
172 "Gray" => Some(Color::Gray),
173 "DarkGray" => Some(Color::DarkGray),
174 "LightRed" => Some(Color::LightRed),
175 "LightGreen" => Some(Color::LightGreen),
176 "LightYellow" => Some(Color::LightYellow),
177 "LightBlue" => Some(Color::LightBlue),
178 "LightMagenta" => Some(Color::LightMagenta),
179 "LightCyan" => Some(Color::LightCyan),
180 "White" => Some(Color::White),
181 "Default" | "Reset" => Some(Color::Reset),
182 _ => None,
183 }
184}
185
186impl From<Color> for ColorDef {
187 fn from(color: Color) -> Self {
188 match color {
189 Color::Rgb(r, g, b) => ColorDef::Rgb(r, g, b),
190 Color::White => ColorDef::Named("White".to_string()),
191 Color::Black => ColorDef::Named("Black".to_string()),
192 Color::Red => ColorDef::Named("Red".to_string()),
193 Color::Green => ColorDef::Named("Green".to_string()),
194 Color::Blue => ColorDef::Named("Blue".to_string()),
195 Color::Yellow => ColorDef::Named("Yellow".to_string()),
196 Color::Magenta => ColorDef::Named("Magenta".to_string()),
197 Color::Cyan => ColorDef::Named("Cyan".to_string()),
198 Color::Gray => ColorDef::Named("Gray".to_string()),
199 Color::DarkGray => ColorDef::Named("DarkGray".to_string()),
200 Color::LightRed => ColorDef::Named("LightRed".to_string()),
201 Color::LightGreen => ColorDef::Named("LightGreen".to_string()),
202 Color::LightBlue => ColorDef::Named("LightBlue".to_string()),
203 Color::LightYellow => ColorDef::Named("LightYellow".to_string()),
204 Color::LightMagenta => ColorDef::Named("LightMagenta".to_string()),
205 Color::LightCyan => ColorDef::Named("LightCyan".to_string()),
206 Color::Reset => ColorDef::Named("Default".to_string()),
207 Color::Indexed(_) => {
208 if let Some((r, g, b)) = color_to_rgb(color) {
210 ColorDef::Rgb(r, g, b)
211 } else {
212 ColorDef::Named("Default".to_string())
213 }
214 }
215 }
216 }
217}
218
219#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
221pub struct ThemeFile {
222 pub name: String,
224 pub editor: EditorColors,
226 pub ui: UiColors,
228 pub search: SearchColors,
230 pub diagnostic: DiagnosticColors,
232 pub syntax: SyntaxColors,
234}
235
236#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
238pub struct EditorColors {
239 #[serde(default = "default_editor_bg")]
241 pub bg: ColorDef,
242 #[serde(default = "default_editor_fg")]
244 pub fg: ColorDef,
245 #[serde(default = "default_cursor")]
247 pub cursor: ColorDef,
248 #[serde(default = "default_inactive_cursor")]
250 pub inactive_cursor: ColorDef,
251 #[serde(default = "default_selection_bg")]
253 pub selection_bg: ColorDef,
254 #[serde(default = "default_current_line_bg")]
256 pub current_line_bg: ColorDef,
257 #[serde(default = "default_line_number_fg")]
259 pub line_number_fg: ColorDef,
260 #[serde(default = "default_line_number_bg")]
262 pub line_number_bg: ColorDef,
263 #[serde(default = "default_diff_add_bg")]
265 pub diff_add_bg: ColorDef,
266 #[serde(default = "default_diff_remove_bg")]
268 pub diff_remove_bg: ColorDef,
269 #[serde(default)]
272 pub diff_add_highlight_bg: Option<ColorDef>,
273 #[serde(default)]
276 pub diff_remove_highlight_bg: Option<ColorDef>,
277 #[serde(default = "default_diff_modify_bg")]
279 pub diff_modify_bg: ColorDef,
280 #[serde(default = "default_ruler_bg")]
282 pub ruler_bg: ColorDef,
283 #[serde(default = "default_whitespace_indicator_fg")]
285 pub whitespace_indicator_fg: ColorDef,
286}
287
288fn default_editor_bg() -> ColorDef {
290 ColorDef::Rgb(30, 30, 30)
291}
292fn default_editor_fg() -> ColorDef {
293 ColorDef::Rgb(212, 212, 212)
294}
295fn default_cursor() -> ColorDef {
296 ColorDef::Rgb(255, 255, 255)
297}
298fn default_inactive_cursor() -> ColorDef {
299 ColorDef::Named("DarkGray".to_string())
300}
301fn default_selection_bg() -> ColorDef {
302 ColorDef::Rgb(38, 79, 120)
303}
304fn default_current_line_bg() -> ColorDef {
305 ColorDef::Rgb(40, 40, 40)
306}
307fn default_line_number_fg() -> ColorDef {
308 ColorDef::Rgb(100, 100, 100)
309}
310fn default_line_number_bg() -> ColorDef {
311 ColorDef::Rgb(30, 30, 30)
312}
313fn default_diff_add_bg() -> ColorDef {
314 ColorDef::Rgb(35, 60, 35) }
316fn default_diff_remove_bg() -> ColorDef {
317 ColorDef::Rgb(70, 35, 35) }
319fn default_diff_modify_bg() -> ColorDef {
320 ColorDef::Rgb(40, 38, 30) }
322fn default_ruler_bg() -> ColorDef {
323 ColorDef::Rgb(50, 50, 50) }
325fn default_whitespace_indicator_fg() -> ColorDef {
326 ColorDef::Rgb(70, 70, 70) }
328
329#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
331pub struct UiColors {
332 #[serde(default = "default_tab_active_fg")]
334 pub tab_active_fg: ColorDef,
335 #[serde(default = "default_tab_active_bg")]
337 pub tab_active_bg: ColorDef,
338 #[serde(default = "default_tab_inactive_fg")]
340 pub tab_inactive_fg: ColorDef,
341 #[serde(default = "default_tab_inactive_bg")]
343 pub tab_inactive_bg: ColorDef,
344 #[serde(default = "default_tab_separator_bg")]
346 pub tab_separator_bg: ColorDef,
347 #[serde(default = "default_tab_close_hover_fg")]
349 pub tab_close_hover_fg: ColorDef,
350 #[serde(default = "default_tab_hover_bg")]
352 pub tab_hover_bg: ColorDef,
353 #[serde(default = "default_menu_bg")]
355 pub menu_bg: ColorDef,
356 #[serde(default = "default_menu_fg")]
358 pub menu_fg: ColorDef,
359 #[serde(default = "default_menu_active_bg")]
361 pub menu_active_bg: ColorDef,
362 #[serde(default = "default_menu_active_fg")]
364 pub menu_active_fg: ColorDef,
365 #[serde(default = "default_menu_dropdown_bg")]
367 pub menu_dropdown_bg: ColorDef,
368 #[serde(default = "default_menu_dropdown_fg")]
370 pub menu_dropdown_fg: ColorDef,
371 #[serde(default = "default_menu_highlight_bg")]
373 pub menu_highlight_bg: ColorDef,
374 #[serde(default = "default_menu_highlight_fg")]
376 pub menu_highlight_fg: ColorDef,
377 #[serde(default = "default_menu_border_fg")]
379 pub menu_border_fg: ColorDef,
380 #[serde(default = "default_menu_separator_fg")]
382 pub menu_separator_fg: ColorDef,
383 #[serde(default = "default_menu_hover_bg")]
385 pub menu_hover_bg: ColorDef,
386 #[serde(default = "default_menu_hover_fg")]
388 pub menu_hover_fg: ColorDef,
389 #[serde(default = "default_menu_disabled_fg")]
391 pub menu_disabled_fg: ColorDef,
392 #[serde(default = "default_menu_disabled_bg")]
394 pub menu_disabled_bg: ColorDef,
395 #[serde(default = "default_status_bar_fg")]
397 pub status_bar_fg: ColorDef,
398 #[serde(default = "default_status_bar_bg")]
400 pub status_bar_bg: ColorDef,
401 #[serde(default = "default_prompt_fg")]
403 pub prompt_fg: ColorDef,
404 #[serde(default = "default_prompt_bg")]
406 pub prompt_bg: ColorDef,
407 #[serde(default = "default_prompt_selection_fg")]
409 pub prompt_selection_fg: ColorDef,
410 #[serde(default = "default_prompt_selection_bg")]
412 pub prompt_selection_bg: ColorDef,
413 #[serde(default = "default_popup_border_fg")]
415 pub popup_border_fg: ColorDef,
416 #[serde(default = "default_popup_bg")]
418 pub popup_bg: ColorDef,
419 #[serde(default = "default_popup_selection_bg")]
421 pub popup_selection_bg: ColorDef,
422 #[serde(default = "default_popup_selection_fg")]
424 pub popup_selection_fg: ColorDef,
425 #[serde(default = "default_popup_text_fg")]
427 pub popup_text_fg: ColorDef,
428 #[serde(default = "default_suggestion_bg")]
430 pub suggestion_bg: ColorDef,
431 #[serde(default = "default_suggestion_selected_bg")]
433 pub suggestion_selected_bg: ColorDef,
434 #[serde(default = "default_help_bg")]
436 pub help_bg: ColorDef,
437 #[serde(default = "default_help_fg")]
439 pub help_fg: ColorDef,
440 #[serde(default = "default_help_key_fg")]
442 pub help_key_fg: ColorDef,
443 #[serde(default = "default_help_separator_fg")]
445 pub help_separator_fg: ColorDef,
446 #[serde(default = "default_help_indicator_fg")]
448 pub help_indicator_fg: ColorDef,
449 #[serde(default = "default_help_indicator_bg")]
451 pub help_indicator_bg: ColorDef,
452 #[serde(default = "default_inline_code_bg")]
454 pub inline_code_bg: ColorDef,
455 #[serde(default = "default_split_separator_fg")]
457 pub split_separator_fg: ColorDef,
458 #[serde(default = "default_split_separator_hover_fg")]
460 pub split_separator_hover_fg: ColorDef,
461 #[serde(default = "default_scrollbar_track_fg")]
463 pub scrollbar_track_fg: ColorDef,
464 #[serde(default = "default_scrollbar_thumb_fg")]
466 pub scrollbar_thumb_fg: ColorDef,
467 #[serde(default = "default_scrollbar_track_hover_fg")]
469 pub scrollbar_track_hover_fg: ColorDef,
470 #[serde(default = "default_scrollbar_thumb_hover_fg")]
472 pub scrollbar_thumb_hover_fg: ColorDef,
473 #[serde(default = "default_compose_margin_bg")]
475 pub compose_margin_bg: ColorDef,
476 #[serde(default = "default_semantic_highlight_bg")]
478 pub semantic_highlight_bg: ColorDef,
479 #[serde(default = "default_terminal_bg")]
481 pub terminal_bg: ColorDef,
482 #[serde(default = "default_terminal_fg")]
484 pub terminal_fg: ColorDef,
485 #[serde(default = "default_status_warning_indicator_bg")]
487 pub status_warning_indicator_bg: ColorDef,
488 #[serde(default = "default_status_warning_indicator_fg")]
490 pub status_warning_indicator_fg: ColorDef,
491 #[serde(default = "default_status_error_indicator_bg")]
493 pub status_error_indicator_bg: ColorDef,
494 #[serde(default = "default_status_error_indicator_fg")]
496 pub status_error_indicator_fg: ColorDef,
497 #[serde(default = "default_status_warning_indicator_hover_bg")]
499 pub status_warning_indicator_hover_bg: ColorDef,
500 #[serde(default = "default_status_warning_indicator_hover_fg")]
502 pub status_warning_indicator_hover_fg: ColorDef,
503 #[serde(default = "default_status_error_indicator_hover_bg")]
505 pub status_error_indicator_hover_bg: ColorDef,
506 #[serde(default = "default_status_error_indicator_hover_fg")]
508 pub status_error_indicator_hover_fg: ColorDef,
509 #[serde(default = "default_tab_drop_zone_bg")]
511 pub tab_drop_zone_bg: ColorDef,
512 #[serde(default = "default_tab_drop_zone_border")]
514 pub tab_drop_zone_border: ColorDef,
515 #[serde(default = "default_settings_selected_bg")]
517 pub settings_selected_bg: ColorDef,
518 #[serde(default = "default_settings_selected_fg")]
520 pub settings_selected_fg: ColorDef,
521 #[serde(default)]
523 pub file_status_added_fg: Option<ColorDef>,
524 #[serde(default)]
526 pub file_status_modified_fg: Option<ColorDef>,
527 #[serde(default)]
529 pub file_status_deleted_fg: Option<ColorDef>,
530 #[serde(default)]
532 pub file_status_renamed_fg: Option<ColorDef>,
533 #[serde(default)]
535 pub file_status_untracked_fg: Option<ColorDef>,
536 #[serde(default)]
538 pub file_status_conflicted_fg: Option<ColorDef>,
539}
540
541fn default_tab_active_fg() -> ColorDef {
544 ColorDef::Named("Yellow".to_string())
545}
546fn default_tab_active_bg() -> ColorDef {
547 ColorDef::Named("Blue".to_string())
548}
549fn default_tab_inactive_fg() -> ColorDef {
550 ColorDef::Named("White".to_string())
551}
552fn default_tab_inactive_bg() -> ColorDef {
553 ColorDef::Named("DarkGray".to_string())
554}
555fn default_tab_separator_bg() -> ColorDef {
556 ColorDef::Named("Black".to_string())
557}
558fn default_tab_close_hover_fg() -> ColorDef {
559 ColorDef::Rgb(255, 100, 100) }
561fn default_tab_hover_bg() -> ColorDef {
562 ColorDef::Rgb(70, 70, 75) }
564
565fn default_menu_bg() -> ColorDef {
567 ColorDef::Rgb(60, 60, 65)
568}
569fn default_menu_fg() -> ColorDef {
570 ColorDef::Rgb(220, 220, 220)
571}
572fn default_menu_active_bg() -> ColorDef {
573 ColorDef::Rgb(60, 60, 60)
574}
575fn default_menu_active_fg() -> ColorDef {
576 ColorDef::Rgb(255, 255, 255)
577}
578fn default_menu_dropdown_bg() -> ColorDef {
579 ColorDef::Rgb(50, 50, 50)
580}
581fn default_menu_dropdown_fg() -> ColorDef {
582 ColorDef::Rgb(220, 220, 220)
583}
584fn default_menu_highlight_bg() -> ColorDef {
585 ColorDef::Rgb(70, 130, 180)
586}
587fn default_menu_highlight_fg() -> ColorDef {
588 ColorDef::Rgb(255, 255, 255)
589}
590fn default_menu_border_fg() -> ColorDef {
591 ColorDef::Rgb(100, 100, 100)
592}
593fn default_menu_separator_fg() -> ColorDef {
594 ColorDef::Rgb(80, 80, 80)
595}
596fn default_menu_hover_bg() -> ColorDef {
597 ColorDef::Rgb(55, 55, 55)
598}
599fn default_menu_hover_fg() -> ColorDef {
600 ColorDef::Rgb(255, 255, 255)
601}
602fn default_menu_disabled_fg() -> ColorDef {
603 ColorDef::Rgb(100, 100, 100) }
605fn default_menu_disabled_bg() -> ColorDef {
606 ColorDef::Rgb(50, 50, 50) }
608fn default_status_bar_fg() -> ColorDef {
610 ColorDef::Named("White".to_string())
611}
612fn default_status_bar_bg() -> ColorDef {
613 ColorDef::Named("DarkGray".to_string())
614}
615
616fn default_prompt_fg() -> ColorDef {
618 ColorDef::Named("White".to_string())
619}
620fn default_prompt_bg() -> ColorDef {
621 ColorDef::Named("Black".to_string())
622}
623fn default_prompt_selection_fg() -> ColorDef {
624 ColorDef::Named("White".to_string())
625}
626fn default_prompt_selection_bg() -> ColorDef {
627 ColorDef::Rgb(58, 79, 120)
628}
629
630fn default_popup_border_fg() -> ColorDef {
632 ColorDef::Named("Gray".to_string())
633}
634fn default_popup_bg() -> ColorDef {
635 ColorDef::Rgb(30, 30, 30)
636}
637fn default_popup_selection_bg() -> ColorDef {
638 ColorDef::Rgb(58, 79, 120)
639}
640fn default_popup_selection_fg() -> ColorDef {
641 ColorDef::Rgb(255, 255, 255) }
643fn default_popup_text_fg() -> ColorDef {
644 ColorDef::Named("White".to_string())
645}
646
647fn default_suggestion_bg() -> ColorDef {
649 ColorDef::Rgb(30, 30, 30)
650}
651fn default_suggestion_selected_bg() -> ColorDef {
652 ColorDef::Rgb(58, 79, 120)
653}
654
655fn default_help_bg() -> ColorDef {
657 ColorDef::Named("Black".to_string())
658}
659fn default_help_fg() -> ColorDef {
660 ColorDef::Named("White".to_string())
661}
662fn default_help_key_fg() -> ColorDef {
663 ColorDef::Named("Cyan".to_string())
664}
665fn default_help_separator_fg() -> ColorDef {
666 ColorDef::Named("DarkGray".to_string())
667}
668fn default_help_indicator_fg() -> ColorDef {
669 ColorDef::Named("Red".to_string())
670}
671fn default_help_indicator_bg() -> ColorDef {
672 ColorDef::Named("Black".to_string())
673}
674
675fn default_inline_code_bg() -> ColorDef {
676 ColorDef::Named("DarkGray".to_string())
677}
678
679fn default_split_separator_fg() -> ColorDef {
681 ColorDef::Rgb(100, 100, 100)
682}
683fn default_split_separator_hover_fg() -> ColorDef {
684 ColorDef::Rgb(100, 149, 237) }
686fn default_scrollbar_track_fg() -> ColorDef {
687 ColorDef::Named("DarkGray".to_string())
688}
689fn default_scrollbar_thumb_fg() -> ColorDef {
690 ColorDef::Named("Gray".to_string())
691}
692fn default_scrollbar_track_hover_fg() -> ColorDef {
693 ColorDef::Named("Gray".to_string())
694}
695fn default_scrollbar_thumb_hover_fg() -> ColorDef {
696 ColorDef::Named("White".to_string())
697}
698fn default_compose_margin_bg() -> ColorDef {
699 ColorDef::Rgb(18, 18, 18) }
701fn default_semantic_highlight_bg() -> ColorDef {
702 ColorDef::Rgb(60, 60, 80) }
704fn default_terminal_bg() -> ColorDef {
705 ColorDef::Named("Default".to_string()) }
707fn default_terminal_fg() -> ColorDef {
708 ColorDef::Named("Default".to_string()) }
710fn default_status_warning_indicator_bg() -> ColorDef {
711 ColorDef::Rgb(181, 137, 0) }
713fn default_status_warning_indicator_fg() -> ColorDef {
714 ColorDef::Rgb(0, 0, 0) }
716fn default_status_error_indicator_bg() -> ColorDef {
717 ColorDef::Rgb(220, 50, 47) }
719fn default_status_error_indicator_fg() -> ColorDef {
720 ColorDef::Rgb(255, 255, 255) }
722fn default_status_warning_indicator_hover_bg() -> ColorDef {
723 ColorDef::Rgb(211, 167, 30) }
725fn default_status_warning_indicator_hover_fg() -> ColorDef {
726 ColorDef::Rgb(0, 0, 0) }
728fn default_status_error_indicator_hover_bg() -> ColorDef {
729 ColorDef::Rgb(250, 80, 77) }
731fn default_status_error_indicator_hover_fg() -> ColorDef {
732 ColorDef::Rgb(255, 255, 255) }
734fn default_tab_drop_zone_bg() -> ColorDef {
735 ColorDef::Rgb(70, 130, 180) }
737fn default_tab_drop_zone_border() -> ColorDef {
738 ColorDef::Rgb(100, 149, 237) }
740fn default_settings_selected_bg() -> ColorDef {
741 ColorDef::Rgb(60, 60, 70) }
743fn default_settings_selected_fg() -> ColorDef {
744 ColorDef::Rgb(255, 255, 255) }
746#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
748pub struct SearchColors {
749 #[serde(default = "default_search_match_bg")]
751 pub match_bg: ColorDef,
752 #[serde(default = "default_search_match_fg")]
754 pub match_fg: ColorDef,
755}
756
757fn default_search_match_bg() -> ColorDef {
759 ColorDef::Rgb(100, 100, 20)
760}
761fn default_search_match_fg() -> ColorDef {
762 ColorDef::Rgb(255, 255, 255)
763}
764
765#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
767pub struct DiagnosticColors {
768 #[serde(default = "default_diagnostic_error_fg")]
770 pub error_fg: ColorDef,
771 #[serde(default = "default_diagnostic_error_bg")]
773 pub error_bg: ColorDef,
774 #[serde(default = "default_diagnostic_warning_fg")]
776 pub warning_fg: ColorDef,
777 #[serde(default = "default_diagnostic_warning_bg")]
779 pub warning_bg: ColorDef,
780 #[serde(default = "default_diagnostic_info_fg")]
782 pub info_fg: ColorDef,
783 #[serde(default = "default_diagnostic_info_bg")]
785 pub info_bg: ColorDef,
786 #[serde(default = "default_diagnostic_hint_fg")]
788 pub hint_fg: ColorDef,
789 #[serde(default = "default_diagnostic_hint_bg")]
791 pub hint_bg: ColorDef,
792}
793
794fn default_diagnostic_error_fg() -> ColorDef {
796 ColorDef::Named("Red".to_string())
797}
798fn default_diagnostic_error_bg() -> ColorDef {
799 ColorDef::Rgb(60, 20, 20)
800}
801fn default_diagnostic_warning_fg() -> ColorDef {
802 ColorDef::Named("Yellow".to_string())
803}
804fn default_diagnostic_warning_bg() -> ColorDef {
805 ColorDef::Rgb(60, 50, 0)
806}
807fn default_diagnostic_info_fg() -> ColorDef {
808 ColorDef::Named("Blue".to_string())
809}
810fn default_diagnostic_info_bg() -> ColorDef {
811 ColorDef::Rgb(0, 30, 60)
812}
813fn default_diagnostic_hint_fg() -> ColorDef {
814 ColorDef::Named("Gray".to_string())
815}
816fn default_diagnostic_hint_bg() -> ColorDef {
817 ColorDef::Rgb(30, 30, 30)
818}
819
820#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
822pub struct SyntaxColors {
823 #[serde(default = "default_syntax_keyword")]
825 pub keyword: ColorDef,
826 #[serde(default = "default_syntax_string")]
828 pub string: ColorDef,
829 #[serde(default = "default_syntax_comment")]
831 pub comment: ColorDef,
832 #[serde(default = "default_syntax_function")]
834 pub function: ColorDef,
835 #[serde(rename = "type", default = "default_syntax_type")]
837 pub type_: ColorDef,
838 #[serde(default = "default_syntax_variable")]
840 pub variable: ColorDef,
841 #[serde(default = "default_syntax_constant")]
843 pub constant: ColorDef,
844 #[serde(default = "default_syntax_operator")]
846 pub operator: ColorDef,
847 #[serde(default = "default_syntax_punctuation_bracket")]
849 pub punctuation_bracket: ColorDef,
850 #[serde(default = "default_syntax_punctuation_delimiter")]
852 pub punctuation_delimiter: ColorDef,
853}
854
855fn default_syntax_keyword() -> ColorDef {
857 ColorDef::Rgb(86, 156, 214)
858}
859fn default_syntax_string() -> ColorDef {
860 ColorDef::Rgb(206, 145, 120)
861}
862fn default_syntax_comment() -> ColorDef {
863 ColorDef::Rgb(106, 153, 85)
864}
865fn default_syntax_function() -> ColorDef {
866 ColorDef::Rgb(220, 220, 170)
867}
868fn default_syntax_type() -> ColorDef {
869 ColorDef::Rgb(78, 201, 176)
870}
871fn default_syntax_variable() -> ColorDef {
872 ColorDef::Rgb(156, 220, 254)
873}
874fn default_syntax_constant() -> ColorDef {
875 ColorDef::Rgb(79, 193, 255)
876}
877fn default_syntax_operator() -> ColorDef {
878 ColorDef::Rgb(212, 212, 212)
879}
880fn default_syntax_punctuation_bracket() -> ColorDef {
881 ColorDef::Rgb(212, 212, 212) }
883fn default_syntax_punctuation_delimiter() -> ColorDef {
884 ColorDef::Rgb(212, 212, 212) }
886
887#[derive(Debug, Clone)]
889pub struct Theme {
890 pub name: String,
892
893 pub editor_bg: Color,
895 pub editor_fg: Color,
896 pub cursor: Color,
897 pub inactive_cursor: Color,
898 pub selection_bg: Color,
899 pub current_line_bg: Color,
900 pub line_number_fg: Color,
901 pub line_number_bg: Color,
902
903 pub ruler_bg: Color,
905
906 pub whitespace_indicator_fg: Color,
908
909 pub diff_add_bg: Color,
911 pub diff_remove_bg: Color,
912 pub diff_modify_bg: Color,
913 pub diff_add_highlight_bg: Color,
915 pub diff_remove_highlight_bg: Color,
917
918 pub tab_active_fg: Color,
920 pub tab_active_bg: Color,
921 pub tab_inactive_fg: Color,
922 pub tab_inactive_bg: Color,
923 pub tab_separator_bg: Color,
924 pub tab_close_hover_fg: Color,
925 pub tab_hover_bg: Color,
926
927 pub menu_bg: Color,
929 pub menu_fg: Color,
930 pub menu_active_bg: Color,
931 pub menu_active_fg: Color,
932 pub menu_dropdown_bg: Color,
933 pub menu_dropdown_fg: Color,
934 pub menu_highlight_bg: Color,
935 pub menu_highlight_fg: Color,
936 pub menu_border_fg: Color,
937 pub menu_separator_fg: Color,
938 pub menu_hover_bg: Color,
939 pub menu_hover_fg: Color,
940 pub menu_disabled_fg: Color,
941 pub menu_disabled_bg: Color,
942
943 pub status_bar_fg: Color,
944 pub status_bar_bg: Color,
945 pub prompt_fg: Color,
946 pub prompt_bg: Color,
947 pub prompt_selection_fg: Color,
948 pub prompt_selection_bg: Color,
949
950 pub popup_border_fg: Color,
951 pub popup_bg: Color,
952 pub popup_selection_bg: Color,
953 pub popup_selection_fg: Color,
954 pub popup_text_fg: Color,
955
956 pub suggestion_bg: Color,
957 pub suggestion_selected_bg: Color,
958
959 pub help_bg: Color,
960 pub help_fg: Color,
961 pub help_key_fg: Color,
962 pub help_separator_fg: Color,
963
964 pub help_indicator_fg: Color,
965 pub help_indicator_bg: Color,
966
967 pub inline_code_bg: Color,
969
970 pub split_separator_fg: Color,
971 pub split_separator_hover_fg: Color,
972
973 pub scrollbar_track_fg: Color,
975 pub scrollbar_thumb_fg: Color,
976 pub scrollbar_track_hover_fg: Color,
977 pub scrollbar_thumb_hover_fg: Color,
978
979 pub compose_margin_bg: Color,
981
982 pub semantic_highlight_bg: Color,
984
985 pub terminal_bg: Color,
987 pub terminal_fg: Color,
988
989 pub status_warning_indicator_bg: Color,
991 pub status_warning_indicator_fg: Color,
992 pub status_error_indicator_bg: Color,
993 pub status_error_indicator_fg: Color,
994 pub status_warning_indicator_hover_bg: Color,
995 pub status_warning_indicator_hover_fg: Color,
996 pub status_error_indicator_hover_bg: Color,
997 pub status_error_indicator_hover_fg: Color,
998
999 pub tab_drop_zone_bg: Color,
1001 pub tab_drop_zone_border: Color,
1002
1003 pub settings_selected_bg: Color,
1005 pub settings_selected_fg: Color,
1006
1007 pub file_status_added_fg: Color,
1009 pub file_status_modified_fg: Color,
1010 pub file_status_deleted_fg: Color,
1011 pub file_status_renamed_fg: Color,
1012 pub file_status_untracked_fg: Color,
1013 pub file_status_conflicted_fg: Color,
1014
1015 pub search_match_bg: Color,
1017 pub search_match_fg: Color,
1018
1019 pub diagnostic_error_fg: Color,
1021 pub diagnostic_error_bg: Color,
1022 pub diagnostic_warning_fg: Color,
1023 pub diagnostic_warning_bg: Color,
1024 pub diagnostic_info_fg: Color,
1025 pub diagnostic_info_bg: Color,
1026 pub diagnostic_hint_fg: Color,
1027 pub diagnostic_hint_bg: Color,
1028
1029 pub syntax_keyword: Color,
1031 pub syntax_string: Color,
1032 pub syntax_comment: Color,
1033 pub syntax_function: Color,
1034 pub syntax_type: Color,
1035 pub syntax_variable: Color,
1036 pub syntax_constant: Color,
1037 pub syntax_operator: Color,
1038 pub syntax_punctuation_bracket: Color,
1039 pub syntax_punctuation_delimiter: Color,
1040}
1041
1042impl From<ThemeFile> for Theme {
1043 fn from(file: ThemeFile) -> Self {
1044 Self {
1045 name: file.name,
1046 editor_bg: file.editor.bg.into(),
1047 editor_fg: file.editor.fg.into(),
1048 cursor: file.editor.cursor.into(),
1049 inactive_cursor: file.editor.inactive_cursor.into(),
1050 selection_bg: file.editor.selection_bg.into(),
1051 current_line_bg: file.editor.current_line_bg.into(),
1052 line_number_fg: file.editor.line_number_fg.into(),
1053 line_number_bg: file.editor.line_number_bg.into(),
1054 ruler_bg: file.editor.ruler_bg.into(),
1055 whitespace_indicator_fg: file.editor.whitespace_indicator_fg.into(),
1056 diff_add_bg: file.editor.diff_add_bg.clone().into(),
1057 diff_remove_bg: file.editor.diff_remove_bg.clone().into(),
1058 diff_modify_bg: file.editor.diff_modify_bg.into(),
1059 diff_add_highlight_bg: file
1061 .editor
1062 .diff_add_highlight_bg
1063 .map(|c| c.into())
1064 .unwrap_or_else(|| brighten_color(file.editor.diff_add_bg.into(), 40)),
1065 diff_remove_highlight_bg: file
1066 .editor
1067 .diff_remove_highlight_bg
1068 .map(|c| c.into())
1069 .unwrap_or_else(|| brighten_color(file.editor.diff_remove_bg.into(), 40)),
1070 tab_active_fg: file.ui.tab_active_fg.into(),
1071 tab_active_bg: file.ui.tab_active_bg.into(),
1072 tab_inactive_fg: file.ui.tab_inactive_fg.into(),
1073 tab_inactive_bg: file.ui.tab_inactive_bg.into(),
1074 tab_separator_bg: file.ui.tab_separator_bg.into(),
1075 tab_close_hover_fg: file.ui.tab_close_hover_fg.into(),
1076 tab_hover_bg: file.ui.tab_hover_bg.into(),
1077 menu_bg: file.ui.menu_bg.into(),
1078 menu_fg: file.ui.menu_fg.into(),
1079 menu_active_bg: file.ui.menu_active_bg.into(),
1080 menu_active_fg: file.ui.menu_active_fg.into(),
1081 menu_dropdown_bg: file.ui.menu_dropdown_bg.into(),
1082 menu_dropdown_fg: file.ui.menu_dropdown_fg.into(),
1083 menu_highlight_bg: file.ui.menu_highlight_bg.into(),
1084 menu_highlight_fg: file.ui.menu_highlight_fg.into(),
1085 menu_border_fg: file.ui.menu_border_fg.into(),
1086 menu_separator_fg: file.ui.menu_separator_fg.into(),
1087 menu_hover_bg: file.ui.menu_hover_bg.into(),
1088 menu_hover_fg: file.ui.menu_hover_fg.into(),
1089 menu_disabled_fg: file.ui.menu_disabled_fg.into(),
1090 menu_disabled_bg: file.ui.menu_disabled_bg.into(),
1091 status_bar_fg: file.ui.status_bar_fg.into(),
1092 status_bar_bg: file.ui.status_bar_bg.into(),
1093 prompt_fg: file.ui.prompt_fg.into(),
1094 prompt_bg: file.ui.prompt_bg.into(),
1095 prompt_selection_fg: file.ui.prompt_selection_fg.into(),
1096 prompt_selection_bg: file.ui.prompt_selection_bg.into(),
1097 popup_border_fg: file.ui.popup_border_fg.into(),
1098 popup_bg: file.ui.popup_bg.into(),
1099 popup_selection_bg: file.ui.popup_selection_bg.into(),
1100 popup_selection_fg: file.ui.popup_selection_fg.into(),
1101 popup_text_fg: file.ui.popup_text_fg.into(),
1102 suggestion_bg: file.ui.suggestion_bg.into(),
1103 suggestion_selected_bg: file.ui.suggestion_selected_bg.into(),
1104 help_bg: file.ui.help_bg.into(),
1105 help_fg: file.ui.help_fg.into(),
1106 help_key_fg: file.ui.help_key_fg.into(),
1107 help_separator_fg: file.ui.help_separator_fg.into(),
1108 help_indicator_fg: file.ui.help_indicator_fg.into(),
1109 help_indicator_bg: file.ui.help_indicator_bg.into(),
1110 inline_code_bg: file.ui.inline_code_bg.into(),
1111 split_separator_fg: file.ui.split_separator_fg.into(),
1112 split_separator_hover_fg: file.ui.split_separator_hover_fg.into(),
1113 scrollbar_track_fg: file.ui.scrollbar_track_fg.into(),
1114 scrollbar_thumb_fg: file.ui.scrollbar_thumb_fg.into(),
1115 scrollbar_track_hover_fg: file.ui.scrollbar_track_hover_fg.into(),
1116 scrollbar_thumb_hover_fg: file.ui.scrollbar_thumb_hover_fg.into(),
1117 compose_margin_bg: file.ui.compose_margin_bg.into(),
1118 semantic_highlight_bg: file.ui.semantic_highlight_bg.into(),
1119 terminal_bg: file.ui.terminal_bg.into(),
1120 terminal_fg: file.ui.terminal_fg.into(),
1121 status_warning_indicator_bg: file.ui.status_warning_indicator_bg.into(),
1122 status_warning_indicator_fg: file.ui.status_warning_indicator_fg.into(),
1123 status_error_indicator_bg: file.ui.status_error_indicator_bg.into(),
1124 status_error_indicator_fg: file.ui.status_error_indicator_fg.into(),
1125 status_warning_indicator_hover_bg: file.ui.status_warning_indicator_hover_bg.into(),
1126 status_warning_indicator_hover_fg: file.ui.status_warning_indicator_hover_fg.into(),
1127 status_error_indicator_hover_bg: file.ui.status_error_indicator_hover_bg.into(),
1128 status_error_indicator_hover_fg: file.ui.status_error_indicator_hover_fg.into(),
1129 tab_drop_zone_bg: file.ui.tab_drop_zone_bg.into(),
1130 tab_drop_zone_border: file.ui.tab_drop_zone_border.into(),
1131 settings_selected_bg: file.ui.settings_selected_bg.into(),
1132 settings_selected_fg: file.ui.settings_selected_fg.into(),
1133 file_status_added_fg: file
1134 .ui
1135 .file_status_added_fg
1136 .map(|c| c.into())
1137 .unwrap_or_else(|| file.diagnostic.info_fg.clone().into()),
1138 file_status_modified_fg: file
1139 .ui
1140 .file_status_modified_fg
1141 .map(|c| c.into())
1142 .unwrap_or_else(|| file.diagnostic.warning_fg.clone().into()),
1143 file_status_deleted_fg: file
1144 .ui
1145 .file_status_deleted_fg
1146 .map(|c| c.into())
1147 .unwrap_or_else(|| file.diagnostic.error_fg.clone().into()),
1148 file_status_renamed_fg: file
1149 .ui
1150 .file_status_renamed_fg
1151 .map(|c| c.into())
1152 .unwrap_or_else(|| file.diagnostic.info_fg.clone().into()),
1153 file_status_untracked_fg: file
1154 .ui
1155 .file_status_untracked_fg
1156 .map(|c| c.into())
1157 .unwrap_or_else(|| file.diagnostic.hint_fg.clone().into()),
1158 file_status_conflicted_fg: file
1159 .ui
1160 .file_status_conflicted_fg
1161 .map(|c| c.into())
1162 .unwrap_or_else(|| file.diagnostic.error_fg.clone().into()),
1163 search_match_bg: file.search.match_bg.into(),
1164 search_match_fg: file.search.match_fg.into(),
1165 diagnostic_error_fg: file.diagnostic.error_fg.into(),
1166 diagnostic_error_bg: file.diagnostic.error_bg.into(),
1167 diagnostic_warning_fg: file.diagnostic.warning_fg.into(),
1168 diagnostic_warning_bg: file.diagnostic.warning_bg.into(),
1169 diagnostic_info_fg: file.diagnostic.info_fg.into(),
1170 diagnostic_info_bg: file.diagnostic.info_bg.into(),
1171 diagnostic_hint_fg: file.diagnostic.hint_fg.into(),
1172 diagnostic_hint_bg: file.diagnostic.hint_bg.into(),
1173 syntax_keyword: file.syntax.keyword.into(),
1174 syntax_string: file.syntax.string.into(),
1175 syntax_comment: file.syntax.comment.into(),
1176 syntax_function: file.syntax.function.into(),
1177 syntax_type: file.syntax.type_.into(),
1178 syntax_variable: file.syntax.variable.into(),
1179 syntax_constant: file.syntax.constant.into(),
1180 syntax_operator: file.syntax.operator.into(),
1181 syntax_punctuation_bracket: file.syntax.punctuation_bracket.into(),
1182 syntax_punctuation_delimiter: file.syntax.punctuation_delimiter.into(),
1183 }
1184 }
1185}
1186
1187impl From<Theme> for ThemeFile {
1188 fn from(theme: Theme) -> Self {
1189 Self {
1190 name: theme.name,
1191 editor: EditorColors {
1192 bg: theme.editor_bg.into(),
1193 fg: theme.editor_fg.into(),
1194 cursor: theme.cursor.into(),
1195 inactive_cursor: theme.inactive_cursor.into(),
1196 selection_bg: theme.selection_bg.into(),
1197 current_line_bg: theme.current_line_bg.into(),
1198 line_number_fg: theme.line_number_fg.into(),
1199 line_number_bg: theme.line_number_bg.into(),
1200 diff_add_bg: theme.diff_add_bg.into(),
1201 diff_remove_bg: theme.diff_remove_bg.into(),
1202 diff_add_highlight_bg: Some(theme.diff_add_highlight_bg.into()),
1203 diff_remove_highlight_bg: Some(theme.diff_remove_highlight_bg.into()),
1204 diff_modify_bg: theme.diff_modify_bg.into(),
1205 ruler_bg: theme.ruler_bg.into(),
1206 whitespace_indicator_fg: theme.whitespace_indicator_fg.into(),
1207 },
1208 ui: UiColors {
1209 tab_active_fg: theme.tab_active_fg.into(),
1210 tab_active_bg: theme.tab_active_bg.into(),
1211 tab_inactive_fg: theme.tab_inactive_fg.into(),
1212 tab_inactive_bg: theme.tab_inactive_bg.into(),
1213 tab_separator_bg: theme.tab_separator_bg.into(),
1214 tab_close_hover_fg: theme.tab_close_hover_fg.into(),
1215 tab_hover_bg: theme.tab_hover_bg.into(),
1216 menu_bg: theme.menu_bg.into(),
1217 menu_fg: theme.menu_fg.into(),
1218 menu_active_bg: theme.menu_active_bg.into(),
1219 menu_active_fg: theme.menu_active_fg.into(),
1220 menu_dropdown_bg: theme.menu_dropdown_bg.into(),
1221 menu_dropdown_fg: theme.menu_dropdown_fg.into(),
1222 menu_highlight_bg: theme.menu_highlight_bg.into(),
1223 menu_highlight_fg: theme.menu_highlight_fg.into(),
1224 menu_border_fg: theme.menu_border_fg.into(),
1225 menu_separator_fg: theme.menu_separator_fg.into(),
1226 menu_hover_bg: theme.menu_hover_bg.into(),
1227 menu_hover_fg: theme.menu_hover_fg.into(),
1228 menu_disabled_fg: theme.menu_disabled_fg.into(),
1229 menu_disabled_bg: theme.menu_disabled_bg.into(),
1230 status_bar_fg: theme.status_bar_fg.into(),
1231 status_bar_bg: theme.status_bar_bg.into(),
1232 prompt_fg: theme.prompt_fg.into(),
1233 prompt_bg: theme.prompt_bg.into(),
1234 prompt_selection_fg: theme.prompt_selection_fg.into(),
1235 prompt_selection_bg: theme.prompt_selection_bg.into(),
1236 popup_border_fg: theme.popup_border_fg.into(),
1237 popup_bg: theme.popup_bg.into(),
1238 popup_selection_bg: theme.popup_selection_bg.into(),
1239 popup_selection_fg: theme.popup_selection_fg.into(),
1240 popup_text_fg: theme.popup_text_fg.into(),
1241 suggestion_bg: theme.suggestion_bg.into(),
1242 suggestion_selected_bg: theme.suggestion_selected_bg.into(),
1243 help_bg: theme.help_bg.into(),
1244 help_fg: theme.help_fg.into(),
1245 help_key_fg: theme.help_key_fg.into(),
1246 help_separator_fg: theme.help_separator_fg.into(),
1247 help_indicator_fg: theme.help_indicator_fg.into(),
1248 help_indicator_bg: theme.help_indicator_bg.into(),
1249 inline_code_bg: theme.inline_code_bg.into(),
1250 split_separator_fg: theme.split_separator_fg.into(),
1251 split_separator_hover_fg: theme.split_separator_hover_fg.into(),
1252 scrollbar_track_fg: theme.scrollbar_track_fg.into(),
1253 scrollbar_thumb_fg: theme.scrollbar_thumb_fg.into(),
1254 scrollbar_track_hover_fg: theme.scrollbar_track_hover_fg.into(),
1255 scrollbar_thumb_hover_fg: theme.scrollbar_thumb_hover_fg.into(),
1256 compose_margin_bg: theme.compose_margin_bg.into(),
1257 semantic_highlight_bg: theme.semantic_highlight_bg.into(),
1258 terminal_bg: theme.terminal_bg.into(),
1259 terminal_fg: theme.terminal_fg.into(),
1260 status_warning_indicator_bg: theme.status_warning_indicator_bg.into(),
1261 status_warning_indicator_fg: theme.status_warning_indicator_fg.into(),
1262 status_error_indicator_bg: theme.status_error_indicator_bg.into(),
1263 status_error_indicator_fg: theme.status_error_indicator_fg.into(),
1264 status_warning_indicator_hover_bg: theme.status_warning_indicator_hover_bg.into(),
1265 status_warning_indicator_hover_fg: theme.status_warning_indicator_hover_fg.into(),
1266 status_error_indicator_hover_bg: theme.status_error_indicator_hover_bg.into(),
1267 status_error_indicator_hover_fg: theme.status_error_indicator_hover_fg.into(),
1268 tab_drop_zone_bg: theme.tab_drop_zone_bg.into(),
1269 tab_drop_zone_border: theme.tab_drop_zone_border.into(),
1270 settings_selected_bg: theme.settings_selected_bg.into(),
1271 settings_selected_fg: theme.settings_selected_fg.into(),
1272 file_status_added_fg: Some(theme.file_status_added_fg.into()),
1273 file_status_modified_fg: Some(theme.file_status_modified_fg.into()),
1274 file_status_deleted_fg: Some(theme.file_status_deleted_fg.into()),
1275 file_status_renamed_fg: Some(theme.file_status_renamed_fg.into()),
1276 file_status_untracked_fg: Some(theme.file_status_untracked_fg.into()),
1277 file_status_conflicted_fg: Some(theme.file_status_conflicted_fg.into()),
1278 },
1279 search: SearchColors {
1280 match_bg: theme.search_match_bg.into(),
1281 match_fg: theme.search_match_fg.into(),
1282 },
1283 diagnostic: DiagnosticColors {
1284 error_fg: theme.diagnostic_error_fg.into(),
1285 error_bg: theme.diagnostic_error_bg.into(),
1286 warning_fg: theme.diagnostic_warning_fg.into(),
1287 warning_bg: theme.diagnostic_warning_bg.into(),
1288 info_fg: theme.diagnostic_info_fg.into(),
1289 info_bg: theme.diagnostic_info_bg.into(),
1290 hint_fg: theme.diagnostic_hint_fg.into(),
1291 hint_bg: theme.diagnostic_hint_bg.into(),
1292 },
1293 syntax: SyntaxColors {
1294 keyword: theme.syntax_keyword.into(),
1295 string: theme.syntax_string.into(),
1296 comment: theme.syntax_comment.into(),
1297 function: theme.syntax_function.into(),
1298 type_: theme.syntax_type.into(),
1299 variable: theme.syntax_variable.into(),
1300 constant: theme.syntax_constant.into(),
1301 operator: theme.syntax_operator.into(),
1302 punctuation_bracket: theme.syntax_punctuation_bracket.into(),
1303 punctuation_delimiter: theme.syntax_punctuation_delimiter.into(),
1304 },
1305 }
1306 }
1307}
1308
1309impl Theme {
1310 pub fn is_light(&self) -> bool {
1316 if let Some((r, g, b)) = color_to_rgb(self.editor_bg) {
1317 let lum = 0.2126 * (r as f64 / 255.0)
1319 + 0.7152 * (g as f64 / 255.0)
1320 + 0.0722 * (b as f64 / 255.0);
1321 lum > 0.5
1322 } else {
1323 false
1324 }
1325 }
1326
1327 pub fn load_builtin(name: &str) -> Option<Self> {
1329 BUILTIN_THEMES
1330 .iter()
1331 .find(|t| t.name == name)
1332 .and_then(|t| serde_json::from_str::<ThemeFile>(t.json).ok())
1333 .map(|tf| tf.into())
1334 }
1335
1336 pub fn from_json(json: &str) -> Result<Self, String> {
1338 let theme_file: ThemeFile =
1339 serde_json::from_str(json).map_err(|e| format!("Failed to parse theme JSON: {}", e))?;
1340 Ok(theme_file.into())
1341 }
1342
1343 pub fn resolve_theme_key(&self, key: &str) -> Option<Color> {
1354 let parts: Vec<&str> = key.split('.').collect();
1356 if parts.len() != 2 {
1357 return None;
1358 }
1359
1360 let (section, field) = (parts[0], parts[1]);
1361
1362 match section {
1363 "editor" => match field {
1364 "bg" => Some(self.editor_bg),
1365 "fg" => Some(self.editor_fg),
1366 "cursor" => Some(self.cursor),
1367 "inactive_cursor" => Some(self.inactive_cursor),
1368 "selection_bg" => Some(self.selection_bg),
1369 "current_line_bg" => Some(self.current_line_bg),
1370 "line_number_fg" => Some(self.line_number_fg),
1371 "line_number_bg" => Some(self.line_number_bg),
1372 "diff_add_bg" => Some(self.diff_add_bg),
1373 "diff_remove_bg" => Some(self.diff_remove_bg),
1374 "diff_modify_bg" => Some(self.diff_modify_bg),
1375 "ruler_bg" => Some(self.ruler_bg),
1376 "whitespace_indicator_fg" => Some(self.whitespace_indicator_fg),
1377 _ => None,
1378 },
1379 "ui" => match field {
1380 "tab_active_fg" => Some(self.tab_active_fg),
1381 "tab_active_bg" => Some(self.tab_active_bg),
1382 "tab_inactive_fg" => Some(self.tab_inactive_fg),
1383 "tab_inactive_bg" => Some(self.tab_inactive_bg),
1384 "status_bar_fg" => Some(self.status_bar_fg),
1385 "status_bar_bg" => Some(self.status_bar_bg),
1386 "prompt_fg" => Some(self.prompt_fg),
1387 "prompt_bg" => Some(self.prompt_bg),
1388 "prompt_selection_fg" => Some(self.prompt_selection_fg),
1389 "prompt_selection_bg" => Some(self.prompt_selection_bg),
1390 "popup_bg" => Some(self.popup_bg),
1391 "popup_border_fg" => Some(self.popup_border_fg),
1392 "popup_selection_bg" => Some(self.popup_selection_bg),
1393 "popup_selection_fg" => Some(self.popup_selection_fg),
1394 "popup_text_fg" => Some(self.popup_text_fg),
1395 "menu_bg" => Some(self.menu_bg),
1396 "menu_fg" => Some(self.menu_fg),
1397 "menu_active_bg" => Some(self.menu_active_bg),
1398 "menu_active_fg" => Some(self.menu_active_fg),
1399 "help_bg" => Some(self.help_bg),
1400 "help_fg" => Some(self.help_fg),
1401 "help_key_fg" => Some(self.help_key_fg),
1402 "split_separator_fg" => Some(self.split_separator_fg),
1403 "scrollbar_thumb_fg" => Some(self.scrollbar_thumb_fg),
1404 "semantic_highlight_bg" => Some(self.semantic_highlight_bg),
1405 "file_status_added_fg" => Some(self.file_status_added_fg),
1406 "file_status_modified_fg" => Some(self.file_status_modified_fg),
1407 "file_status_deleted_fg" => Some(self.file_status_deleted_fg),
1408 "file_status_renamed_fg" => Some(self.file_status_renamed_fg),
1409 "file_status_untracked_fg" => Some(self.file_status_untracked_fg),
1410 "file_status_conflicted_fg" => Some(self.file_status_conflicted_fg),
1411 _ => None,
1412 },
1413 "syntax" => match field {
1414 "keyword" => Some(self.syntax_keyword),
1415 "string" => Some(self.syntax_string),
1416 "comment" => Some(self.syntax_comment),
1417 "function" => Some(self.syntax_function),
1418 "type" => Some(self.syntax_type),
1419 "variable" => Some(self.syntax_variable),
1420 "constant" => Some(self.syntax_constant),
1421 "operator" => Some(self.syntax_operator),
1422 "punctuation_bracket" => Some(self.syntax_punctuation_bracket),
1423 "punctuation_delimiter" => Some(self.syntax_punctuation_delimiter),
1424 _ => None,
1425 },
1426 "diagnostic" => match field {
1427 "error_fg" => Some(self.diagnostic_error_fg),
1428 "error_bg" => Some(self.diagnostic_error_bg),
1429 "warning_fg" => Some(self.diagnostic_warning_fg),
1430 "warning_bg" => Some(self.diagnostic_warning_bg),
1431 "info_fg" => Some(self.diagnostic_info_fg),
1432 "info_bg" => Some(self.diagnostic_info_bg),
1433 "hint_fg" => Some(self.diagnostic_hint_fg),
1434 "hint_bg" => Some(self.diagnostic_hint_bg),
1435 _ => None,
1436 },
1437 "search" => match field {
1438 "match_bg" => Some(self.search_match_bg),
1439 "match_fg" => Some(self.search_match_fg),
1440 _ => None,
1441 },
1442 _ => None,
1443 }
1444 }
1445}
1446
1447pub fn get_theme_schema() -> serde_json::Value {
1455 use schemars::schema_for;
1456 let schema = schema_for!(ThemeFile);
1457 serde_json::to_value(&schema).unwrap_or_default()
1458}
1459
1460pub fn get_builtin_themes() -> serde_json::Value {
1462 let mut map = serde_json::Map::new();
1463 for theme in BUILTIN_THEMES {
1464 map.insert(
1465 theme.name.to_string(),
1466 serde_json::Value::String(theme.json.to_string()),
1467 );
1468 }
1469 serde_json::Value::Object(map)
1470}
1471
1472#[cfg(test)]
1473mod tests {
1474 use super::*;
1475
1476 #[test]
1477 fn test_load_builtin_theme() {
1478 let dark = Theme::load_builtin(THEME_DARK).expect("Dark theme must exist");
1479 assert_eq!(dark.name, THEME_DARK);
1480
1481 let light = Theme::load_builtin(THEME_LIGHT).expect("Light theme must exist");
1482 assert_eq!(light.name, THEME_LIGHT);
1483
1484 let high_contrast =
1485 Theme::load_builtin(THEME_HIGH_CONTRAST).expect("High contrast theme must exist");
1486 assert_eq!(high_contrast.name, THEME_HIGH_CONTRAST);
1487 }
1488
1489 #[test]
1490 fn test_builtin_themes_match_schema() {
1491 for theme in BUILTIN_THEMES {
1492 let _: ThemeFile = serde_json::from_str(theme.json)
1493 .unwrap_or_else(|_| panic!("Theme '{}' does not match schema", theme.name));
1494 }
1495 }
1496
1497 #[test]
1498 fn test_from_json() {
1499 let json = r#"{"name":"test","editor":{},"ui":{},"search":{},"diagnostic":{},"syntax":{}}"#;
1500 let theme = Theme::from_json(json).expect("Should parse minimal theme");
1501 assert_eq!(theme.name, "test");
1502 }
1503
1504 #[test]
1505 fn test_default_reset_color() {
1506 let color: Color = ColorDef::Named("Default".to_string()).into();
1508 assert_eq!(color, Color::Reset);
1509
1510 let color: Color = ColorDef::Named("Reset".to_string()).into();
1512 assert_eq!(color, Color::Reset);
1513 }
1514
1515 #[test]
1516 fn test_file_status_colors_fall_back_to_diagnostic_colors() {
1517 let json = r#"{
1519 "name": "test-fallback",
1520 "editor": {},
1521 "ui": {},
1522 "search": {},
1523 "diagnostic": {
1524 "error_fg": [220, 50, 47],
1525 "warning_fg": [181, 137, 0],
1526 "info_fg": [38, 139, 210],
1527 "hint_fg": [101, 123, 131]
1528 },
1529 "syntax": {}
1530 }"#;
1531 let theme = Theme::from_json(json).expect("Should parse theme without file_status keys");
1532
1533 assert_eq!(theme.file_status_added_fg, Color::Rgb(38, 139, 210));
1535 assert_eq!(theme.file_status_renamed_fg, Color::Rgb(38, 139, 210));
1536 assert_eq!(theme.file_status_modified_fg, Color::Rgb(181, 137, 0));
1538 assert_eq!(theme.file_status_deleted_fg, Color::Rgb(220, 50, 47));
1540 assert_eq!(theme.file_status_conflicted_fg, Color::Rgb(220, 50, 47));
1541 assert_eq!(theme.file_status_untracked_fg, Color::Rgb(101, 123, 131));
1543 }
1544
1545 #[test]
1546 fn test_file_status_colors_explicit_override() {
1547 let json = r#"{
1549 "name": "test-override",
1550 "editor": {},
1551 "ui": {
1552 "file_status_added_fg": [80, 250, 123],
1553 "file_status_modified_fg": [255, 184, 108]
1554 },
1555 "search": {},
1556 "diagnostic": {
1557 "info_fg": [38, 139, 210],
1558 "warning_fg": [181, 137, 0]
1559 },
1560 "syntax": {}
1561 }"#;
1562 let theme = Theme::from_json(json).expect("Should parse theme with file_status overrides");
1563
1564 assert_eq!(theme.file_status_added_fg, Color::Rgb(80, 250, 123));
1566 assert_eq!(theme.file_status_modified_fg, Color::Rgb(255, 184, 108));
1567 assert_eq!(theme.file_status_renamed_fg, Color::Rgb(38, 139, 210));
1569 }
1570
1571 #[test]
1572 fn test_file_status_colors_resolve_via_theme_key() {
1573 let json = r#"{
1574 "name": "test-resolve",
1575 "editor": {},
1576 "ui": {
1577 "file_status_added_fg": [80, 250, 123]
1578 },
1579 "search": {},
1580 "diagnostic": {
1581 "warning_fg": [181, 137, 0]
1582 },
1583 "syntax": {}
1584 }"#;
1585 let theme = Theme::from_json(json).expect("Should parse theme");
1586
1587 assert_eq!(
1589 theme.resolve_theme_key("ui.file_status_added_fg"),
1590 Some(Color::Rgb(80, 250, 123))
1591 );
1592 assert_eq!(
1593 theme.resolve_theme_key("ui.file_status_modified_fg"),
1594 Some(Color::Rgb(181, 137, 0))
1595 );
1596 }
1597
1598 #[test]
1599 fn test_all_builtin_themes_have_file_status_colors() {
1600 for builtin in BUILTIN_THEMES {
1602 let theme = Theme::from_json(builtin.json)
1603 .unwrap_or_else(|e| panic!("Theme '{}' failed to parse: {}", builtin.name, e));
1604
1605 for key in &[
1607 "ui.file_status_added_fg",
1608 "ui.file_status_modified_fg",
1609 "ui.file_status_deleted_fg",
1610 "ui.file_status_renamed_fg",
1611 "ui.file_status_untracked_fg",
1612 "ui.file_status_conflicted_fg",
1613 ] {
1614 assert!(
1615 theme.resolve_theme_key(key).is_some(),
1616 "Theme '{}' missing resolution for '{}'",
1617 builtin.name,
1618 key
1619 );
1620 }
1621 }
1622 }
1623}