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
122pub fn shade_toward_contrast(color: Color, amount: u8) -> Color {
130 if let Some((r, g, b)) = color_to_rgb(color) {
131 let avg = (u16::from(r) + u16::from(g) + u16::from(b)) / 3;
132 if avg < 128 {
133 Color::Rgb(
134 r.saturating_add(amount),
135 g.saturating_add(amount),
136 b.saturating_add(amount),
137 )
138 } else {
139 Color::Rgb(
140 r.saturating_sub(amount),
141 g.saturating_sub(amount),
142 b.saturating_sub(amount),
143 )
144 }
145 } else {
146 color
147 }
148}
149
150#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
152#[serde(untagged)]
153pub enum ColorDef {
154 Rgb(u8, u8, u8),
156 Named(String),
158}
159
160impl From<ColorDef> for Color {
161 fn from(def: ColorDef) -> Self {
162 match def {
163 ColorDef::Rgb(r, g, b) => Color::Rgb(r, g, b),
164 ColorDef::Named(name) => match name.as_str() {
165 "Black" => Color::Black,
166 "Red" => Color::Red,
167 "Green" => Color::Green,
168 "Yellow" => Color::Yellow,
169 "Blue" => Color::Blue,
170 "Magenta" => Color::Magenta,
171 "Cyan" => Color::Cyan,
172 "Gray" => Color::Gray,
173 "DarkGray" => Color::DarkGray,
174 "LightRed" => Color::LightRed,
175 "LightGreen" => Color::LightGreen,
176 "LightYellow" => Color::LightYellow,
177 "LightBlue" => Color::LightBlue,
178 "LightMagenta" => Color::LightMagenta,
179 "LightCyan" => Color::LightCyan,
180 "White" => Color::White,
181 "Default" | "Reset" => Color::Reset,
183 _ => Color::White, },
185 }
186 }
187}
188
189pub fn named_color_from_str(name: &str) -> Option<Color> {
192 match name {
193 "Black" => Some(Color::Black),
194 "Red" => Some(Color::Red),
195 "Green" => Some(Color::Green),
196 "Yellow" => Some(Color::Yellow),
197 "Blue" => Some(Color::Blue),
198 "Magenta" => Some(Color::Magenta),
199 "Cyan" => Some(Color::Cyan),
200 "Gray" => Some(Color::Gray),
201 "DarkGray" => Some(Color::DarkGray),
202 "LightRed" => Some(Color::LightRed),
203 "LightGreen" => Some(Color::LightGreen),
204 "LightYellow" => Some(Color::LightYellow),
205 "LightBlue" => Some(Color::LightBlue),
206 "LightMagenta" => Some(Color::LightMagenta),
207 "LightCyan" => Some(Color::LightCyan),
208 "White" => Some(Color::White),
209 "Default" | "Reset" => Some(Color::Reset),
210 _ => None,
211 }
212}
213
214impl From<Color> for ColorDef {
215 fn from(color: Color) -> Self {
216 match color {
217 Color::Rgb(r, g, b) => ColorDef::Rgb(r, g, b),
218 Color::White => ColorDef::Named("White".to_string()),
219 Color::Black => ColorDef::Named("Black".to_string()),
220 Color::Red => ColorDef::Named("Red".to_string()),
221 Color::Green => ColorDef::Named("Green".to_string()),
222 Color::Blue => ColorDef::Named("Blue".to_string()),
223 Color::Yellow => ColorDef::Named("Yellow".to_string()),
224 Color::Magenta => ColorDef::Named("Magenta".to_string()),
225 Color::Cyan => ColorDef::Named("Cyan".to_string()),
226 Color::Gray => ColorDef::Named("Gray".to_string()),
227 Color::DarkGray => ColorDef::Named("DarkGray".to_string()),
228 Color::LightRed => ColorDef::Named("LightRed".to_string()),
229 Color::LightGreen => ColorDef::Named("LightGreen".to_string()),
230 Color::LightBlue => ColorDef::Named("LightBlue".to_string()),
231 Color::LightYellow => ColorDef::Named("LightYellow".to_string()),
232 Color::LightMagenta => ColorDef::Named("LightMagenta".to_string()),
233 Color::LightCyan => ColorDef::Named("LightCyan".to_string()),
234 Color::Reset => ColorDef::Named("Default".to_string()),
235 Color::Indexed(_) => {
236 if let Some((r, g, b)) = color_to_rgb(color) {
238 ColorDef::Rgb(r, g, b)
239 } else {
240 ColorDef::Named("Default".to_string())
241 }
242 }
243 }
244 }
245}
246
247#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
249pub struct ThemeFile {
250 pub name: String,
252 pub editor: EditorColors,
254 pub ui: UiColors,
256 pub search: SearchColors,
258 pub diagnostic: DiagnosticColors,
260 pub syntax: SyntaxColors,
262}
263
264#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
266pub struct EditorColors {
267 #[serde(default = "default_editor_bg")]
269 pub bg: ColorDef,
270 #[serde(default = "default_editor_fg")]
272 pub fg: ColorDef,
273 #[serde(default = "default_cursor")]
275 pub cursor: ColorDef,
276 #[serde(default = "default_inactive_cursor")]
278 pub inactive_cursor: ColorDef,
279 #[serde(default = "default_selection_bg")]
281 pub selection_bg: ColorDef,
282 #[serde(default = "default_current_line_bg")]
284 pub current_line_bg: ColorDef,
285 #[serde(default = "default_line_number_fg")]
287 pub line_number_fg: ColorDef,
288 #[serde(default = "default_line_number_bg")]
290 pub line_number_bg: ColorDef,
291 #[serde(default = "default_diff_add_bg")]
293 pub diff_add_bg: ColorDef,
294 #[serde(default = "default_diff_remove_bg")]
296 pub diff_remove_bg: ColorDef,
297 #[serde(default)]
300 pub diff_add_highlight_bg: Option<ColorDef>,
301 #[serde(default)]
304 pub diff_remove_highlight_bg: Option<ColorDef>,
305 #[serde(default = "default_diff_modify_bg")]
307 pub diff_modify_bg: ColorDef,
308 #[serde(default = "default_ruler_bg")]
310 pub ruler_bg: ColorDef,
311 #[serde(default = "default_whitespace_indicator_fg")]
313 pub whitespace_indicator_fg: ColorDef,
314 #[serde(default)]
319 pub after_eof_bg: Option<ColorDef>,
320}
321
322fn default_editor_bg() -> ColorDef {
324 ColorDef::Rgb(30, 30, 30)
325}
326fn default_editor_fg() -> ColorDef {
327 ColorDef::Rgb(212, 212, 212)
328}
329fn default_cursor() -> ColorDef {
330 ColorDef::Rgb(255, 255, 255)
331}
332fn default_inactive_cursor() -> ColorDef {
333 ColorDef::Named("DarkGray".to_string())
334}
335fn default_selection_bg() -> ColorDef {
336 ColorDef::Rgb(38, 79, 120)
337}
338fn default_current_line_bg() -> ColorDef {
339 ColorDef::Rgb(40, 40, 40)
340}
341fn default_line_number_fg() -> ColorDef {
342 ColorDef::Rgb(100, 100, 100)
343}
344fn default_line_number_bg() -> ColorDef {
345 ColorDef::Rgb(30, 30, 30)
346}
347fn default_diff_add_bg() -> ColorDef {
348 ColorDef::Rgb(35, 60, 35) }
350fn default_diff_remove_bg() -> ColorDef {
351 ColorDef::Rgb(70, 35, 35) }
353fn default_diff_modify_bg() -> ColorDef {
354 ColorDef::Rgb(40, 38, 30) }
356fn default_ruler_bg() -> ColorDef {
357 ColorDef::Rgb(50, 50, 50) }
359fn default_whitespace_indicator_fg() -> ColorDef {
360 ColorDef::Rgb(70, 70, 70) }
362
363#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
365pub struct UiColors {
366 #[serde(default = "default_tab_active_fg")]
368 pub tab_active_fg: ColorDef,
369 #[serde(default = "default_tab_active_bg")]
371 pub tab_active_bg: ColorDef,
372 #[serde(default = "default_tab_inactive_fg")]
374 pub tab_inactive_fg: ColorDef,
375 #[serde(default = "default_tab_inactive_bg")]
377 pub tab_inactive_bg: ColorDef,
378 #[serde(default = "default_tab_separator_bg")]
380 pub tab_separator_bg: ColorDef,
381 #[serde(default = "default_tab_close_hover_fg")]
383 pub tab_close_hover_fg: ColorDef,
384 #[serde(default = "default_tab_hover_bg")]
386 pub tab_hover_bg: ColorDef,
387 #[serde(default = "default_menu_bg")]
389 pub menu_bg: ColorDef,
390 #[serde(default = "default_menu_fg")]
392 pub menu_fg: ColorDef,
393 #[serde(default = "default_menu_active_bg")]
395 pub menu_active_bg: ColorDef,
396 #[serde(default = "default_menu_active_fg")]
398 pub menu_active_fg: ColorDef,
399 #[serde(default = "default_menu_dropdown_bg")]
401 pub menu_dropdown_bg: ColorDef,
402 #[serde(default = "default_menu_dropdown_fg")]
404 pub menu_dropdown_fg: ColorDef,
405 #[serde(default = "default_menu_highlight_bg")]
407 pub menu_highlight_bg: ColorDef,
408 #[serde(default = "default_menu_highlight_fg")]
410 pub menu_highlight_fg: ColorDef,
411 #[serde(default = "default_menu_border_fg")]
413 pub menu_border_fg: ColorDef,
414 #[serde(default = "default_menu_separator_fg")]
416 pub menu_separator_fg: ColorDef,
417 #[serde(default = "default_menu_hover_bg")]
419 pub menu_hover_bg: ColorDef,
420 #[serde(default = "default_menu_hover_fg")]
422 pub menu_hover_fg: ColorDef,
423 #[serde(default = "default_menu_disabled_fg")]
425 pub menu_disabled_fg: ColorDef,
426 #[serde(default = "default_menu_disabled_bg")]
428 pub menu_disabled_bg: ColorDef,
429 #[serde(default = "default_status_bar_fg")]
431 pub status_bar_fg: ColorDef,
432 #[serde(default = "default_status_bar_bg")]
434 pub status_bar_bg: ColorDef,
435 #[serde(default)]
437 pub status_palette_fg: Option<ColorDef>,
438 #[serde(default)]
440 pub status_palette_bg: Option<ColorDef>,
441 #[serde(default)]
443 pub status_lsp_on_fg: Option<ColorDef>,
444 #[serde(default)]
446 pub status_lsp_on_bg: Option<ColorDef>,
447 #[serde(default = "default_prompt_fg")]
449 pub prompt_fg: ColorDef,
450 #[serde(default = "default_prompt_bg")]
452 pub prompt_bg: ColorDef,
453 #[serde(default = "default_prompt_selection_fg")]
455 pub prompt_selection_fg: ColorDef,
456 #[serde(default = "default_prompt_selection_bg")]
458 pub prompt_selection_bg: ColorDef,
459 #[serde(default = "default_popup_border_fg")]
461 pub popup_border_fg: ColorDef,
462 #[serde(default = "default_popup_bg")]
464 pub popup_bg: ColorDef,
465 #[serde(default = "default_popup_selection_bg")]
467 pub popup_selection_bg: ColorDef,
468 #[serde(default = "default_popup_selection_fg")]
470 pub popup_selection_fg: ColorDef,
471 #[serde(default = "default_popup_text_fg")]
473 pub popup_text_fg: ColorDef,
474 #[serde(default = "default_suggestion_bg")]
476 pub suggestion_bg: ColorDef,
477 #[serde(default = "default_suggestion_selected_bg")]
479 pub suggestion_selected_bg: ColorDef,
480 #[serde(default = "default_help_bg")]
482 pub help_bg: ColorDef,
483 #[serde(default = "default_help_fg")]
485 pub help_fg: ColorDef,
486 #[serde(default = "default_help_key_fg")]
488 pub help_key_fg: ColorDef,
489 #[serde(default = "default_help_separator_fg")]
491 pub help_separator_fg: ColorDef,
492 #[serde(default = "default_help_indicator_fg")]
494 pub help_indicator_fg: ColorDef,
495 #[serde(default = "default_help_indicator_bg")]
497 pub help_indicator_bg: ColorDef,
498 #[serde(default = "default_inline_code_bg")]
500 pub inline_code_bg: ColorDef,
501 #[serde(default = "default_split_separator_fg")]
503 pub split_separator_fg: ColorDef,
504 #[serde(default = "default_split_separator_hover_fg")]
506 pub split_separator_hover_fg: ColorDef,
507 #[serde(default = "default_scrollbar_track_fg")]
509 pub scrollbar_track_fg: ColorDef,
510 #[serde(default = "default_scrollbar_thumb_fg")]
512 pub scrollbar_thumb_fg: ColorDef,
513 #[serde(default = "default_scrollbar_track_hover_fg")]
515 pub scrollbar_track_hover_fg: ColorDef,
516 #[serde(default = "default_scrollbar_thumb_hover_fg")]
518 pub scrollbar_thumb_hover_fg: ColorDef,
519 #[serde(default = "default_compose_margin_bg")]
521 pub compose_margin_bg: ColorDef,
522 #[serde(default = "default_semantic_highlight_bg")]
524 pub semantic_highlight_bg: ColorDef,
525 #[serde(default = "default_terminal_bg")]
527 pub terminal_bg: ColorDef,
528 #[serde(default = "default_terminal_fg")]
530 pub terminal_fg: ColorDef,
531 #[serde(default = "default_status_warning_indicator_bg")]
533 pub status_warning_indicator_bg: ColorDef,
534 #[serde(default = "default_status_warning_indicator_fg")]
536 pub status_warning_indicator_fg: ColorDef,
537 #[serde(default = "default_status_error_indicator_bg")]
539 pub status_error_indicator_bg: ColorDef,
540 #[serde(default = "default_status_error_indicator_fg")]
542 pub status_error_indicator_fg: ColorDef,
543 #[serde(default = "default_status_warning_indicator_hover_bg")]
545 pub status_warning_indicator_hover_bg: ColorDef,
546 #[serde(default = "default_status_warning_indicator_hover_fg")]
548 pub status_warning_indicator_hover_fg: ColorDef,
549 #[serde(default = "default_status_error_indicator_hover_bg")]
551 pub status_error_indicator_hover_bg: ColorDef,
552 #[serde(default = "default_status_error_indicator_hover_fg")]
554 pub status_error_indicator_hover_fg: ColorDef,
555 #[serde(default = "default_tab_drop_zone_bg")]
557 pub tab_drop_zone_bg: ColorDef,
558 #[serde(default = "default_tab_drop_zone_border")]
560 pub tab_drop_zone_border: ColorDef,
561 #[serde(default = "default_settings_selected_bg")]
563 pub settings_selected_bg: ColorDef,
564 #[serde(default = "default_settings_selected_fg")]
566 pub settings_selected_fg: ColorDef,
567 #[serde(default)]
569 pub file_status_added_fg: Option<ColorDef>,
570 #[serde(default)]
572 pub file_status_modified_fg: Option<ColorDef>,
573 #[serde(default)]
575 pub file_status_deleted_fg: Option<ColorDef>,
576 #[serde(default)]
578 pub file_status_renamed_fg: Option<ColorDef>,
579 #[serde(default)]
581 pub file_status_untracked_fg: Option<ColorDef>,
582 #[serde(default)]
584 pub file_status_conflicted_fg: Option<ColorDef>,
585}
586
587fn default_tab_active_fg() -> ColorDef {
590 ColorDef::Named("Yellow".to_string())
591}
592fn default_tab_active_bg() -> ColorDef {
593 ColorDef::Named("Blue".to_string())
594}
595fn default_tab_inactive_fg() -> ColorDef {
596 ColorDef::Named("White".to_string())
597}
598fn default_tab_inactive_bg() -> ColorDef {
599 ColorDef::Named("DarkGray".to_string())
600}
601fn default_tab_separator_bg() -> ColorDef {
602 ColorDef::Named("Black".to_string())
603}
604fn default_tab_close_hover_fg() -> ColorDef {
605 ColorDef::Rgb(255, 100, 100) }
607fn default_tab_hover_bg() -> ColorDef {
608 ColorDef::Rgb(70, 70, 75) }
610
611fn default_menu_bg() -> ColorDef {
613 ColorDef::Rgb(60, 60, 65)
614}
615fn default_menu_fg() -> ColorDef {
616 ColorDef::Rgb(220, 220, 220)
617}
618fn default_menu_active_bg() -> ColorDef {
619 ColorDef::Rgb(60, 60, 60)
620}
621fn default_menu_active_fg() -> ColorDef {
622 ColorDef::Rgb(255, 255, 255)
623}
624fn default_menu_dropdown_bg() -> ColorDef {
625 ColorDef::Rgb(50, 50, 50)
626}
627fn default_menu_dropdown_fg() -> ColorDef {
628 ColorDef::Rgb(220, 220, 220)
629}
630fn default_menu_highlight_bg() -> ColorDef {
631 ColorDef::Rgb(70, 130, 180)
632}
633fn default_menu_highlight_fg() -> ColorDef {
634 ColorDef::Rgb(255, 255, 255)
635}
636fn default_menu_border_fg() -> ColorDef {
637 ColorDef::Rgb(100, 100, 100)
638}
639fn default_menu_separator_fg() -> ColorDef {
640 ColorDef::Rgb(80, 80, 80)
641}
642fn default_menu_hover_bg() -> ColorDef {
643 ColorDef::Rgb(55, 55, 55)
644}
645fn default_menu_hover_fg() -> ColorDef {
646 ColorDef::Rgb(255, 255, 255)
647}
648fn default_menu_disabled_fg() -> ColorDef {
649 ColorDef::Rgb(100, 100, 100) }
651fn default_menu_disabled_bg() -> ColorDef {
652 ColorDef::Rgb(50, 50, 50) }
654fn default_status_bar_fg() -> ColorDef {
656 ColorDef::Named("White".to_string())
657}
658fn default_status_bar_bg() -> ColorDef {
659 ColorDef::Named("DarkGray".to_string())
660}
661
662fn default_prompt_fg() -> ColorDef {
664 ColorDef::Named("White".to_string())
665}
666fn default_prompt_bg() -> ColorDef {
667 ColorDef::Named("Black".to_string())
668}
669fn default_prompt_selection_fg() -> ColorDef {
670 ColorDef::Named("White".to_string())
671}
672fn default_prompt_selection_bg() -> ColorDef {
673 ColorDef::Rgb(58, 79, 120)
674}
675
676fn default_popup_border_fg() -> ColorDef {
678 ColorDef::Named("Gray".to_string())
679}
680fn default_popup_bg() -> ColorDef {
681 ColorDef::Rgb(30, 30, 30)
682}
683fn default_popup_selection_bg() -> ColorDef {
684 ColorDef::Rgb(58, 79, 120)
685}
686fn default_popup_selection_fg() -> ColorDef {
687 ColorDef::Rgb(255, 255, 255) }
689fn default_popup_text_fg() -> ColorDef {
690 ColorDef::Named("White".to_string())
691}
692
693fn default_suggestion_bg() -> ColorDef {
695 ColorDef::Rgb(30, 30, 30)
696}
697fn default_suggestion_selected_bg() -> ColorDef {
698 ColorDef::Rgb(58, 79, 120)
699}
700
701fn default_help_bg() -> ColorDef {
703 ColorDef::Named("Black".to_string())
704}
705fn default_help_fg() -> ColorDef {
706 ColorDef::Named("White".to_string())
707}
708fn default_help_key_fg() -> ColorDef {
709 ColorDef::Named("Cyan".to_string())
710}
711fn default_help_separator_fg() -> ColorDef {
712 ColorDef::Named("DarkGray".to_string())
713}
714fn default_help_indicator_fg() -> ColorDef {
715 ColorDef::Named("Red".to_string())
716}
717fn default_help_indicator_bg() -> ColorDef {
718 ColorDef::Named("Black".to_string())
719}
720
721fn default_inline_code_bg() -> ColorDef {
722 ColorDef::Named("DarkGray".to_string())
723}
724
725fn default_split_separator_fg() -> ColorDef {
727 ColorDef::Rgb(100, 100, 100)
728}
729fn default_split_separator_hover_fg() -> ColorDef {
730 ColorDef::Rgb(100, 149, 237) }
732fn default_scrollbar_track_fg() -> ColorDef {
733 ColorDef::Named("DarkGray".to_string())
734}
735fn default_scrollbar_thumb_fg() -> ColorDef {
736 ColorDef::Named("Gray".to_string())
737}
738fn default_scrollbar_track_hover_fg() -> ColorDef {
739 ColorDef::Named("Gray".to_string())
740}
741fn default_scrollbar_thumb_hover_fg() -> ColorDef {
742 ColorDef::Named("White".to_string())
743}
744fn default_compose_margin_bg() -> ColorDef {
745 ColorDef::Rgb(18, 18, 18) }
747fn default_semantic_highlight_bg() -> ColorDef {
748 ColorDef::Rgb(60, 60, 80) }
750fn default_terminal_bg() -> ColorDef {
751 ColorDef::Named("Default".to_string()) }
753fn default_terminal_fg() -> ColorDef {
754 ColorDef::Named("Default".to_string()) }
756fn default_status_warning_indicator_bg() -> ColorDef {
757 ColorDef::Rgb(181, 137, 0) }
759fn default_status_warning_indicator_fg() -> ColorDef {
760 ColorDef::Rgb(0, 0, 0) }
762fn default_status_error_indicator_bg() -> ColorDef {
763 ColorDef::Rgb(220, 50, 47) }
765fn default_status_error_indicator_fg() -> ColorDef {
766 ColorDef::Rgb(255, 255, 255) }
768fn default_status_warning_indicator_hover_bg() -> ColorDef {
769 ColorDef::Rgb(211, 167, 30) }
771fn default_status_warning_indicator_hover_fg() -> ColorDef {
772 ColorDef::Rgb(0, 0, 0) }
774fn default_status_error_indicator_hover_bg() -> ColorDef {
775 ColorDef::Rgb(250, 80, 77) }
777fn default_status_error_indicator_hover_fg() -> ColorDef {
778 ColorDef::Rgb(255, 255, 255) }
780fn default_tab_drop_zone_bg() -> ColorDef {
781 ColorDef::Rgb(70, 130, 180) }
783fn default_tab_drop_zone_border() -> ColorDef {
784 ColorDef::Rgb(100, 149, 237) }
786fn default_settings_selected_bg() -> ColorDef {
787 ColorDef::Rgb(60, 60, 70) }
789fn default_settings_selected_fg() -> ColorDef {
790 ColorDef::Rgb(255, 255, 255) }
792#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
794pub struct SearchColors {
795 #[serde(default = "default_search_match_bg")]
797 pub match_bg: ColorDef,
798 #[serde(default = "default_search_match_fg")]
800 pub match_fg: ColorDef,
801 #[serde(default = "default_search_label_bg")]
805 pub label_bg: ColorDef,
806 #[serde(default = "default_search_label_fg")]
810 pub label_fg: ColorDef,
811}
812
813fn default_search_match_bg() -> ColorDef {
815 ColorDef::Rgb(100, 100, 20)
816}
817fn default_search_match_fg() -> ColorDef {
818 ColorDef::Rgb(255, 255, 255)
819}
820fn default_search_label_bg() -> ColorDef {
825 ColorDef::Rgb(199, 78, 189)
826}
827fn default_search_label_fg() -> ColorDef {
828 ColorDef::Rgb(255, 255, 255)
829}
830
831#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
833pub struct DiagnosticColors {
834 #[serde(default = "default_diagnostic_error_fg")]
836 pub error_fg: ColorDef,
837 #[serde(default = "default_diagnostic_error_bg")]
839 pub error_bg: ColorDef,
840 #[serde(default = "default_diagnostic_warning_fg")]
842 pub warning_fg: ColorDef,
843 #[serde(default = "default_diagnostic_warning_bg")]
845 pub warning_bg: ColorDef,
846 #[serde(default = "default_diagnostic_info_fg")]
848 pub info_fg: ColorDef,
849 #[serde(default = "default_diagnostic_info_bg")]
851 pub info_bg: ColorDef,
852 #[serde(default = "default_diagnostic_hint_fg")]
854 pub hint_fg: ColorDef,
855 #[serde(default = "default_diagnostic_hint_bg")]
857 pub hint_bg: ColorDef,
858}
859
860fn default_diagnostic_error_fg() -> ColorDef {
862 ColorDef::Named("Red".to_string())
863}
864fn default_diagnostic_error_bg() -> ColorDef {
865 ColorDef::Rgb(60, 20, 20)
866}
867fn default_diagnostic_warning_fg() -> ColorDef {
868 ColorDef::Named("Yellow".to_string())
869}
870fn default_diagnostic_warning_bg() -> ColorDef {
871 ColorDef::Rgb(60, 50, 0)
872}
873fn default_diagnostic_info_fg() -> ColorDef {
874 ColorDef::Named("Blue".to_string())
875}
876fn default_diagnostic_info_bg() -> ColorDef {
877 ColorDef::Rgb(0, 30, 60)
878}
879fn default_diagnostic_hint_fg() -> ColorDef {
880 ColorDef::Named("Gray".to_string())
881}
882fn default_diagnostic_hint_bg() -> ColorDef {
883 ColorDef::Rgb(30, 30, 30)
884}
885
886#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
888pub struct SyntaxColors {
889 #[serde(default = "default_syntax_keyword")]
891 pub keyword: ColorDef,
892 #[serde(default = "default_syntax_string")]
894 pub string: ColorDef,
895 #[serde(default = "default_syntax_comment")]
897 pub comment: ColorDef,
898 #[serde(default = "default_syntax_function")]
900 pub function: ColorDef,
901 #[serde(rename = "type", default = "default_syntax_type")]
903 pub type_: ColorDef,
904 #[serde(default = "default_syntax_variable")]
906 pub variable: ColorDef,
907 #[serde(default = "default_syntax_constant")]
909 pub constant: ColorDef,
910 #[serde(default = "default_syntax_operator")]
912 pub operator: ColorDef,
913 #[serde(default = "default_syntax_punctuation_bracket")]
915 pub punctuation_bracket: ColorDef,
916 #[serde(default = "default_syntax_punctuation_delimiter")]
918 pub punctuation_delimiter: ColorDef,
919}
920
921fn default_syntax_keyword() -> ColorDef {
923 ColorDef::Rgb(86, 156, 214)
924}
925fn default_syntax_string() -> ColorDef {
926 ColorDef::Rgb(206, 145, 120)
927}
928fn default_syntax_comment() -> ColorDef {
929 ColorDef::Rgb(106, 153, 85)
930}
931fn default_syntax_function() -> ColorDef {
932 ColorDef::Rgb(220, 220, 170)
933}
934fn default_syntax_type() -> ColorDef {
935 ColorDef::Rgb(78, 201, 176)
936}
937fn default_syntax_variable() -> ColorDef {
938 ColorDef::Rgb(156, 220, 254)
939}
940fn default_syntax_constant() -> ColorDef {
941 ColorDef::Rgb(79, 193, 255)
942}
943fn default_syntax_operator() -> ColorDef {
944 ColorDef::Rgb(212, 212, 212)
945}
946fn default_syntax_punctuation_bracket() -> ColorDef {
947 ColorDef::Rgb(212, 212, 212) }
949fn default_syntax_punctuation_delimiter() -> ColorDef {
950 ColorDef::Rgb(212, 212, 212) }
952
953#[derive(Debug, Clone)]
955pub struct Theme {
956 pub name: String,
958
959 pub editor_bg: Color,
961 pub editor_fg: Color,
962 pub cursor: Color,
963 pub inactive_cursor: Color,
964 pub selection_bg: Color,
965 pub current_line_bg: Color,
966 pub line_number_fg: Color,
967 pub line_number_bg: Color,
968
969 pub after_eof_bg: Color,
971
972 pub ruler_bg: Color,
974
975 pub whitespace_indicator_fg: Color,
977
978 pub diff_add_bg: Color,
980 pub diff_remove_bg: Color,
981 pub diff_modify_bg: Color,
982 pub diff_add_highlight_bg: Color,
984 pub diff_remove_highlight_bg: Color,
986
987 pub tab_active_fg: Color,
989 pub tab_active_bg: Color,
990 pub tab_inactive_fg: Color,
991 pub tab_inactive_bg: Color,
992 pub tab_separator_bg: Color,
993 pub tab_close_hover_fg: Color,
994 pub tab_hover_bg: Color,
995
996 pub menu_bg: Color,
998 pub menu_fg: Color,
999 pub menu_active_bg: Color,
1000 pub menu_active_fg: Color,
1001 pub menu_dropdown_bg: Color,
1002 pub menu_dropdown_fg: Color,
1003 pub menu_highlight_bg: Color,
1004 pub menu_highlight_fg: Color,
1005 pub menu_border_fg: Color,
1006 pub menu_separator_fg: Color,
1007 pub menu_hover_bg: Color,
1008 pub menu_hover_fg: Color,
1009 pub menu_disabled_fg: Color,
1010 pub menu_disabled_bg: Color,
1011
1012 pub status_bar_fg: Color,
1013 pub status_bar_bg: Color,
1014 pub status_palette_fg: Color,
1016 pub status_palette_bg: Color,
1017 pub status_lsp_on_fg: Color,
1019 pub status_lsp_on_bg: Color,
1020 pub prompt_fg: Color,
1021 pub prompt_bg: Color,
1022 pub prompt_selection_fg: Color,
1023 pub prompt_selection_bg: Color,
1024
1025 pub popup_border_fg: Color,
1026 pub popup_bg: Color,
1027 pub popup_selection_bg: Color,
1028 pub popup_selection_fg: Color,
1029 pub popup_text_fg: Color,
1030
1031 pub suggestion_bg: Color,
1032 pub suggestion_selected_bg: Color,
1033
1034 pub help_bg: Color,
1035 pub help_fg: Color,
1036 pub help_key_fg: Color,
1037 pub help_separator_fg: Color,
1038
1039 pub help_indicator_fg: Color,
1040 pub help_indicator_bg: Color,
1041
1042 pub inline_code_bg: Color,
1044
1045 pub split_separator_fg: Color,
1046 pub split_separator_hover_fg: Color,
1047
1048 pub scrollbar_track_fg: Color,
1050 pub scrollbar_thumb_fg: Color,
1051 pub scrollbar_track_hover_fg: Color,
1052 pub scrollbar_thumb_hover_fg: Color,
1053
1054 pub compose_margin_bg: Color,
1056
1057 pub semantic_highlight_bg: Color,
1059
1060 pub terminal_bg: Color,
1062 pub terminal_fg: Color,
1063
1064 pub status_warning_indicator_bg: Color,
1066 pub status_warning_indicator_fg: Color,
1067 pub status_error_indicator_bg: Color,
1068 pub status_error_indicator_fg: Color,
1069 pub status_warning_indicator_hover_bg: Color,
1070 pub status_warning_indicator_hover_fg: Color,
1071 pub status_error_indicator_hover_bg: Color,
1072 pub status_error_indicator_hover_fg: Color,
1073
1074 pub tab_drop_zone_bg: Color,
1076 pub tab_drop_zone_border: Color,
1077
1078 pub settings_selected_bg: Color,
1080 pub settings_selected_fg: Color,
1081
1082 pub file_status_added_fg: Color,
1084 pub file_status_modified_fg: Color,
1085 pub file_status_deleted_fg: Color,
1086 pub file_status_renamed_fg: Color,
1087 pub file_status_untracked_fg: Color,
1088 pub file_status_conflicted_fg: Color,
1089
1090 pub search_match_bg: Color,
1092 pub search_match_fg: Color,
1093 pub search_label_bg: Color,
1094 pub search_label_fg: Color,
1095
1096 pub diagnostic_error_fg: Color,
1098 pub diagnostic_error_bg: Color,
1099 pub diagnostic_warning_fg: Color,
1100 pub diagnostic_warning_bg: Color,
1101 pub diagnostic_info_fg: Color,
1102 pub diagnostic_info_bg: Color,
1103 pub diagnostic_hint_fg: Color,
1104 pub diagnostic_hint_bg: Color,
1105
1106 pub syntax_keyword: Color,
1108 pub syntax_string: Color,
1109 pub syntax_comment: Color,
1110 pub syntax_function: Color,
1111 pub syntax_type: Color,
1112 pub syntax_variable: Color,
1113 pub syntax_constant: Color,
1114 pub syntax_operator: Color,
1115 pub syntax_punctuation_bracket: Color,
1116 pub syntax_punctuation_delimiter: Color,
1117}
1118
1119impl From<ThemeFile> for Theme {
1120 fn from(file: ThemeFile) -> Self {
1121 Self {
1122 name: file.name,
1123 editor_bg: file.editor.bg.clone().into(),
1124 editor_fg: file.editor.fg.into(),
1125 cursor: file.editor.cursor.into(),
1126 inactive_cursor: file.editor.inactive_cursor.into(),
1127 selection_bg: file.editor.selection_bg.into(),
1128 current_line_bg: file.editor.current_line_bg.into(),
1129 line_number_fg: file.editor.line_number_fg.into(),
1130 line_number_bg: file.editor.line_number_bg.into(),
1131 after_eof_bg: file
1134 .editor
1135 .after_eof_bg
1136 .clone()
1137 .map(|c| c.into())
1138 .unwrap_or_else(|| shade_toward_contrast(file.editor.bg.clone().into(), 10)),
1139 ruler_bg: file.editor.ruler_bg.into(),
1140 whitespace_indicator_fg: file.editor.whitespace_indicator_fg.into(),
1141 diff_add_bg: file.editor.diff_add_bg.clone().into(),
1142 diff_remove_bg: file.editor.diff_remove_bg.clone().into(),
1143 diff_modify_bg: file.editor.diff_modify_bg.into(),
1144 diff_add_highlight_bg: file
1146 .editor
1147 .diff_add_highlight_bg
1148 .map(|c| c.into())
1149 .unwrap_or_else(|| brighten_color(file.editor.diff_add_bg.into(), 40)),
1150 diff_remove_highlight_bg: file
1151 .editor
1152 .diff_remove_highlight_bg
1153 .map(|c| c.into())
1154 .unwrap_or_else(|| brighten_color(file.editor.diff_remove_bg.into(), 40)),
1155 tab_active_fg: file.ui.tab_active_fg.into(),
1156 tab_active_bg: file.ui.tab_active_bg.into(),
1157 tab_inactive_fg: file.ui.tab_inactive_fg.into(),
1158 tab_inactive_bg: file.ui.tab_inactive_bg.into(),
1159 tab_separator_bg: file.ui.tab_separator_bg.into(),
1160 tab_close_hover_fg: file.ui.tab_close_hover_fg.into(),
1161 tab_hover_bg: file.ui.tab_hover_bg.into(),
1162 menu_bg: file.ui.menu_bg.into(),
1163 menu_fg: file.ui.menu_fg.into(),
1164 menu_active_bg: file.ui.menu_active_bg.into(),
1165 menu_active_fg: file.ui.menu_active_fg.into(),
1166 menu_dropdown_bg: file.ui.menu_dropdown_bg.into(),
1167 menu_dropdown_fg: file.ui.menu_dropdown_fg.into(),
1168 menu_highlight_bg: file.ui.menu_highlight_bg.into(),
1169 menu_highlight_fg: file.ui.menu_highlight_fg.into(),
1170 menu_border_fg: file.ui.menu_border_fg.into(),
1171 menu_separator_fg: file.ui.menu_separator_fg.into(),
1172 menu_hover_bg: file.ui.menu_hover_bg.into(),
1173 menu_hover_fg: file.ui.menu_hover_fg.into(),
1174 menu_disabled_fg: file.ui.menu_disabled_fg.into(),
1175 menu_disabled_bg: file.ui.menu_disabled_bg.into(),
1176 status_bar_fg: file.ui.status_bar_fg.clone().into(),
1177 status_bar_bg: file.ui.status_bar_bg.clone().into(),
1178 status_palette_fg: file
1179 .ui
1180 .status_palette_fg
1181 .clone()
1182 .map(|c| c.into())
1183 .unwrap_or_else(|| file.ui.status_bar_fg.clone().into()),
1184 status_palette_bg: file
1185 .ui
1186 .status_palette_bg
1187 .clone()
1188 .map(|c| c.into())
1189 .unwrap_or_else(|| file.ui.status_bar_bg.clone().into()),
1190 status_lsp_on_fg: file
1191 .ui
1192 .status_lsp_on_fg
1193 .clone()
1194 .map(|c| c.into())
1195 .unwrap_or_else(|| file.ui.status_bar_fg.clone().into()),
1196 status_lsp_on_bg: file
1197 .ui
1198 .status_lsp_on_bg
1199 .clone()
1200 .map(|c| c.into())
1201 .unwrap_or_else(|| file.ui.status_bar_bg.clone().into()),
1202 prompt_fg: file.ui.prompt_fg.into(),
1203 prompt_bg: file.ui.prompt_bg.into(),
1204 prompt_selection_fg: file.ui.prompt_selection_fg.into(),
1205 prompt_selection_bg: file.ui.prompt_selection_bg.into(),
1206 popup_border_fg: file.ui.popup_border_fg.into(),
1207 popup_bg: file.ui.popup_bg.into(),
1208 popup_selection_bg: file.ui.popup_selection_bg.into(),
1209 popup_selection_fg: file.ui.popup_selection_fg.into(),
1210 popup_text_fg: file.ui.popup_text_fg.into(),
1211 suggestion_bg: file.ui.suggestion_bg.into(),
1212 suggestion_selected_bg: file.ui.suggestion_selected_bg.into(),
1213 help_bg: file.ui.help_bg.into(),
1214 help_fg: file.ui.help_fg.into(),
1215 help_key_fg: file.ui.help_key_fg.into(),
1216 help_separator_fg: file.ui.help_separator_fg.into(),
1217 help_indicator_fg: file.ui.help_indicator_fg.into(),
1218 help_indicator_bg: file.ui.help_indicator_bg.into(),
1219 inline_code_bg: file.ui.inline_code_bg.into(),
1220 split_separator_fg: file.ui.split_separator_fg.into(),
1221 split_separator_hover_fg: file.ui.split_separator_hover_fg.into(),
1222 scrollbar_track_fg: file.ui.scrollbar_track_fg.into(),
1223 scrollbar_thumb_fg: file.ui.scrollbar_thumb_fg.into(),
1224 scrollbar_track_hover_fg: file.ui.scrollbar_track_hover_fg.into(),
1225 scrollbar_thumb_hover_fg: file.ui.scrollbar_thumb_hover_fg.into(),
1226 compose_margin_bg: file.ui.compose_margin_bg.into(),
1227 semantic_highlight_bg: file.ui.semantic_highlight_bg.into(),
1228 terminal_bg: file.ui.terminal_bg.into(),
1229 terminal_fg: file.ui.terminal_fg.into(),
1230 status_warning_indicator_bg: file.ui.status_warning_indicator_bg.into(),
1231 status_warning_indicator_fg: file.ui.status_warning_indicator_fg.into(),
1232 status_error_indicator_bg: file.ui.status_error_indicator_bg.into(),
1233 status_error_indicator_fg: file.ui.status_error_indicator_fg.into(),
1234 status_warning_indicator_hover_bg: file.ui.status_warning_indicator_hover_bg.into(),
1235 status_warning_indicator_hover_fg: file.ui.status_warning_indicator_hover_fg.into(),
1236 status_error_indicator_hover_bg: file.ui.status_error_indicator_hover_bg.into(),
1237 status_error_indicator_hover_fg: file.ui.status_error_indicator_hover_fg.into(),
1238 tab_drop_zone_bg: file.ui.tab_drop_zone_bg.into(),
1239 tab_drop_zone_border: file.ui.tab_drop_zone_border.into(),
1240 settings_selected_bg: file.ui.settings_selected_bg.into(),
1241 settings_selected_fg: file.ui.settings_selected_fg.into(),
1242 file_status_added_fg: file
1243 .ui
1244 .file_status_added_fg
1245 .map(|c| c.into())
1246 .unwrap_or_else(|| file.diagnostic.info_fg.clone().into()),
1247 file_status_modified_fg: file
1248 .ui
1249 .file_status_modified_fg
1250 .map(|c| c.into())
1251 .unwrap_or_else(|| file.diagnostic.warning_fg.clone().into()),
1252 file_status_deleted_fg: file
1253 .ui
1254 .file_status_deleted_fg
1255 .map(|c| c.into())
1256 .unwrap_or_else(|| file.diagnostic.error_fg.clone().into()),
1257 file_status_renamed_fg: file
1258 .ui
1259 .file_status_renamed_fg
1260 .map(|c| c.into())
1261 .unwrap_or_else(|| file.diagnostic.info_fg.clone().into()),
1262 file_status_untracked_fg: file
1263 .ui
1264 .file_status_untracked_fg
1265 .map(|c| c.into())
1266 .unwrap_or_else(|| file.diagnostic.hint_fg.clone().into()),
1267 file_status_conflicted_fg: file
1268 .ui
1269 .file_status_conflicted_fg
1270 .map(|c| c.into())
1271 .unwrap_or_else(|| file.diagnostic.error_fg.clone().into()),
1272 search_match_bg: file.search.match_bg.into(),
1273 search_match_fg: file.search.match_fg.into(),
1274 search_label_bg: file.search.label_bg.into(),
1275 search_label_fg: file.search.label_fg.into(),
1276 diagnostic_error_fg: file.diagnostic.error_fg.into(),
1277 diagnostic_error_bg: file.diagnostic.error_bg.into(),
1278 diagnostic_warning_fg: file.diagnostic.warning_fg.into(),
1279 diagnostic_warning_bg: file.diagnostic.warning_bg.into(),
1280 diagnostic_info_fg: file.diagnostic.info_fg.into(),
1281 diagnostic_info_bg: file.diagnostic.info_bg.into(),
1282 diagnostic_hint_fg: file.diagnostic.hint_fg.into(),
1283 diagnostic_hint_bg: file.diagnostic.hint_bg.into(),
1284 syntax_keyword: file.syntax.keyword.into(),
1285 syntax_string: file.syntax.string.into(),
1286 syntax_comment: file.syntax.comment.into(),
1287 syntax_function: file.syntax.function.into(),
1288 syntax_type: file.syntax.type_.into(),
1289 syntax_variable: file.syntax.variable.into(),
1290 syntax_constant: file.syntax.constant.into(),
1291 syntax_operator: file.syntax.operator.into(),
1292 syntax_punctuation_bracket: file.syntax.punctuation_bracket.into(),
1293 syntax_punctuation_delimiter: file.syntax.punctuation_delimiter.into(),
1294 }
1295 }
1296}
1297
1298impl From<Theme> for ThemeFile {
1299 fn from(theme: Theme) -> Self {
1300 Self {
1301 name: theme.name,
1302 editor: EditorColors {
1303 bg: theme.editor_bg.into(),
1304 fg: theme.editor_fg.into(),
1305 cursor: theme.cursor.into(),
1306 inactive_cursor: theme.inactive_cursor.into(),
1307 selection_bg: theme.selection_bg.into(),
1308 current_line_bg: theme.current_line_bg.into(),
1309 line_number_fg: theme.line_number_fg.into(),
1310 line_number_bg: theme.line_number_bg.into(),
1311 diff_add_bg: theme.diff_add_bg.into(),
1312 diff_remove_bg: theme.diff_remove_bg.into(),
1313 diff_add_highlight_bg: Some(theme.diff_add_highlight_bg.into()),
1314 diff_remove_highlight_bg: Some(theme.diff_remove_highlight_bg.into()),
1315 diff_modify_bg: theme.diff_modify_bg.into(),
1316 ruler_bg: theme.ruler_bg.into(),
1317 whitespace_indicator_fg: theme.whitespace_indicator_fg.into(),
1318 after_eof_bg: Some(theme.after_eof_bg.into()),
1319 },
1320 ui: UiColors {
1321 tab_active_fg: theme.tab_active_fg.into(),
1322 tab_active_bg: theme.tab_active_bg.into(),
1323 tab_inactive_fg: theme.tab_inactive_fg.into(),
1324 tab_inactive_bg: theme.tab_inactive_bg.into(),
1325 tab_separator_bg: theme.tab_separator_bg.into(),
1326 tab_close_hover_fg: theme.tab_close_hover_fg.into(),
1327 tab_hover_bg: theme.tab_hover_bg.into(),
1328 menu_bg: theme.menu_bg.into(),
1329 menu_fg: theme.menu_fg.into(),
1330 menu_active_bg: theme.menu_active_bg.into(),
1331 menu_active_fg: theme.menu_active_fg.into(),
1332 menu_dropdown_bg: theme.menu_dropdown_bg.into(),
1333 menu_dropdown_fg: theme.menu_dropdown_fg.into(),
1334 menu_highlight_bg: theme.menu_highlight_bg.into(),
1335 menu_highlight_fg: theme.menu_highlight_fg.into(),
1336 menu_border_fg: theme.menu_border_fg.into(),
1337 menu_separator_fg: theme.menu_separator_fg.into(),
1338 menu_hover_bg: theme.menu_hover_bg.into(),
1339 menu_hover_fg: theme.menu_hover_fg.into(),
1340 menu_disabled_fg: theme.menu_disabled_fg.into(),
1341 menu_disabled_bg: theme.menu_disabled_bg.into(),
1342 status_bar_fg: theme.status_bar_fg.into(),
1343 status_bar_bg: theme.status_bar_bg.into(),
1344 status_palette_fg: Some(theme.status_palette_fg.into()),
1345 status_palette_bg: Some(theme.status_palette_bg.into()),
1346 status_lsp_on_fg: Some(theme.status_lsp_on_fg.into()),
1347 status_lsp_on_bg: Some(theme.status_lsp_on_bg.into()),
1348 prompt_fg: theme.prompt_fg.into(),
1349 prompt_bg: theme.prompt_bg.into(),
1350 prompt_selection_fg: theme.prompt_selection_fg.into(),
1351 prompt_selection_bg: theme.prompt_selection_bg.into(),
1352 popup_border_fg: theme.popup_border_fg.into(),
1353 popup_bg: theme.popup_bg.into(),
1354 popup_selection_bg: theme.popup_selection_bg.into(),
1355 popup_selection_fg: theme.popup_selection_fg.into(),
1356 popup_text_fg: theme.popup_text_fg.into(),
1357 suggestion_bg: theme.suggestion_bg.into(),
1358 suggestion_selected_bg: theme.suggestion_selected_bg.into(),
1359 help_bg: theme.help_bg.into(),
1360 help_fg: theme.help_fg.into(),
1361 help_key_fg: theme.help_key_fg.into(),
1362 help_separator_fg: theme.help_separator_fg.into(),
1363 help_indicator_fg: theme.help_indicator_fg.into(),
1364 help_indicator_bg: theme.help_indicator_bg.into(),
1365 inline_code_bg: theme.inline_code_bg.into(),
1366 split_separator_fg: theme.split_separator_fg.into(),
1367 split_separator_hover_fg: theme.split_separator_hover_fg.into(),
1368 scrollbar_track_fg: theme.scrollbar_track_fg.into(),
1369 scrollbar_thumb_fg: theme.scrollbar_thumb_fg.into(),
1370 scrollbar_track_hover_fg: theme.scrollbar_track_hover_fg.into(),
1371 scrollbar_thumb_hover_fg: theme.scrollbar_thumb_hover_fg.into(),
1372 compose_margin_bg: theme.compose_margin_bg.into(),
1373 semantic_highlight_bg: theme.semantic_highlight_bg.into(),
1374 terminal_bg: theme.terminal_bg.into(),
1375 terminal_fg: theme.terminal_fg.into(),
1376 status_warning_indicator_bg: theme.status_warning_indicator_bg.into(),
1377 status_warning_indicator_fg: theme.status_warning_indicator_fg.into(),
1378 status_error_indicator_bg: theme.status_error_indicator_bg.into(),
1379 status_error_indicator_fg: theme.status_error_indicator_fg.into(),
1380 status_warning_indicator_hover_bg: theme.status_warning_indicator_hover_bg.into(),
1381 status_warning_indicator_hover_fg: theme.status_warning_indicator_hover_fg.into(),
1382 status_error_indicator_hover_bg: theme.status_error_indicator_hover_bg.into(),
1383 status_error_indicator_hover_fg: theme.status_error_indicator_hover_fg.into(),
1384 tab_drop_zone_bg: theme.tab_drop_zone_bg.into(),
1385 tab_drop_zone_border: theme.tab_drop_zone_border.into(),
1386 settings_selected_bg: theme.settings_selected_bg.into(),
1387 settings_selected_fg: theme.settings_selected_fg.into(),
1388 file_status_added_fg: Some(theme.file_status_added_fg.into()),
1389 file_status_modified_fg: Some(theme.file_status_modified_fg.into()),
1390 file_status_deleted_fg: Some(theme.file_status_deleted_fg.into()),
1391 file_status_renamed_fg: Some(theme.file_status_renamed_fg.into()),
1392 file_status_untracked_fg: Some(theme.file_status_untracked_fg.into()),
1393 file_status_conflicted_fg: Some(theme.file_status_conflicted_fg.into()),
1394 },
1395 search: SearchColors {
1396 match_bg: theme.search_match_bg.into(),
1397 match_fg: theme.search_match_fg.into(),
1398 label_bg: theme.search_label_bg.into(),
1399 label_fg: theme.search_label_fg.into(),
1400 },
1401 diagnostic: DiagnosticColors {
1402 error_fg: theme.diagnostic_error_fg.into(),
1403 error_bg: theme.diagnostic_error_bg.into(),
1404 warning_fg: theme.diagnostic_warning_fg.into(),
1405 warning_bg: theme.diagnostic_warning_bg.into(),
1406 info_fg: theme.diagnostic_info_fg.into(),
1407 info_bg: theme.diagnostic_info_bg.into(),
1408 hint_fg: theme.diagnostic_hint_fg.into(),
1409 hint_bg: theme.diagnostic_hint_bg.into(),
1410 },
1411 syntax: SyntaxColors {
1412 keyword: theme.syntax_keyword.into(),
1413 string: theme.syntax_string.into(),
1414 comment: theme.syntax_comment.into(),
1415 function: theme.syntax_function.into(),
1416 type_: theme.syntax_type.into(),
1417 variable: theme.syntax_variable.into(),
1418 constant: theme.syntax_constant.into(),
1419 operator: theme.syntax_operator.into(),
1420 punctuation_bracket: theme.syntax_punctuation_bracket.into(),
1421 punctuation_delimiter: theme.syntax_punctuation_delimiter.into(),
1422 },
1423 }
1424 }
1425}
1426
1427impl Theme {
1428 pub fn is_light(&self) -> bool {
1434 if let Some((r, g, b)) = color_to_rgb(self.editor_bg) {
1435 let lum = 0.2126 * (r as f64 / 255.0)
1437 + 0.7152 * (g as f64 / 255.0)
1438 + 0.0722 * (b as f64 / 255.0);
1439 lum > 0.5
1440 } else {
1441 false
1442 }
1443 }
1444
1445 pub fn load_builtin(name: &str) -> Option<Self> {
1447 BUILTIN_THEMES
1448 .iter()
1449 .find(|t| t.name == name)
1450 .and_then(|t| serde_json::from_str::<ThemeFile>(t.json).ok())
1451 .map(|tf| tf.into())
1452 }
1453
1454 pub fn from_json(json: &str) -> Result<Self, String> {
1456 let theme_file: ThemeFile =
1457 serde_json::from_str(json).map_err(|e| format!("Failed to parse theme JSON: {}", e))?;
1458 Ok(theme_file.into())
1459 }
1460
1461 pub fn resolve_theme_key(&self, key: &str) -> Option<Color> {
1472 let parts: Vec<&str> = key.split('.').collect();
1474 if parts.len() != 2 {
1475 return None;
1476 }
1477
1478 let (section, field) = (parts[0], parts[1]);
1479
1480 match section {
1481 "editor" => match field {
1482 "bg" => Some(self.editor_bg),
1483 "fg" => Some(self.editor_fg),
1484 "cursor" => Some(self.cursor),
1485 "inactive_cursor" => Some(self.inactive_cursor),
1486 "selection_bg" => Some(self.selection_bg),
1487 "current_line_bg" => Some(self.current_line_bg),
1488 "line_number_fg" => Some(self.line_number_fg),
1489 "line_number_bg" => Some(self.line_number_bg),
1490 "diff_add_bg" => Some(self.diff_add_bg),
1491 "diff_remove_bg" => Some(self.diff_remove_bg),
1492 "diff_modify_bg" => Some(self.diff_modify_bg),
1493 "ruler_bg" => Some(self.ruler_bg),
1494 "whitespace_indicator_fg" => Some(self.whitespace_indicator_fg),
1495 _ => None,
1496 },
1497 "ui" => match field {
1498 "tab_active_fg" => Some(self.tab_active_fg),
1499 "tab_active_bg" => Some(self.tab_active_bg),
1500 "tab_inactive_fg" => Some(self.tab_inactive_fg),
1501 "tab_inactive_bg" => Some(self.tab_inactive_bg),
1502 "status_bar_fg" => Some(self.status_bar_fg),
1503 "status_bar_bg" => Some(self.status_bar_bg),
1504 "status_palette_fg" => Some(self.status_palette_fg),
1505 "status_palette_bg" => Some(self.status_palette_bg),
1506 "status_lsp_on_fg" => Some(self.status_lsp_on_fg),
1507 "status_lsp_on_bg" => Some(self.status_lsp_on_bg),
1508 "prompt_fg" => Some(self.prompt_fg),
1509 "prompt_bg" => Some(self.prompt_bg),
1510 "prompt_selection_fg" => Some(self.prompt_selection_fg),
1511 "prompt_selection_bg" => Some(self.prompt_selection_bg),
1512 "popup_bg" => Some(self.popup_bg),
1513 "popup_border_fg" => Some(self.popup_border_fg),
1514 "popup_selection_bg" => Some(self.popup_selection_bg),
1515 "popup_selection_fg" => Some(self.popup_selection_fg),
1516 "popup_text_fg" => Some(self.popup_text_fg),
1517 "menu_bg" => Some(self.menu_bg),
1518 "menu_fg" => Some(self.menu_fg),
1519 "menu_active_bg" => Some(self.menu_active_bg),
1520 "menu_active_fg" => Some(self.menu_active_fg),
1521 "help_bg" => Some(self.help_bg),
1522 "help_fg" => Some(self.help_fg),
1523 "help_key_fg" => Some(self.help_key_fg),
1524 "split_separator_fg" => Some(self.split_separator_fg),
1525 "scrollbar_thumb_fg" => Some(self.scrollbar_thumb_fg),
1526 "semantic_highlight_bg" => Some(self.semantic_highlight_bg),
1527 "file_status_added_fg" => Some(self.file_status_added_fg),
1528 "file_status_modified_fg" => Some(self.file_status_modified_fg),
1529 "file_status_deleted_fg" => Some(self.file_status_deleted_fg),
1530 "file_status_renamed_fg" => Some(self.file_status_renamed_fg),
1531 "file_status_untracked_fg" => Some(self.file_status_untracked_fg),
1532 "file_status_conflicted_fg" => Some(self.file_status_conflicted_fg),
1533 _ => None,
1534 },
1535 "syntax" => match field {
1536 "keyword" => Some(self.syntax_keyword),
1537 "string" => Some(self.syntax_string),
1538 "comment" => Some(self.syntax_comment),
1539 "function" => Some(self.syntax_function),
1540 "type" => Some(self.syntax_type),
1541 "variable" => Some(self.syntax_variable),
1542 "constant" => Some(self.syntax_constant),
1543 "operator" => Some(self.syntax_operator),
1544 "punctuation_bracket" => Some(self.syntax_punctuation_bracket),
1545 "punctuation_delimiter" => Some(self.syntax_punctuation_delimiter),
1546 _ => None,
1547 },
1548 "diagnostic" => match field {
1549 "error_fg" => Some(self.diagnostic_error_fg),
1550 "error_bg" => Some(self.diagnostic_error_bg),
1551 "warning_fg" => Some(self.diagnostic_warning_fg),
1552 "warning_bg" => Some(self.diagnostic_warning_bg),
1553 "info_fg" => Some(self.diagnostic_info_fg),
1554 "info_bg" => Some(self.diagnostic_info_bg),
1555 "hint_fg" => Some(self.diagnostic_hint_fg),
1556 "hint_bg" => Some(self.diagnostic_hint_bg),
1557 _ => None,
1558 },
1559 "search" => match field {
1560 "match_bg" => Some(self.search_match_bg),
1561 "match_fg" => Some(self.search_match_fg),
1562 "label_bg" => Some(self.search_label_bg),
1563 "label_fg" => Some(self.search_label_fg),
1564 _ => None,
1565 },
1566 _ => None,
1567 }
1568 }
1569
1570 pub fn resolve_theme_key_mut(&mut self, key: &str) -> Option<&mut Color> {
1574 let parts: Vec<&str> = key.split('.').collect();
1575 if parts.len() != 2 {
1576 return None;
1577 }
1578 let (section, field) = (parts[0], parts[1]);
1579 match section {
1580 "editor" => match field {
1581 "bg" => Some(&mut self.editor_bg),
1582 "fg" => Some(&mut self.editor_fg),
1583 "cursor" => Some(&mut self.cursor),
1584 "inactive_cursor" => Some(&mut self.inactive_cursor),
1585 "selection_bg" => Some(&mut self.selection_bg),
1586 "current_line_bg" => Some(&mut self.current_line_bg),
1587 "line_number_fg" => Some(&mut self.line_number_fg),
1588 "line_number_bg" => Some(&mut self.line_number_bg),
1589 "diff_add_bg" => Some(&mut self.diff_add_bg),
1590 "diff_remove_bg" => Some(&mut self.diff_remove_bg),
1591 "diff_modify_bg" => Some(&mut self.diff_modify_bg),
1592 "ruler_bg" => Some(&mut self.ruler_bg),
1593 "whitespace_indicator_fg" => Some(&mut self.whitespace_indicator_fg),
1594 _ => None,
1595 },
1596 "ui" => match field {
1597 "tab_active_fg" => Some(&mut self.tab_active_fg),
1598 "tab_active_bg" => Some(&mut self.tab_active_bg),
1599 "tab_inactive_fg" => Some(&mut self.tab_inactive_fg),
1600 "tab_inactive_bg" => Some(&mut self.tab_inactive_bg),
1601 "status_bar_fg" => Some(&mut self.status_bar_fg),
1602 "status_bar_bg" => Some(&mut self.status_bar_bg),
1603 "status_palette_fg" => Some(&mut self.status_palette_fg),
1604 "status_palette_bg" => Some(&mut self.status_palette_bg),
1605 "status_lsp_on_fg" => Some(&mut self.status_lsp_on_fg),
1606 "status_lsp_on_bg" => Some(&mut self.status_lsp_on_bg),
1607 "prompt_fg" => Some(&mut self.prompt_fg),
1608 "prompt_bg" => Some(&mut self.prompt_bg),
1609 "prompt_selection_fg" => Some(&mut self.prompt_selection_fg),
1610 "prompt_selection_bg" => Some(&mut self.prompt_selection_bg),
1611 "popup_bg" => Some(&mut self.popup_bg),
1612 "popup_border_fg" => Some(&mut self.popup_border_fg),
1613 "popup_selection_bg" => Some(&mut self.popup_selection_bg),
1614 "popup_selection_fg" => Some(&mut self.popup_selection_fg),
1615 "popup_text_fg" => Some(&mut self.popup_text_fg),
1616 "menu_bg" => Some(&mut self.menu_bg),
1617 "menu_fg" => Some(&mut self.menu_fg),
1618 "menu_active_bg" => Some(&mut self.menu_active_bg),
1619 "menu_active_fg" => Some(&mut self.menu_active_fg),
1620 "help_bg" => Some(&mut self.help_bg),
1621 "help_fg" => Some(&mut self.help_fg),
1622 "help_key_fg" => Some(&mut self.help_key_fg),
1623 "split_separator_fg" => Some(&mut self.split_separator_fg),
1624 "scrollbar_thumb_fg" => Some(&mut self.scrollbar_thumb_fg),
1625 "semantic_highlight_bg" => Some(&mut self.semantic_highlight_bg),
1626 "file_status_added_fg" => Some(&mut self.file_status_added_fg),
1627 "file_status_modified_fg" => Some(&mut self.file_status_modified_fg),
1628 "file_status_deleted_fg" => Some(&mut self.file_status_deleted_fg),
1629 "file_status_renamed_fg" => Some(&mut self.file_status_renamed_fg),
1630 "file_status_untracked_fg" => Some(&mut self.file_status_untracked_fg),
1631 "file_status_conflicted_fg" => Some(&mut self.file_status_conflicted_fg),
1632 _ => None,
1633 },
1634 "syntax" => match field {
1635 "keyword" => Some(&mut self.syntax_keyword),
1636 "string" => Some(&mut self.syntax_string),
1637 "comment" => Some(&mut self.syntax_comment),
1638 "function" => Some(&mut self.syntax_function),
1639 "type" => Some(&mut self.syntax_type),
1640 "variable" => Some(&mut self.syntax_variable),
1641 "constant" => Some(&mut self.syntax_constant),
1642 "operator" => Some(&mut self.syntax_operator),
1643 "punctuation_bracket" => Some(&mut self.syntax_punctuation_bracket),
1644 "punctuation_delimiter" => Some(&mut self.syntax_punctuation_delimiter),
1645 _ => None,
1646 },
1647 "diagnostic" => match field {
1648 "error_fg" => Some(&mut self.diagnostic_error_fg),
1649 "error_bg" => Some(&mut self.diagnostic_error_bg),
1650 "warning_fg" => Some(&mut self.diagnostic_warning_fg),
1651 "warning_bg" => Some(&mut self.diagnostic_warning_bg),
1652 "info_fg" => Some(&mut self.diagnostic_info_fg),
1653 "info_bg" => Some(&mut self.diagnostic_info_bg),
1654 "hint_fg" => Some(&mut self.diagnostic_hint_fg),
1655 "hint_bg" => Some(&mut self.diagnostic_hint_bg),
1656 _ => None,
1657 },
1658 "search" => match field {
1659 "match_bg" => Some(&mut self.search_match_bg),
1660 "match_fg" => Some(&mut self.search_match_fg),
1661 "label_bg" => Some(&mut self.search_label_bg),
1662 "label_fg" => Some(&mut self.search_label_fg),
1663 _ => None,
1664 },
1665 _ => None,
1666 }
1667 }
1668
1669 pub fn override_colors<I, K>(&mut self, overrides: I) -> usize
1674 where
1675 I: IntoIterator<Item = (K, Color)>,
1676 K: AsRef<str>,
1677 {
1678 let mut applied = 0;
1679 for (key, color) in overrides {
1680 if let Some(slot) = self.resolve_theme_key_mut(key.as_ref()) {
1681 *slot = color;
1682 applied += 1;
1683 }
1684 }
1685 applied
1686 }
1687}
1688
1689pub fn get_theme_schema() -> serde_json::Value {
1697 use schemars::schema_for;
1698 let schema = schema_for!(ThemeFile);
1699 serde_json::to_value(&schema).unwrap_or_default()
1700}
1701
1702pub fn get_builtin_themes() -> serde_json::Value {
1704 let mut map = serde_json::Map::new();
1705 for theme in BUILTIN_THEMES {
1706 map.insert(
1707 theme.name.to_string(),
1708 serde_json::Value::String(theme.json.to_string()),
1709 );
1710 }
1711 serde_json::Value::Object(map)
1712}
1713
1714#[cfg(test)]
1715mod tests {
1716 use super::*;
1717
1718 #[test]
1719 fn test_load_builtin_theme() {
1720 let dark = Theme::load_builtin(THEME_DARK).expect("Dark theme must exist");
1721 assert_eq!(dark.name, THEME_DARK);
1722
1723 let light = Theme::load_builtin(THEME_LIGHT).expect("Light theme must exist");
1724 assert_eq!(light.name, THEME_LIGHT);
1725
1726 let high_contrast =
1727 Theme::load_builtin(THEME_HIGH_CONTRAST).expect("High contrast theme must exist");
1728 assert_eq!(high_contrast.name, THEME_HIGH_CONTRAST);
1729 }
1730
1731 #[test]
1732 fn test_builtin_themes_match_schema() {
1733 for theme in BUILTIN_THEMES {
1734 let _: ThemeFile = serde_json::from_str(theme.json)
1735 .unwrap_or_else(|_| panic!("Theme '{}' does not match schema", theme.name));
1736 }
1737 }
1738
1739 #[test]
1740 fn test_from_json() {
1741 let json = r#"{"name":"test","editor":{},"ui":{},"search":{},"diagnostic":{},"syntax":{}}"#;
1742 let theme = Theme::from_json(json).expect("Should parse minimal theme");
1743 assert_eq!(theme.name, "test");
1744 }
1745
1746 #[test]
1747 fn test_default_reset_color() {
1748 let color: Color = ColorDef::Named("Default".to_string()).into();
1750 assert_eq!(color, Color::Reset);
1751
1752 let color: Color = ColorDef::Named("Reset".to_string()).into();
1754 assert_eq!(color, Color::Reset);
1755 }
1756
1757 #[test]
1758 fn test_file_status_colors_fall_back_to_diagnostic_colors() {
1759 let json = r#"{
1761 "name": "test-fallback",
1762 "editor": {},
1763 "ui": {},
1764 "search": {},
1765 "diagnostic": {
1766 "error_fg": [220, 50, 47],
1767 "warning_fg": [181, 137, 0],
1768 "info_fg": [38, 139, 210],
1769 "hint_fg": [101, 123, 131]
1770 },
1771 "syntax": {}
1772 }"#;
1773 let theme = Theme::from_json(json).expect("Should parse theme without file_status keys");
1774
1775 assert_eq!(theme.file_status_added_fg, Color::Rgb(38, 139, 210));
1777 assert_eq!(theme.file_status_renamed_fg, Color::Rgb(38, 139, 210));
1778 assert_eq!(theme.file_status_modified_fg, Color::Rgb(181, 137, 0));
1780 assert_eq!(theme.file_status_deleted_fg, Color::Rgb(220, 50, 47));
1782 assert_eq!(theme.file_status_conflicted_fg, Color::Rgb(220, 50, 47));
1783 assert_eq!(theme.file_status_untracked_fg, Color::Rgb(101, 123, 131));
1785 }
1786
1787 #[test]
1788 fn test_file_status_colors_explicit_override() {
1789 let json = r#"{
1791 "name": "test-override",
1792 "editor": {},
1793 "ui": {
1794 "file_status_added_fg": [80, 250, 123],
1795 "file_status_modified_fg": [255, 184, 108]
1796 },
1797 "search": {},
1798 "diagnostic": {
1799 "info_fg": [38, 139, 210],
1800 "warning_fg": [181, 137, 0]
1801 },
1802 "syntax": {}
1803 }"#;
1804 let theme = Theme::from_json(json).expect("Should parse theme with file_status overrides");
1805
1806 assert_eq!(theme.file_status_added_fg, Color::Rgb(80, 250, 123));
1808 assert_eq!(theme.file_status_modified_fg, Color::Rgb(255, 184, 108));
1809 assert_eq!(theme.file_status_renamed_fg, Color::Rgb(38, 139, 210));
1811 }
1812
1813 #[test]
1814 fn test_file_status_colors_resolve_via_theme_key() {
1815 let json = r#"{
1816 "name": "test-resolve",
1817 "editor": {},
1818 "ui": {
1819 "file_status_added_fg": [80, 250, 123]
1820 },
1821 "search": {},
1822 "diagnostic": {
1823 "warning_fg": [181, 137, 0]
1824 },
1825 "syntax": {}
1826 }"#;
1827 let theme = Theme::from_json(json).expect("Should parse theme");
1828
1829 assert_eq!(
1831 theme.resolve_theme_key("ui.file_status_added_fg"),
1832 Some(Color::Rgb(80, 250, 123))
1833 );
1834 assert_eq!(
1835 theme.resolve_theme_key("ui.file_status_modified_fg"),
1836 Some(Color::Rgb(181, 137, 0))
1837 );
1838 }
1839
1840 #[test]
1841 fn override_colors_writes_known_keys_and_drops_unknowns() {
1842 let mut theme = Theme::load_builtin(THEME_DARK).expect("dark builtin");
1843 let applied = theme.override_colors([
1844 ("editor.bg".to_string(), Color::Rgb(10, 20, 30)),
1845 ("ui.status_bar_fg".to_string(), Color::Rgb(1, 2, 3)),
1846 ("does.not_exist".to_string(), Color::Rgb(9, 9, 9)),
1847 ("garbage_no_dot".to_string(), Color::Rgb(9, 9, 9)),
1848 ]);
1849 assert_eq!(applied, 2, "only the two valid keys should be applied");
1850 assert_eq!(
1851 theme.resolve_theme_key("editor.bg"),
1852 Some(Color::Rgb(10, 20, 30))
1853 );
1854 assert_eq!(
1855 theme.resolve_theme_key("ui.status_bar_fg"),
1856 Some(Color::Rgb(1, 2, 3))
1857 );
1858 }
1859
1860 #[test]
1861 fn resolve_theme_key_mut_matches_resolve_theme_key_domain() {
1862 let mut theme = Theme::load_builtin(THEME_DARK).expect("dark builtin");
1865 let probe = [
1866 "editor.bg",
1867 "editor.fg",
1868 "ui.status_bar_fg",
1869 "ui.tab_active_bg",
1870 "syntax.keyword",
1871 "diagnostic.error_fg",
1872 "search.match_bg",
1873 ];
1874 for key in probe {
1875 assert!(
1876 theme.resolve_theme_key(key).is_some(),
1877 "reader lost key {key}"
1878 );
1879 assert!(
1880 theme.resolve_theme_key_mut(key).is_some(),
1881 "mutator missing key {key}"
1882 );
1883 }
1884 }
1885
1886 #[test]
1887 fn test_all_builtin_themes_set_prominent_palette_indicator() {
1888 for builtin in BUILTIN_THEMES {
1895 let theme = Theme::from_json(builtin.json)
1896 .unwrap_or_else(|e| panic!("Theme '{}' failed to parse: {}", builtin.name, e));
1897 assert!(
1898 theme.status_palette_fg != theme.status_bar_fg
1899 || theme.status_palette_bg != theme.status_bar_bg,
1900 "Theme '{}' must set status_palette_fg/bg to a prominent \
1901 accent distinct from status_bar_fg/bg",
1902 builtin.name
1903 );
1904 }
1905 }
1906
1907 #[test]
1908 fn test_all_builtin_themes_have_file_status_colors() {
1909 for builtin in BUILTIN_THEMES {
1911 let theme = Theme::from_json(builtin.json)
1912 .unwrap_or_else(|e| panic!("Theme '{}' failed to parse: {}", builtin.name, e));
1913
1914 for key in &[
1916 "ui.file_status_added_fg",
1917 "ui.file_status_modified_fg",
1918 "ui.file_status_deleted_fg",
1919 "ui.file_status_renamed_fg",
1920 "ui.file_status_untracked_fg",
1921 "ui.file_status_conflicted_fg",
1922 ] {
1923 assert!(
1924 theme.resolve_theme_key(key).is_some(),
1925 "Theme '{}' missing resolution for '{}'",
1926 builtin.name,
1927 key
1928 );
1929 }
1930 }
1931 }
1932}