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 json: &'static str,
22}
23
24pub const BUILTIN_THEMES: &[BuiltinTheme] = &[
26 BuiltinTheme {
27 name: THEME_DARK,
28 json: include_str!("../../../themes/dark.json"),
29 },
30 BuiltinTheme {
31 name: THEME_LIGHT,
32 json: include_str!("../../../themes/light.json"),
33 },
34 BuiltinTheme {
35 name: THEME_HIGH_CONTRAST,
36 json: include_str!("../../../themes/high-contrast.json"),
37 },
38 BuiltinTheme {
39 name: THEME_NOSTALGIA,
40 json: include_str!("../../../themes/nostalgia.json"),
41 },
42 BuiltinTheme {
43 name: THEME_DRACULA,
44 json: include_str!("../../../themes/dracula.json"),
45 },
46 BuiltinTheme {
47 name: THEME_NORD,
48 json: include_str!("../../../themes/nord.json"),
49 },
50 BuiltinTheme {
51 name: THEME_SOLARIZED_DARK,
52 json: include_str!("../../../themes/solarized-dark.json"),
53 },
54];
55
56pub fn color_to_rgb(color: Color) -> Option<(u8, u8, u8)> {
59 match color {
60 Color::Rgb(r, g, b) => Some((r, g, b)),
61 Color::White => Some((255, 255, 255)),
62 Color::Black => Some((0, 0, 0)),
63 Color::Red => Some((205, 0, 0)),
64 Color::Green => Some((0, 205, 0)),
65 Color::Blue => Some((0, 0, 238)),
66 Color::Yellow => Some((205, 205, 0)),
67 Color::Magenta => Some((205, 0, 205)),
68 Color::Cyan => Some((0, 205, 205)),
69 Color::Gray => Some((229, 229, 229)),
70 Color::DarkGray => Some((127, 127, 127)),
71 Color::LightRed => Some((255, 0, 0)),
72 Color::LightGreen => Some((0, 255, 0)),
73 Color::LightBlue => Some((92, 92, 255)),
74 Color::LightYellow => Some((255, 255, 0)),
75 Color::LightMagenta => Some((255, 0, 255)),
76 Color::LightCyan => Some((0, 255, 255)),
77 Color::Reset | Color::Indexed(_) => None,
78 }
79}
80
81pub fn brighten_color(color: Color, amount: u8) -> Color {
84 if let Some((r, g, b)) = color_to_rgb(color) {
85 Color::Rgb(
86 r.saturating_add(amount),
87 g.saturating_add(amount),
88 b.saturating_add(amount),
89 )
90 } else {
91 color
92 }
93}
94
95#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
97#[serde(untagged)]
98pub enum ColorDef {
99 Rgb(u8, u8, u8),
101 Named(String),
103}
104
105impl From<ColorDef> for Color {
106 fn from(def: ColorDef) -> Self {
107 match def {
108 ColorDef::Rgb(r, g, b) => Color::Rgb(r, g, b),
109 ColorDef::Named(name) => match name.as_str() {
110 "Black" => Color::Black,
111 "Red" => Color::Red,
112 "Green" => Color::Green,
113 "Yellow" => Color::Yellow,
114 "Blue" => Color::Blue,
115 "Magenta" => Color::Magenta,
116 "Cyan" => Color::Cyan,
117 "Gray" => Color::Gray,
118 "DarkGray" => Color::DarkGray,
119 "LightRed" => Color::LightRed,
120 "LightGreen" => Color::LightGreen,
121 "LightYellow" => Color::LightYellow,
122 "LightBlue" => Color::LightBlue,
123 "LightMagenta" => Color::LightMagenta,
124 "LightCyan" => Color::LightCyan,
125 "White" => Color::White,
126 "Default" | "Reset" => Color::Reset,
128 _ => Color::White, },
130 }
131 }
132}
133
134impl From<Color> for ColorDef {
135 fn from(color: Color) -> Self {
136 match color {
137 Color::Rgb(r, g, b) => ColorDef::Rgb(r, g, b),
138 Color::White => ColorDef::Named("White".to_string()),
139 Color::Black => ColorDef::Named("Black".to_string()),
140 Color::Red => ColorDef::Named("Red".to_string()),
141 Color::Green => ColorDef::Named("Green".to_string()),
142 Color::Blue => ColorDef::Named("Blue".to_string()),
143 Color::Yellow => ColorDef::Named("Yellow".to_string()),
144 Color::Magenta => ColorDef::Named("Magenta".to_string()),
145 Color::Cyan => ColorDef::Named("Cyan".to_string()),
146 Color::Gray => ColorDef::Named("Gray".to_string()),
147 Color::DarkGray => ColorDef::Named("DarkGray".to_string()),
148 Color::LightRed => ColorDef::Named("LightRed".to_string()),
149 Color::LightGreen => ColorDef::Named("LightGreen".to_string()),
150 Color::LightBlue => ColorDef::Named("LightBlue".to_string()),
151 Color::LightYellow => ColorDef::Named("LightYellow".to_string()),
152 Color::LightMagenta => ColorDef::Named("LightMagenta".to_string()),
153 Color::LightCyan => ColorDef::Named("LightCyan".to_string()),
154 Color::Reset => ColorDef::Named("Default".to_string()),
155 Color::Indexed(_) => {
156 if let Some((r, g, b)) = color_to_rgb(color) {
158 ColorDef::Rgb(r, g, b)
159 } else {
160 ColorDef::Named("Default".to_string())
161 }
162 }
163 }
164 }
165}
166
167#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
169pub struct ThemeFile {
170 pub name: String,
172 pub editor: EditorColors,
174 pub ui: UiColors,
176 pub search: SearchColors,
178 pub diagnostic: DiagnosticColors,
180 pub syntax: SyntaxColors,
182}
183
184#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
186pub struct EditorColors {
187 #[serde(default = "default_editor_bg")]
189 pub bg: ColorDef,
190 #[serde(default = "default_editor_fg")]
192 pub fg: ColorDef,
193 #[serde(default = "default_cursor")]
195 pub cursor: ColorDef,
196 #[serde(default = "default_inactive_cursor")]
198 pub inactive_cursor: ColorDef,
199 #[serde(default = "default_selection_bg")]
201 pub selection_bg: ColorDef,
202 #[serde(default = "default_current_line_bg")]
204 pub current_line_bg: ColorDef,
205 #[serde(default = "default_line_number_fg")]
207 pub line_number_fg: ColorDef,
208 #[serde(default = "default_line_number_bg")]
210 pub line_number_bg: ColorDef,
211 #[serde(default = "default_diff_add_bg")]
213 pub diff_add_bg: ColorDef,
214 #[serde(default = "default_diff_remove_bg")]
216 pub diff_remove_bg: ColorDef,
217 #[serde(default = "default_diff_modify_bg")]
219 pub diff_modify_bg: ColorDef,
220}
221
222fn default_editor_bg() -> ColorDef {
224 ColorDef::Rgb(30, 30, 30)
225}
226fn default_editor_fg() -> ColorDef {
227 ColorDef::Rgb(212, 212, 212)
228}
229fn default_cursor() -> ColorDef {
230 ColorDef::Rgb(255, 255, 255)
231}
232fn default_inactive_cursor() -> ColorDef {
233 ColorDef::Named("DarkGray".to_string())
234}
235fn default_selection_bg() -> ColorDef {
236 ColorDef::Rgb(38, 79, 120)
237}
238fn default_current_line_bg() -> ColorDef {
239 ColorDef::Rgb(40, 40, 40)
240}
241fn default_line_number_fg() -> ColorDef {
242 ColorDef::Rgb(100, 100, 100)
243}
244fn default_line_number_bg() -> ColorDef {
245 ColorDef::Rgb(30, 30, 30)
246}
247fn default_diff_add_bg() -> ColorDef {
248 ColorDef::Rgb(35, 60, 35) }
250fn default_diff_remove_bg() -> ColorDef {
251 ColorDef::Rgb(70, 35, 35) }
253fn default_diff_modify_bg() -> ColorDef {
254 ColorDef::Rgb(40, 38, 30) }
256
257#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
259pub struct UiColors {
260 #[serde(default = "default_tab_active_fg")]
262 pub tab_active_fg: ColorDef,
263 #[serde(default = "default_tab_active_bg")]
265 pub tab_active_bg: ColorDef,
266 #[serde(default = "default_tab_inactive_fg")]
268 pub tab_inactive_fg: ColorDef,
269 #[serde(default = "default_tab_inactive_bg")]
271 pub tab_inactive_bg: ColorDef,
272 #[serde(default = "default_tab_separator_bg")]
274 pub tab_separator_bg: ColorDef,
275 #[serde(default = "default_tab_close_hover_fg")]
277 pub tab_close_hover_fg: ColorDef,
278 #[serde(default = "default_tab_hover_bg")]
280 pub tab_hover_bg: ColorDef,
281 #[serde(default = "default_menu_bg")]
283 pub menu_bg: ColorDef,
284 #[serde(default = "default_menu_fg")]
286 pub menu_fg: ColorDef,
287 #[serde(default = "default_menu_active_bg")]
289 pub menu_active_bg: ColorDef,
290 #[serde(default = "default_menu_active_fg")]
292 pub menu_active_fg: ColorDef,
293 #[serde(default = "default_menu_dropdown_bg")]
295 pub menu_dropdown_bg: ColorDef,
296 #[serde(default = "default_menu_dropdown_fg")]
298 pub menu_dropdown_fg: ColorDef,
299 #[serde(default = "default_menu_highlight_bg")]
301 pub menu_highlight_bg: ColorDef,
302 #[serde(default = "default_menu_highlight_fg")]
304 pub menu_highlight_fg: ColorDef,
305 #[serde(default = "default_menu_border_fg")]
307 pub menu_border_fg: ColorDef,
308 #[serde(default = "default_menu_separator_fg")]
310 pub menu_separator_fg: ColorDef,
311 #[serde(default = "default_menu_hover_bg")]
313 pub menu_hover_bg: ColorDef,
314 #[serde(default = "default_menu_hover_fg")]
316 pub menu_hover_fg: ColorDef,
317 #[serde(default = "default_menu_disabled_fg")]
319 pub menu_disabled_fg: ColorDef,
320 #[serde(default = "default_menu_disabled_bg")]
322 pub menu_disabled_bg: ColorDef,
323 #[serde(default = "default_status_bar_fg")]
325 pub status_bar_fg: ColorDef,
326 #[serde(default = "default_status_bar_bg")]
328 pub status_bar_bg: ColorDef,
329 #[serde(default = "default_prompt_fg")]
331 pub prompt_fg: ColorDef,
332 #[serde(default = "default_prompt_bg")]
334 pub prompt_bg: ColorDef,
335 #[serde(default = "default_prompt_selection_fg")]
337 pub prompt_selection_fg: ColorDef,
338 #[serde(default = "default_prompt_selection_bg")]
340 pub prompt_selection_bg: ColorDef,
341 #[serde(default = "default_popup_border_fg")]
343 pub popup_border_fg: ColorDef,
344 #[serde(default = "default_popup_bg")]
346 pub popup_bg: ColorDef,
347 #[serde(default = "default_popup_selection_bg")]
349 pub popup_selection_bg: ColorDef,
350 #[serde(default = "default_popup_text_fg")]
352 pub popup_text_fg: ColorDef,
353 #[serde(default = "default_suggestion_bg")]
355 pub suggestion_bg: ColorDef,
356 #[serde(default = "default_suggestion_selected_bg")]
358 pub suggestion_selected_bg: ColorDef,
359 #[serde(default = "default_help_bg")]
361 pub help_bg: ColorDef,
362 #[serde(default = "default_help_fg")]
364 pub help_fg: ColorDef,
365 #[serde(default = "default_help_key_fg")]
367 pub help_key_fg: ColorDef,
368 #[serde(default = "default_help_separator_fg")]
370 pub help_separator_fg: ColorDef,
371 #[serde(default = "default_help_indicator_fg")]
373 pub help_indicator_fg: ColorDef,
374 #[serde(default = "default_help_indicator_bg")]
376 pub help_indicator_bg: ColorDef,
377 #[serde(default = "default_inline_code_bg")]
379 pub inline_code_bg: ColorDef,
380 #[serde(default = "default_split_separator_fg")]
382 pub split_separator_fg: ColorDef,
383 #[serde(default = "default_split_separator_hover_fg")]
385 pub split_separator_hover_fg: ColorDef,
386 #[serde(default = "default_scrollbar_track_fg")]
388 pub scrollbar_track_fg: ColorDef,
389 #[serde(default = "default_scrollbar_thumb_fg")]
391 pub scrollbar_thumb_fg: ColorDef,
392 #[serde(default = "default_scrollbar_track_hover_fg")]
394 pub scrollbar_track_hover_fg: ColorDef,
395 #[serde(default = "default_scrollbar_thumb_hover_fg")]
397 pub scrollbar_thumb_hover_fg: ColorDef,
398 #[serde(default = "default_compose_margin_bg")]
400 pub compose_margin_bg: ColorDef,
401 #[serde(default = "default_semantic_highlight_bg")]
403 pub semantic_highlight_bg: ColorDef,
404 #[serde(default = "default_terminal_bg")]
406 pub terminal_bg: ColorDef,
407 #[serde(default = "default_terminal_fg")]
409 pub terminal_fg: ColorDef,
410 #[serde(default = "default_status_warning_indicator_bg")]
412 pub status_warning_indicator_bg: ColorDef,
413 #[serde(default = "default_status_warning_indicator_fg")]
415 pub status_warning_indicator_fg: ColorDef,
416 #[serde(default = "default_status_error_indicator_bg")]
418 pub status_error_indicator_bg: ColorDef,
419 #[serde(default = "default_status_error_indicator_fg")]
421 pub status_error_indicator_fg: ColorDef,
422 #[serde(default = "default_status_warning_indicator_hover_bg")]
424 pub status_warning_indicator_hover_bg: ColorDef,
425 #[serde(default = "default_status_warning_indicator_hover_fg")]
427 pub status_warning_indicator_hover_fg: ColorDef,
428 #[serde(default = "default_status_error_indicator_hover_bg")]
430 pub status_error_indicator_hover_bg: ColorDef,
431 #[serde(default = "default_status_error_indicator_hover_fg")]
433 pub status_error_indicator_hover_fg: ColorDef,
434 #[serde(default = "default_tab_drop_zone_bg")]
436 pub tab_drop_zone_bg: ColorDef,
437 #[serde(default = "default_tab_drop_zone_border")]
439 pub tab_drop_zone_border: ColorDef,
440}
441
442fn default_tab_active_fg() -> ColorDef {
445 ColorDef::Named("Yellow".to_string())
446}
447fn default_tab_active_bg() -> ColorDef {
448 ColorDef::Named("Blue".to_string())
449}
450fn default_tab_inactive_fg() -> ColorDef {
451 ColorDef::Named("White".to_string())
452}
453fn default_tab_inactive_bg() -> ColorDef {
454 ColorDef::Named("DarkGray".to_string())
455}
456fn default_tab_separator_bg() -> ColorDef {
457 ColorDef::Named("Black".to_string())
458}
459fn default_tab_close_hover_fg() -> ColorDef {
460 ColorDef::Rgb(255, 100, 100) }
462fn default_tab_hover_bg() -> ColorDef {
463 ColorDef::Rgb(70, 70, 75) }
465
466fn default_menu_bg() -> ColorDef {
468 ColorDef::Rgb(60, 60, 65)
469}
470fn default_menu_fg() -> ColorDef {
471 ColorDef::Rgb(220, 220, 220)
472}
473fn default_menu_active_bg() -> ColorDef {
474 ColorDef::Rgb(60, 60, 60)
475}
476fn default_menu_active_fg() -> ColorDef {
477 ColorDef::Rgb(255, 255, 255)
478}
479fn default_menu_dropdown_bg() -> ColorDef {
480 ColorDef::Rgb(50, 50, 50)
481}
482fn default_menu_dropdown_fg() -> ColorDef {
483 ColorDef::Rgb(220, 220, 220)
484}
485fn default_menu_highlight_bg() -> ColorDef {
486 ColorDef::Rgb(70, 130, 180)
487}
488fn default_menu_highlight_fg() -> ColorDef {
489 ColorDef::Rgb(255, 255, 255)
490}
491fn default_menu_border_fg() -> ColorDef {
492 ColorDef::Rgb(100, 100, 100)
493}
494fn default_menu_separator_fg() -> ColorDef {
495 ColorDef::Rgb(80, 80, 80)
496}
497fn default_menu_hover_bg() -> ColorDef {
498 ColorDef::Rgb(55, 55, 55)
499}
500fn default_menu_hover_fg() -> ColorDef {
501 ColorDef::Rgb(255, 255, 255)
502}
503fn default_menu_disabled_fg() -> ColorDef {
504 ColorDef::Rgb(100, 100, 100) }
506fn default_menu_disabled_bg() -> ColorDef {
507 ColorDef::Rgb(50, 50, 50) }
509fn default_status_bar_fg() -> ColorDef {
511 ColorDef::Named("White".to_string())
512}
513fn default_status_bar_bg() -> ColorDef {
514 ColorDef::Named("DarkGray".to_string())
515}
516
517fn default_prompt_fg() -> ColorDef {
519 ColorDef::Named("White".to_string())
520}
521fn default_prompt_bg() -> ColorDef {
522 ColorDef::Named("Black".to_string())
523}
524fn default_prompt_selection_fg() -> ColorDef {
525 ColorDef::Named("White".to_string())
526}
527fn default_prompt_selection_bg() -> ColorDef {
528 ColorDef::Rgb(58, 79, 120)
529}
530
531fn default_popup_border_fg() -> ColorDef {
533 ColorDef::Named("Gray".to_string())
534}
535fn default_popup_bg() -> ColorDef {
536 ColorDef::Rgb(30, 30, 30)
537}
538fn default_popup_selection_bg() -> ColorDef {
539 ColorDef::Rgb(58, 79, 120)
540}
541fn default_popup_text_fg() -> ColorDef {
542 ColorDef::Named("White".to_string())
543}
544
545fn default_suggestion_bg() -> ColorDef {
547 ColorDef::Rgb(30, 30, 30)
548}
549fn default_suggestion_selected_bg() -> ColorDef {
550 ColorDef::Rgb(58, 79, 120)
551}
552
553fn default_help_bg() -> ColorDef {
555 ColorDef::Named("Black".to_string())
556}
557fn default_help_fg() -> ColorDef {
558 ColorDef::Named("White".to_string())
559}
560fn default_help_key_fg() -> ColorDef {
561 ColorDef::Named("Cyan".to_string())
562}
563fn default_help_separator_fg() -> ColorDef {
564 ColorDef::Named("DarkGray".to_string())
565}
566fn default_help_indicator_fg() -> ColorDef {
567 ColorDef::Named("Red".to_string())
568}
569fn default_help_indicator_bg() -> ColorDef {
570 ColorDef::Named("Black".to_string())
571}
572
573fn default_inline_code_bg() -> ColorDef {
574 ColorDef::Named("DarkGray".to_string())
575}
576
577fn default_split_separator_fg() -> ColorDef {
579 ColorDef::Rgb(100, 100, 100)
580}
581fn default_split_separator_hover_fg() -> ColorDef {
582 ColorDef::Rgb(100, 149, 237) }
584fn default_scrollbar_track_fg() -> ColorDef {
585 ColorDef::Named("DarkGray".to_string())
586}
587fn default_scrollbar_thumb_fg() -> ColorDef {
588 ColorDef::Named("Gray".to_string())
589}
590fn default_scrollbar_track_hover_fg() -> ColorDef {
591 ColorDef::Named("Gray".to_string())
592}
593fn default_scrollbar_thumb_hover_fg() -> ColorDef {
594 ColorDef::Named("White".to_string())
595}
596fn default_compose_margin_bg() -> ColorDef {
597 ColorDef::Rgb(18, 18, 18) }
599fn default_semantic_highlight_bg() -> ColorDef {
600 ColorDef::Rgb(60, 60, 80) }
602fn default_terminal_bg() -> ColorDef {
603 ColorDef::Named("Default".to_string()) }
605fn default_terminal_fg() -> ColorDef {
606 ColorDef::Named("Default".to_string()) }
608fn default_status_warning_indicator_bg() -> ColorDef {
609 ColorDef::Rgb(181, 137, 0) }
611fn default_status_warning_indicator_fg() -> ColorDef {
612 ColorDef::Rgb(0, 0, 0) }
614fn default_status_error_indicator_bg() -> ColorDef {
615 ColorDef::Rgb(220, 50, 47) }
617fn default_status_error_indicator_fg() -> ColorDef {
618 ColorDef::Rgb(255, 255, 255) }
620fn default_status_warning_indicator_hover_bg() -> ColorDef {
621 ColorDef::Rgb(211, 167, 30) }
623fn default_status_warning_indicator_hover_fg() -> ColorDef {
624 ColorDef::Rgb(0, 0, 0) }
626fn default_status_error_indicator_hover_bg() -> ColorDef {
627 ColorDef::Rgb(250, 80, 77) }
629fn default_status_error_indicator_hover_fg() -> ColorDef {
630 ColorDef::Rgb(255, 255, 255) }
632fn default_tab_drop_zone_bg() -> ColorDef {
633 ColorDef::Rgb(70, 130, 180) }
635fn default_tab_drop_zone_border() -> ColorDef {
636 ColorDef::Rgb(100, 149, 237) }
638
639#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
641pub struct SearchColors {
642 #[serde(default = "default_search_match_bg")]
644 pub match_bg: ColorDef,
645 #[serde(default = "default_search_match_fg")]
647 pub match_fg: ColorDef,
648}
649
650fn default_search_match_bg() -> ColorDef {
652 ColorDef::Rgb(100, 100, 20)
653}
654fn default_search_match_fg() -> ColorDef {
655 ColorDef::Rgb(255, 255, 255)
656}
657
658#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
660pub struct DiagnosticColors {
661 #[serde(default = "default_diagnostic_error_fg")]
663 pub error_fg: ColorDef,
664 #[serde(default = "default_diagnostic_error_bg")]
666 pub error_bg: ColorDef,
667 #[serde(default = "default_diagnostic_warning_fg")]
669 pub warning_fg: ColorDef,
670 #[serde(default = "default_diagnostic_warning_bg")]
672 pub warning_bg: ColorDef,
673 #[serde(default = "default_diagnostic_info_fg")]
675 pub info_fg: ColorDef,
676 #[serde(default = "default_diagnostic_info_bg")]
678 pub info_bg: ColorDef,
679 #[serde(default = "default_diagnostic_hint_fg")]
681 pub hint_fg: ColorDef,
682 #[serde(default = "default_diagnostic_hint_bg")]
684 pub hint_bg: ColorDef,
685}
686
687fn default_diagnostic_error_fg() -> ColorDef {
689 ColorDef::Named("Red".to_string())
690}
691fn default_diagnostic_error_bg() -> ColorDef {
692 ColorDef::Rgb(60, 20, 20)
693}
694fn default_diagnostic_warning_fg() -> ColorDef {
695 ColorDef::Named("Yellow".to_string())
696}
697fn default_diagnostic_warning_bg() -> ColorDef {
698 ColorDef::Rgb(60, 50, 0)
699}
700fn default_diagnostic_info_fg() -> ColorDef {
701 ColorDef::Named("Blue".to_string())
702}
703fn default_diagnostic_info_bg() -> ColorDef {
704 ColorDef::Rgb(0, 30, 60)
705}
706fn default_diagnostic_hint_fg() -> ColorDef {
707 ColorDef::Named("Gray".to_string())
708}
709fn default_diagnostic_hint_bg() -> ColorDef {
710 ColorDef::Rgb(30, 30, 30)
711}
712
713#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
715pub struct SyntaxColors {
716 #[serde(default = "default_syntax_keyword")]
718 pub keyword: ColorDef,
719 #[serde(default = "default_syntax_string")]
721 pub string: ColorDef,
722 #[serde(default = "default_syntax_comment")]
724 pub comment: ColorDef,
725 #[serde(default = "default_syntax_function")]
727 pub function: ColorDef,
728 #[serde(rename = "type", default = "default_syntax_type")]
730 pub type_: ColorDef,
731 #[serde(default = "default_syntax_variable")]
733 pub variable: ColorDef,
734 #[serde(default = "default_syntax_constant")]
736 pub constant: ColorDef,
737 #[serde(default = "default_syntax_operator")]
739 pub operator: ColorDef,
740}
741
742fn default_syntax_keyword() -> ColorDef {
744 ColorDef::Rgb(86, 156, 214)
745}
746fn default_syntax_string() -> ColorDef {
747 ColorDef::Rgb(206, 145, 120)
748}
749fn default_syntax_comment() -> ColorDef {
750 ColorDef::Rgb(106, 153, 85)
751}
752fn default_syntax_function() -> ColorDef {
753 ColorDef::Rgb(220, 220, 170)
754}
755fn default_syntax_type() -> ColorDef {
756 ColorDef::Rgb(78, 201, 176)
757}
758fn default_syntax_variable() -> ColorDef {
759 ColorDef::Rgb(156, 220, 254)
760}
761fn default_syntax_constant() -> ColorDef {
762 ColorDef::Rgb(79, 193, 255)
763}
764fn default_syntax_operator() -> ColorDef {
765 ColorDef::Rgb(212, 212, 212)
766}
767
768#[derive(Debug, Clone)]
770pub struct Theme {
771 pub name: String,
773
774 pub editor_bg: Color,
776 pub editor_fg: Color,
777 pub cursor: Color,
778 pub inactive_cursor: Color,
779 pub selection_bg: Color,
780 pub current_line_bg: Color,
781 pub line_number_fg: Color,
782 pub line_number_bg: Color,
783
784 pub diff_add_bg: Color,
786 pub diff_remove_bg: Color,
787 pub diff_modify_bg: Color,
788 pub diff_add_highlight_bg: Color,
790 pub diff_remove_highlight_bg: Color,
792
793 pub tab_active_fg: Color,
795 pub tab_active_bg: Color,
796 pub tab_inactive_fg: Color,
797 pub tab_inactive_bg: Color,
798 pub tab_separator_bg: Color,
799 pub tab_close_hover_fg: Color,
800 pub tab_hover_bg: Color,
801
802 pub menu_bg: Color,
804 pub menu_fg: Color,
805 pub menu_active_bg: Color,
806 pub menu_active_fg: Color,
807 pub menu_dropdown_bg: Color,
808 pub menu_dropdown_fg: Color,
809 pub menu_highlight_bg: Color,
810 pub menu_highlight_fg: Color,
811 pub menu_border_fg: Color,
812 pub menu_separator_fg: Color,
813 pub menu_hover_bg: Color,
814 pub menu_hover_fg: Color,
815 pub menu_disabled_fg: Color,
816 pub menu_disabled_bg: Color,
817
818 pub status_bar_fg: Color,
819 pub status_bar_bg: Color,
820 pub prompt_fg: Color,
821 pub prompt_bg: Color,
822 pub prompt_selection_fg: Color,
823 pub prompt_selection_bg: Color,
824
825 pub popup_border_fg: Color,
826 pub popup_bg: Color,
827 pub popup_selection_bg: Color,
828 pub popup_text_fg: Color,
829
830 pub suggestion_bg: Color,
831 pub suggestion_selected_bg: Color,
832
833 pub help_bg: Color,
834 pub help_fg: Color,
835 pub help_key_fg: Color,
836 pub help_separator_fg: Color,
837
838 pub help_indicator_fg: Color,
839 pub help_indicator_bg: Color,
840
841 pub inline_code_bg: Color,
843
844 pub split_separator_fg: Color,
845 pub split_separator_hover_fg: Color,
846
847 pub scrollbar_track_fg: Color,
849 pub scrollbar_thumb_fg: Color,
850 pub scrollbar_track_hover_fg: Color,
851 pub scrollbar_thumb_hover_fg: Color,
852
853 pub compose_margin_bg: Color,
855
856 pub semantic_highlight_bg: Color,
858
859 pub terminal_bg: Color,
861 pub terminal_fg: Color,
862
863 pub status_warning_indicator_bg: Color,
865 pub status_warning_indicator_fg: Color,
866 pub status_error_indicator_bg: Color,
867 pub status_error_indicator_fg: Color,
868 pub status_warning_indicator_hover_bg: Color,
869 pub status_warning_indicator_hover_fg: Color,
870 pub status_error_indicator_hover_bg: Color,
871 pub status_error_indicator_hover_fg: Color,
872
873 pub tab_drop_zone_bg: Color,
875 pub tab_drop_zone_border: Color,
876
877 pub search_match_bg: Color,
879 pub search_match_fg: Color,
880
881 pub diagnostic_error_fg: Color,
883 pub diagnostic_error_bg: Color,
884 pub diagnostic_warning_fg: Color,
885 pub diagnostic_warning_bg: Color,
886 pub diagnostic_info_fg: Color,
887 pub diagnostic_info_bg: Color,
888 pub diagnostic_hint_fg: Color,
889 pub diagnostic_hint_bg: Color,
890
891 pub syntax_keyword: Color,
893 pub syntax_string: Color,
894 pub syntax_comment: Color,
895 pub syntax_function: Color,
896 pub syntax_type: Color,
897 pub syntax_variable: Color,
898 pub syntax_constant: Color,
899 pub syntax_operator: Color,
900}
901
902impl From<ThemeFile> for Theme {
903 fn from(file: ThemeFile) -> Self {
904 Self {
905 name: file.name,
906 editor_bg: file.editor.bg.into(),
907 editor_fg: file.editor.fg.into(),
908 cursor: file.editor.cursor.into(),
909 inactive_cursor: file.editor.inactive_cursor.into(),
910 selection_bg: file.editor.selection_bg.into(),
911 current_line_bg: file.editor.current_line_bg.into(),
912 line_number_fg: file.editor.line_number_fg.into(),
913 line_number_bg: file.editor.line_number_bg.into(),
914 diff_add_bg: file.editor.diff_add_bg.clone().into(),
915 diff_remove_bg: file.editor.diff_remove_bg.clone().into(),
916 diff_modify_bg: file.editor.diff_modify_bg.into(),
917 diff_add_highlight_bg: brighten_color(file.editor.diff_add_bg.into(), 40),
919 diff_remove_highlight_bg: brighten_color(file.editor.diff_remove_bg.into(), 40),
920 tab_active_fg: file.ui.tab_active_fg.into(),
921 tab_active_bg: file.ui.tab_active_bg.into(),
922 tab_inactive_fg: file.ui.tab_inactive_fg.into(),
923 tab_inactive_bg: file.ui.tab_inactive_bg.into(),
924 tab_separator_bg: file.ui.tab_separator_bg.into(),
925 tab_close_hover_fg: file.ui.tab_close_hover_fg.into(),
926 tab_hover_bg: file.ui.tab_hover_bg.into(),
927 menu_bg: file.ui.menu_bg.into(),
928 menu_fg: file.ui.menu_fg.into(),
929 menu_active_bg: file.ui.menu_active_bg.into(),
930 menu_active_fg: file.ui.menu_active_fg.into(),
931 menu_dropdown_bg: file.ui.menu_dropdown_bg.into(),
932 menu_dropdown_fg: file.ui.menu_dropdown_fg.into(),
933 menu_highlight_bg: file.ui.menu_highlight_bg.into(),
934 menu_highlight_fg: file.ui.menu_highlight_fg.into(),
935 menu_border_fg: file.ui.menu_border_fg.into(),
936 menu_separator_fg: file.ui.menu_separator_fg.into(),
937 menu_hover_bg: file.ui.menu_hover_bg.into(),
938 menu_hover_fg: file.ui.menu_hover_fg.into(),
939 menu_disabled_fg: file.ui.menu_disabled_fg.into(),
940 menu_disabled_bg: file.ui.menu_disabled_bg.into(),
941 status_bar_fg: file.ui.status_bar_fg.into(),
942 status_bar_bg: file.ui.status_bar_bg.into(),
943 prompt_fg: file.ui.prompt_fg.into(),
944 prompt_bg: file.ui.prompt_bg.into(),
945 prompt_selection_fg: file.ui.prompt_selection_fg.into(),
946 prompt_selection_bg: file.ui.prompt_selection_bg.into(),
947 popup_border_fg: file.ui.popup_border_fg.into(),
948 popup_bg: file.ui.popup_bg.into(),
949 popup_selection_bg: file.ui.popup_selection_bg.into(),
950 popup_text_fg: file.ui.popup_text_fg.into(),
951 suggestion_bg: file.ui.suggestion_bg.into(),
952 suggestion_selected_bg: file.ui.suggestion_selected_bg.into(),
953 help_bg: file.ui.help_bg.into(),
954 help_fg: file.ui.help_fg.into(),
955 help_key_fg: file.ui.help_key_fg.into(),
956 help_separator_fg: file.ui.help_separator_fg.into(),
957 help_indicator_fg: file.ui.help_indicator_fg.into(),
958 help_indicator_bg: file.ui.help_indicator_bg.into(),
959 inline_code_bg: file.ui.inline_code_bg.into(),
960 split_separator_fg: file.ui.split_separator_fg.into(),
961 split_separator_hover_fg: file.ui.split_separator_hover_fg.into(),
962 scrollbar_track_fg: file.ui.scrollbar_track_fg.into(),
963 scrollbar_thumb_fg: file.ui.scrollbar_thumb_fg.into(),
964 scrollbar_track_hover_fg: file.ui.scrollbar_track_hover_fg.into(),
965 scrollbar_thumb_hover_fg: file.ui.scrollbar_thumb_hover_fg.into(),
966 compose_margin_bg: file.ui.compose_margin_bg.into(),
967 semantic_highlight_bg: file.ui.semantic_highlight_bg.into(),
968 terminal_bg: file.ui.terminal_bg.into(),
969 terminal_fg: file.ui.terminal_fg.into(),
970 status_warning_indicator_bg: file.ui.status_warning_indicator_bg.into(),
971 status_warning_indicator_fg: file.ui.status_warning_indicator_fg.into(),
972 status_error_indicator_bg: file.ui.status_error_indicator_bg.into(),
973 status_error_indicator_fg: file.ui.status_error_indicator_fg.into(),
974 status_warning_indicator_hover_bg: file.ui.status_warning_indicator_hover_bg.into(),
975 status_warning_indicator_hover_fg: file.ui.status_warning_indicator_hover_fg.into(),
976 status_error_indicator_hover_bg: file.ui.status_error_indicator_hover_bg.into(),
977 status_error_indicator_hover_fg: file.ui.status_error_indicator_hover_fg.into(),
978 tab_drop_zone_bg: file.ui.tab_drop_zone_bg.into(),
979 tab_drop_zone_border: file.ui.tab_drop_zone_border.into(),
980 search_match_bg: file.search.match_bg.into(),
981 search_match_fg: file.search.match_fg.into(),
982 diagnostic_error_fg: file.diagnostic.error_fg.into(),
983 diagnostic_error_bg: file.diagnostic.error_bg.into(),
984 diagnostic_warning_fg: file.diagnostic.warning_fg.into(),
985 diagnostic_warning_bg: file.diagnostic.warning_bg.into(),
986 diagnostic_info_fg: file.diagnostic.info_fg.into(),
987 diagnostic_info_bg: file.diagnostic.info_bg.into(),
988 diagnostic_hint_fg: file.diagnostic.hint_fg.into(),
989 diagnostic_hint_bg: file.diagnostic.hint_bg.into(),
990 syntax_keyword: file.syntax.keyword.into(),
991 syntax_string: file.syntax.string.into(),
992 syntax_comment: file.syntax.comment.into(),
993 syntax_function: file.syntax.function.into(),
994 syntax_type: file.syntax.type_.into(),
995 syntax_variable: file.syntax.variable.into(),
996 syntax_constant: file.syntax.constant.into(),
997 syntax_operator: file.syntax.operator.into(),
998 }
999 }
1000}
1001
1002impl From<Theme> for ThemeFile {
1003 fn from(theme: Theme) -> Self {
1004 Self {
1005 name: theme.name,
1006 editor: EditorColors {
1007 bg: theme.editor_bg.into(),
1008 fg: theme.editor_fg.into(),
1009 cursor: theme.cursor.into(),
1010 inactive_cursor: theme.inactive_cursor.into(),
1011 selection_bg: theme.selection_bg.into(),
1012 current_line_bg: theme.current_line_bg.into(),
1013 line_number_fg: theme.line_number_fg.into(),
1014 line_number_bg: theme.line_number_bg.into(),
1015 diff_add_bg: theme.diff_add_bg.into(),
1016 diff_remove_bg: theme.diff_remove_bg.into(),
1017 diff_modify_bg: theme.diff_modify_bg.into(),
1018 },
1019 ui: UiColors {
1020 tab_active_fg: theme.tab_active_fg.into(),
1021 tab_active_bg: theme.tab_active_bg.into(),
1022 tab_inactive_fg: theme.tab_inactive_fg.into(),
1023 tab_inactive_bg: theme.tab_inactive_bg.into(),
1024 tab_separator_bg: theme.tab_separator_bg.into(),
1025 tab_close_hover_fg: theme.tab_close_hover_fg.into(),
1026 tab_hover_bg: theme.tab_hover_bg.into(),
1027 menu_bg: theme.menu_bg.into(),
1028 menu_fg: theme.menu_fg.into(),
1029 menu_active_bg: theme.menu_active_bg.into(),
1030 menu_active_fg: theme.menu_active_fg.into(),
1031 menu_dropdown_bg: theme.menu_dropdown_bg.into(),
1032 menu_dropdown_fg: theme.menu_dropdown_fg.into(),
1033 menu_highlight_bg: theme.menu_highlight_bg.into(),
1034 menu_highlight_fg: theme.menu_highlight_fg.into(),
1035 menu_border_fg: theme.menu_border_fg.into(),
1036 menu_separator_fg: theme.menu_separator_fg.into(),
1037 menu_hover_bg: theme.menu_hover_bg.into(),
1038 menu_hover_fg: theme.menu_hover_fg.into(),
1039 menu_disabled_fg: theme.menu_disabled_fg.into(),
1040 menu_disabled_bg: theme.menu_disabled_bg.into(),
1041 status_bar_fg: theme.status_bar_fg.into(),
1042 status_bar_bg: theme.status_bar_bg.into(),
1043 prompt_fg: theme.prompt_fg.into(),
1044 prompt_bg: theme.prompt_bg.into(),
1045 prompt_selection_fg: theme.prompt_selection_fg.into(),
1046 prompt_selection_bg: theme.prompt_selection_bg.into(),
1047 popup_border_fg: theme.popup_border_fg.into(),
1048 popup_bg: theme.popup_bg.into(),
1049 popup_selection_bg: theme.popup_selection_bg.into(),
1050 popup_text_fg: theme.popup_text_fg.into(),
1051 suggestion_bg: theme.suggestion_bg.into(),
1052 suggestion_selected_bg: theme.suggestion_selected_bg.into(),
1053 help_bg: theme.help_bg.into(),
1054 help_fg: theme.help_fg.into(),
1055 help_key_fg: theme.help_key_fg.into(),
1056 help_separator_fg: theme.help_separator_fg.into(),
1057 help_indicator_fg: theme.help_indicator_fg.into(),
1058 help_indicator_bg: theme.help_indicator_bg.into(),
1059 inline_code_bg: theme.inline_code_bg.into(),
1060 split_separator_fg: theme.split_separator_fg.into(),
1061 split_separator_hover_fg: theme.split_separator_hover_fg.into(),
1062 scrollbar_track_fg: theme.scrollbar_track_fg.into(),
1063 scrollbar_thumb_fg: theme.scrollbar_thumb_fg.into(),
1064 scrollbar_track_hover_fg: theme.scrollbar_track_hover_fg.into(),
1065 scrollbar_thumb_hover_fg: theme.scrollbar_thumb_hover_fg.into(),
1066 compose_margin_bg: theme.compose_margin_bg.into(),
1067 semantic_highlight_bg: theme.semantic_highlight_bg.into(),
1068 terminal_bg: theme.terminal_bg.into(),
1069 terminal_fg: theme.terminal_fg.into(),
1070 status_warning_indicator_bg: theme.status_warning_indicator_bg.into(),
1071 status_warning_indicator_fg: theme.status_warning_indicator_fg.into(),
1072 status_error_indicator_bg: theme.status_error_indicator_bg.into(),
1073 status_error_indicator_fg: theme.status_error_indicator_fg.into(),
1074 status_warning_indicator_hover_bg: theme.status_warning_indicator_hover_bg.into(),
1075 status_warning_indicator_hover_fg: theme.status_warning_indicator_hover_fg.into(),
1076 status_error_indicator_hover_bg: theme.status_error_indicator_hover_bg.into(),
1077 status_error_indicator_hover_fg: theme.status_error_indicator_hover_fg.into(),
1078 tab_drop_zone_bg: theme.tab_drop_zone_bg.into(),
1079 tab_drop_zone_border: theme.tab_drop_zone_border.into(),
1080 },
1081 search: SearchColors {
1082 match_bg: theme.search_match_bg.into(),
1083 match_fg: theme.search_match_fg.into(),
1084 },
1085 diagnostic: DiagnosticColors {
1086 error_fg: theme.diagnostic_error_fg.into(),
1087 error_bg: theme.diagnostic_error_bg.into(),
1088 warning_fg: theme.diagnostic_warning_fg.into(),
1089 warning_bg: theme.diagnostic_warning_bg.into(),
1090 info_fg: theme.diagnostic_info_fg.into(),
1091 info_bg: theme.diagnostic_info_bg.into(),
1092 hint_fg: theme.diagnostic_hint_fg.into(),
1093 hint_bg: theme.diagnostic_hint_bg.into(),
1094 },
1095 syntax: SyntaxColors {
1096 keyword: theme.syntax_keyword.into(),
1097 string: theme.syntax_string.into(),
1098 comment: theme.syntax_comment.into(),
1099 function: theme.syntax_function.into(),
1100 type_: theme.syntax_type.into(),
1101 variable: theme.syntax_variable.into(),
1102 constant: theme.syntax_constant.into(),
1103 operator: theme.syntax_operator.into(),
1104 },
1105 }
1106 }
1107}
1108
1109impl Theme {
1110 pub fn load_builtin(name: &str) -> Option<Self> {
1112 BUILTIN_THEMES
1113 .iter()
1114 .find(|t| t.name == name)
1115 .and_then(|t| serde_json::from_str::<ThemeFile>(t.json).ok())
1116 .map(|tf| tf.into())
1117 }
1118
1119 pub fn from_json(json: &str) -> Result<Self, String> {
1121 let theme_file: ThemeFile =
1122 serde_json::from_str(json).map_err(|e| format!("Failed to parse theme JSON: {}", e))?;
1123 Ok(theme_file.into())
1124 }
1125}
1126
1127pub fn get_theme_schema() -> serde_json::Value {
1135 use schemars::schema_for;
1136 let schema = schema_for!(ThemeFile);
1137 serde_json::to_value(&schema).unwrap_or_default()
1138}
1139
1140pub fn get_builtin_themes() -> serde_json::Value {
1142 let mut map = serde_json::Map::new();
1143 for theme in BUILTIN_THEMES {
1144 map.insert(
1145 theme.name.to_string(),
1146 serde_json::Value::String(theme.json.to_string()),
1147 );
1148 }
1149 serde_json::Value::Object(map)
1150}
1151
1152#[cfg(test)]
1153mod tests {
1154 use super::*;
1155
1156 #[test]
1157 fn test_load_builtin_theme() {
1158 let dark = Theme::load_builtin(THEME_DARK).expect("Dark theme must exist");
1159 assert_eq!(dark.name, THEME_DARK);
1160
1161 let light = Theme::load_builtin(THEME_LIGHT).expect("Light theme must exist");
1162 assert_eq!(light.name, THEME_LIGHT);
1163
1164 let high_contrast =
1165 Theme::load_builtin(THEME_HIGH_CONTRAST).expect("High contrast theme must exist");
1166 assert_eq!(high_contrast.name, THEME_HIGH_CONTRAST);
1167 }
1168
1169 #[test]
1170 fn test_builtin_themes_match_schema() {
1171 for theme in BUILTIN_THEMES {
1172 let _: ThemeFile = serde_json::from_str(theme.json)
1173 .unwrap_or_else(|_| panic!("Theme '{}' does not match schema", theme.name));
1174 }
1175 }
1176
1177 #[test]
1178 fn test_from_json() {
1179 let json = r#"{"name":"test","editor":{},"ui":{},"search":{},"diagnostic":{},"syntax":{}}"#;
1180 let theme = Theme::from_json(json).expect("Should parse minimal theme");
1181 assert_eq!(theme.name, "test");
1182 }
1183
1184 #[test]
1185 fn test_default_reset_color() {
1186 let color: Color = ColorDef::Named("Default".to_string()).into();
1188 assert_eq!(color, Color::Reset);
1189
1190 let color: Color = ColorDef::Named("Reset".to_string()).into();
1192 assert_eq!(color, Color::Reset);
1193 }
1194}