Skip to main content

fresh/view/theme/
types.rs

1//! Pure theme types without I/O operations.
2//!
3//! This module contains all theme-related data structures that can be used
4//! without filesystem access. This enables WASM compatibility and easier testing.
5
6use 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
18/// A builtin theme with its name, pack, and embedded JSON content.
19pub struct BuiltinTheme {
20    pub name: &'static str,
21    /// Pack name (subdirectory path, empty for root themes)
22    pub pack: &'static str,
23    pub json: &'static str,
24}
25
26// Include the auto-generated BUILTIN_THEMES array from build.rs
27include!(concat!(env!("OUT_DIR"), "/builtin_themes.rs"));
28
29/// Information about an available theme.
30#[derive(Debug, Clone, PartialEq, Eq)]
31pub struct ThemeInfo {
32    /// Theme name (e.g., "dark", "nord")
33    pub name: String,
34    /// Pack name (subdirectory path, empty for root themes)
35    pub pack: String,
36}
37
38impl ThemeInfo {
39    /// Create a new ThemeInfo
40    pub fn new(name: impl Into<String>, pack: impl Into<String>) -> Self {
41        Self {
42            name: name.into(),
43            pack: pack.into(),
44        }
45    }
46
47    /// Get display name showing pack if present
48    pub fn display_name(&self) -> String {
49        if self.pack.is_empty() {
50            self.name.clone()
51        } else {
52            format!("{} ({})", self.name, self.pack)
53        }
54    }
55}
56
57/// Convert a ratatui Color to RGB values.
58/// Returns None for Reset or Indexed colors.
59pub fn color_to_rgb(color: Color) -> Option<(u8, u8, u8)> {
60    match color {
61        Color::Rgb(r, g, b) => Some((r, g, b)),
62        Color::White => Some((255, 255, 255)),
63        Color::Black => Some((0, 0, 0)),
64        Color::Red => Some((205, 0, 0)),
65        Color::Green => Some((0, 205, 0)),
66        Color::Blue => Some((0, 0, 238)),
67        Color::Yellow => Some((205, 205, 0)),
68        Color::Magenta => Some((205, 0, 205)),
69        Color::Cyan => Some((0, 205, 205)),
70        Color::Gray => Some((229, 229, 229)),
71        Color::DarkGray => Some((127, 127, 127)),
72        Color::LightRed => Some((255, 0, 0)),
73        Color::LightGreen => Some((0, 255, 0)),
74        Color::LightBlue => Some((92, 92, 255)),
75        Color::LightYellow => Some((255, 255, 0)),
76        Color::LightMagenta => Some((255, 0, 255)),
77        Color::LightCyan => Some((0, 255, 255)),
78        Color::Reset | Color::Indexed(_) => None,
79    }
80}
81
82/// Brighten a color by adding an amount to each RGB component.
83/// Clamps values to 255.
84pub fn brighten_color(color: Color, amount: u8) -> Color {
85    if let Some((r, g, b)) = color_to_rgb(color) {
86        Color::Rgb(
87            r.saturating_add(amount),
88            g.saturating_add(amount),
89            b.saturating_add(amount),
90        )
91    } else {
92        color
93    }
94}
95
96/// Serializable color representation
97#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
98#[serde(untagged)]
99pub enum ColorDef {
100    /// RGB color as [r, g, b]
101    Rgb(u8, u8, u8),
102    /// Named color
103    Named(String),
104}
105
106impl From<ColorDef> for Color {
107    fn from(def: ColorDef) -> Self {
108        match def {
109            ColorDef::Rgb(r, g, b) => Color::Rgb(r, g, b),
110            ColorDef::Named(name) => match name.as_str() {
111                "Black" => Color::Black,
112                "Red" => Color::Red,
113                "Green" => Color::Green,
114                "Yellow" => Color::Yellow,
115                "Blue" => Color::Blue,
116                "Magenta" => Color::Magenta,
117                "Cyan" => Color::Cyan,
118                "Gray" => Color::Gray,
119                "DarkGray" => Color::DarkGray,
120                "LightRed" => Color::LightRed,
121                "LightGreen" => Color::LightGreen,
122                "LightYellow" => Color::LightYellow,
123                "LightBlue" => Color::LightBlue,
124                "LightMagenta" => Color::LightMagenta,
125                "LightCyan" => Color::LightCyan,
126                "White" => Color::White,
127                // Default/Reset uses the terminal's default color (preserves transparency)
128                "Default" | "Reset" => Color::Reset,
129                _ => Color::White, // Default fallback
130            },
131        }
132    }
133}
134
135impl From<Color> for ColorDef {
136    fn from(color: Color) -> Self {
137        match color {
138            Color::Rgb(r, g, b) => ColorDef::Rgb(r, g, b),
139            Color::White => ColorDef::Named("White".to_string()),
140            Color::Black => ColorDef::Named("Black".to_string()),
141            Color::Red => ColorDef::Named("Red".to_string()),
142            Color::Green => ColorDef::Named("Green".to_string()),
143            Color::Blue => ColorDef::Named("Blue".to_string()),
144            Color::Yellow => ColorDef::Named("Yellow".to_string()),
145            Color::Magenta => ColorDef::Named("Magenta".to_string()),
146            Color::Cyan => ColorDef::Named("Cyan".to_string()),
147            Color::Gray => ColorDef::Named("Gray".to_string()),
148            Color::DarkGray => ColorDef::Named("DarkGray".to_string()),
149            Color::LightRed => ColorDef::Named("LightRed".to_string()),
150            Color::LightGreen => ColorDef::Named("LightGreen".to_string()),
151            Color::LightBlue => ColorDef::Named("LightBlue".to_string()),
152            Color::LightYellow => ColorDef::Named("LightYellow".to_string()),
153            Color::LightMagenta => ColorDef::Named("LightMagenta".to_string()),
154            Color::LightCyan => ColorDef::Named("LightCyan".to_string()),
155            Color::Reset => ColorDef::Named("Default".to_string()),
156            Color::Indexed(_) => {
157                // Fallback for indexed colors
158                if let Some((r, g, b)) = color_to_rgb(color) {
159                    ColorDef::Rgb(r, g, b)
160                } else {
161                    ColorDef::Named("Default".to_string())
162                }
163            }
164        }
165    }
166}
167
168/// Serializable theme definition (matches JSON structure)
169#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
170pub struct ThemeFile {
171    /// Theme name
172    pub name: String,
173    /// Editor area colors
174    pub editor: EditorColors,
175    /// UI element colors (tabs, menus, status bar, etc.)
176    pub ui: UiColors,
177    /// Search result highlighting colors
178    pub search: SearchColors,
179    /// LSP diagnostic colors (errors, warnings, etc.)
180    pub diagnostic: DiagnosticColors,
181    /// Syntax highlighting colors
182    pub syntax: SyntaxColors,
183}
184
185/// Editor area colors
186#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
187pub struct EditorColors {
188    /// Editor background color
189    #[serde(default = "default_editor_bg")]
190    pub bg: ColorDef,
191    /// Default text color
192    #[serde(default = "default_editor_fg")]
193    pub fg: ColorDef,
194    /// Cursor color
195    #[serde(default = "default_cursor")]
196    pub cursor: ColorDef,
197    /// Cursor color in unfocused splits
198    #[serde(default = "default_inactive_cursor")]
199    pub inactive_cursor: ColorDef,
200    /// Selected text background
201    #[serde(default = "default_selection_bg")]
202    pub selection_bg: ColorDef,
203    /// Background of the line containing cursor
204    #[serde(default = "default_current_line_bg")]
205    pub current_line_bg: ColorDef,
206    /// Line number text color
207    #[serde(default = "default_line_number_fg")]
208    pub line_number_fg: ColorDef,
209    /// Line number gutter background
210    #[serde(default = "default_line_number_bg")]
211    pub line_number_bg: ColorDef,
212    /// Diff added line background
213    #[serde(default = "default_diff_add_bg")]
214    pub diff_add_bg: ColorDef,
215    /// Diff removed line background
216    #[serde(default = "default_diff_remove_bg")]
217    pub diff_remove_bg: ColorDef,
218    /// Diff modified line background
219    #[serde(default = "default_diff_modify_bg")]
220    pub diff_modify_bg: ColorDef,
221}
222
223// Default editor colors (for minimal themes)
224fn default_editor_bg() -> ColorDef {
225    ColorDef::Rgb(30, 30, 30)
226}
227fn default_editor_fg() -> ColorDef {
228    ColorDef::Rgb(212, 212, 212)
229}
230fn default_cursor() -> ColorDef {
231    ColorDef::Rgb(255, 255, 255)
232}
233fn default_inactive_cursor() -> ColorDef {
234    ColorDef::Named("DarkGray".to_string())
235}
236fn default_selection_bg() -> ColorDef {
237    ColorDef::Rgb(38, 79, 120)
238}
239fn default_current_line_bg() -> ColorDef {
240    ColorDef::Rgb(40, 40, 40)
241}
242fn default_line_number_fg() -> ColorDef {
243    ColorDef::Rgb(100, 100, 100)
244}
245fn default_line_number_bg() -> ColorDef {
246    ColorDef::Rgb(30, 30, 30)
247}
248fn default_diff_add_bg() -> ColorDef {
249    ColorDef::Rgb(35, 60, 35) // Dark green
250}
251fn default_diff_remove_bg() -> ColorDef {
252    ColorDef::Rgb(70, 35, 35) // Dark red
253}
254fn default_diff_modify_bg() -> ColorDef {
255    ColorDef::Rgb(40, 38, 30) // Very subtle yellow tint, close to dark bg
256}
257
258/// UI element colors (tabs, menus, status bar, etc.)
259#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
260pub struct UiColors {
261    /// Active tab text color
262    #[serde(default = "default_tab_active_fg")]
263    pub tab_active_fg: ColorDef,
264    /// Active tab background color
265    #[serde(default = "default_tab_active_bg")]
266    pub tab_active_bg: ColorDef,
267    /// Inactive tab text color
268    #[serde(default = "default_tab_inactive_fg")]
269    pub tab_inactive_fg: ColorDef,
270    /// Inactive tab background color
271    #[serde(default = "default_tab_inactive_bg")]
272    pub tab_inactive_bg: ColorDef,
273    /// Tab bar separator color
274    #[serde(default = "default_tab_separator_bg")]
275    pub tab_separator_bg: ColorDef,
276    /// Tab close button hover color
277    #[serde(default = "default_tab_close_hover_fg")]
278    pub tab_close_hover_fg: ColorDef,
279    /// Tab hover background color
280    #[serde(default = "default_tab_hover_bg")]
281    pub tab_hover_bg: ColorDef,
282    /// Menu bar background
283    #[serde(default = "default_menu_bg")]
284    pub menu_bg: ColorDef,
285    /// Menu bar text color
286    #[serde(default = "default_menu_fg")]
287    pub menu_fg: ColorDef,
288    /// Active menu item background
289    #[serde(default = "default_menu_active_bg")]
290    pub menu_active_bg: ColorDef,
291    /// Active menu item text color
292    #[serde(default = "default_menu_active_fg")]
293    pub menu_active_fg: ColorDef,
294    /// Dropdown menu background
295    #[serde(default = "default_menu_dropdown_bg")]
296    pub menu_dropdown_bg: ColorDef,
297    /// Dropdown menu text color
298    #[serde(default = "default_menu_dropdown_fg")]
299    pub menu_dropdown_fg: ColorDef,
300    /// Highlighted menu item background
301    #[serde(default = "default_menu_highlight_bg")]
302    pub menu_highlight_bg: ColorDef,
303    /// Highlighted menu item text color
304    #[serde(default = "default_menu_highlight_fg")]
305    pub menu_highlight_fg: ColorDef,
306    /// Menu border color
307    #[serde(default = "default_menu_border_fg")]
308    pub menu_border_fg: ColorDef,
309    /// Menu separator line color
310    #[serde(default = "default_menu_separator_fg")]
311    pub menu_separator_fg: ColorDef,
312    /// Menu item hover background
313    #[serde(default = "default_menu_hover_bg")]
314    pub menu_hover_bg: ColorDef,
315    /// Menu item hover text color
316    #[serde(default = "default_menu_hover_fg")]
317    pub menu_hover_fg: ColorDef,
318    /// Disabled menu item text color
319    #[serde(default = "default_menu_disabled_fg")]
320    pub menu_disabled_fg: ColorDef,
321    /// Disabled menu item background
322    #[serde(default = "default_menu_disabled_bg")]
323    pub menu_disabled_bg: ColorDef,
324    /// Status bar text color
325    #[serde(default = "default_status_bar_fg")]
326    pub status_bar_fg: ColorDef,
327    /// Status bar background color
328    #[serde(default = "default_status_bar_bg")]
329    pub status_bar_bg: ColorDef,
330    /// Command prompt text color
331    #[serde(default = "default_prompt_fg")]
332    pub prompt_fg: ColorDef,
333    /// Command prompt background
334    #[serde(default = "default_prompt_bg")]
335    pub prompt_bg: ColorDef,
336    /// Prompt selected text color
337    #[serde(default = "default_prompt_selection_fg")]
338    pub prompt_selection_fg: ColorDef,
339    /// Prompt selection background
340    #[serde(default = "default_prompt_selection_bg")]
341    pub prompt_selection_bg: ColorDef,
342    /// Popup window border color
343    #[serde(default = "default_popup_border_fg")]
344    pub popup_border_fg: ColorDef,
345    /// Popup window background
346    #[serde(default = "default_popup_bg")]
347    pub popup_bg: ColorDef,
348    /// Popup selected item background
349    #[serde(default = "default_popup_selection_bg")]
350    pub popup_selection_bg: ColorDef,
351    /// Popup selected item text color
352    #[serde(default = "default_popup_selection_fg")]
353    pub popup_selection_fg: ColorDef,
354    /// Popup window text color
355    #[serde(default = "default_popup_text_fg")]
356    pub popup_text_fg: ColorDef,
357    /// Autocomplete suggestion background
358    #[serde(default = "default_suggestion_bg")]
359    pub suggestion_bg: ColorDef,
360    /// Selected suggestion background
361    #[serde(default = "default_suggestion_selected_bg")]
362    pub suggestion_selected_bg: ColorDef,
363    /// Help panel background
364    #[serde(default = "default_help_bg")]
365    pub help_bg: ColorDef,
366    /// Help panel text color
367    #[serde(default = "default_help_fg")]
368    pub help_fg: ColorDef,
369    /// Help keybinding text color
370    #[serde(default = "default_help_key_fg")]
371    pub help_key_fg: ColorDef,
372    /// Help panel separator color
373    #[serde(default = "default_help_separator_fg")]
374    pub help_separator_fg: ColorDef,
375    /// Help indicator text color
376    #[serde(default = "default_help_indicator_fg")]
377    pub help_indicator_fg: ColorDef,
378    /// Help indicator background
379    #[serde(default = "default_help_indicator_bg")]
380    pub help_indicator_bg: ColorDef,
381    /// Inline code block background
382    #[serde(default = "default_inline_code_bg")]
383    pub inline_code_bg: ColorDef,
384    /// Split pane separator color
385    #[serde(default = "default_split_separator_fg")]
386    pub split_separator_fg: ColorDef,
387    /// Split separator hover color
388    #[serde(default = "default_split_separator_hover_fg")]
389    pub split_separator_hover_fg: ColorDef,
390    /// Scrollbar track color
391    #[serde(default = "default_scrollbar_track_fg")]
392    pub scrollbar_track_fg: ColorDef,
393    /// Scrollbar thumb color
394    #[serde(default = "default_scrollbar_thumb_fg")]
395    pub scrollbar_thumb_fg: ColorDef,
396    /// Scrollbar track hover color
397    #[serde(default = "default_scrollbar_track_hover_fg")]
398    pub scrollbar_track_hover_fg: ColorDef,
399    /// Scrollbar thumb hover color
400    #[serde(default = "default_scrollbar_thumb_hover_fg")]
401    pub scrollbar_thumb_hover_fg: ColorDef,
402    /// Compose mode margin background
403    #[serde(default = "default_compose_margin_bg")]
404    pub compose_margin_bg: ColorDef,
405    /// Word under cursor highlight
406    #[serde(default = "default_semantic_highlight_bg")]
407    pub semantic_highlight_bg: ColorDef,
408    /// Embedded terminal background (use Default for transparency)
409    #[serde(default = "default_terminal_bg")]
410    pub terminal_bg: ColorDef,
411    /// Embedded terminal default text color
412    #[serde(default = "default_terminal_fg")]
413    pub terminal_fg: ColorDef,
414    /// Warning indicator background in status bar
415    #[serde(default = "default_status_warning_indicator_bg")]
416    pub status_warning_indicator_bg: ColorDef,
417    /// Warning indicator text color in status bar
418    #[serde(default = "default_status_warning_indicator_fg")]
419    pub status_warning_indicator_fg: ColorDef,
420    /// Error indicator background in status bar
421    #[serde(default = "default_status_error_indicator_bg")]
422    pub status_error_indicator_bg: ColorDef,
423    /// Error indicator text color in status bar
424    #[serde(default = "default_status_error_indicator_fg")]
425    pub status_error_indicator_fg: ColorDef,
426    /// Warning indicator hover background
427    #[serde(default = "default_status_warning_indicator_hover_bg")]
428    pub status_warning_indicator_hover_bg: ColorDef,
429    /// Warning indicator hover text color
430    #[serde(default = "default_status_warning_indicator_hover_fg")]
431    pub status_warning_indicator_hover_fg: ColorDef,
432    /// Error indicator hover background
433    #[serde(default = "default_status_error_indicator_hover_bg")]
434    pub status_error_indicator_hover_bg: ColorDef,
435    /// Error indicator hover text color
436    #[serde(default = "default_status_error_indicator_hover_fg")]
437    pub status_error_indicator_hover_fg: ColorDef,
438    /// Tab drop zone background during drag
439    #[serde(default = "default_tab_drop_zone_bg")]
440    pub tab_drop_zone_bg: ColorDef,
441    /// Tab drop zone border during drag
442    #[serde(default = "default_tab_drop_zone_border")]
443    pub tab_drop_zone_border: ColorDef,
444    /// Settings UI selected item background
445    #[serde(default = "default_settings_selected_bg")]
446    pub settings_selected_bg: ColorDef,
447    /// Settings UI selected item foreground (text on selected background)
448    #[serde(default = "default_settings_selected_fg")]
449    pub settings_selected_fg: ColorDef,
450}
451
452// Default tab close hover color (for backward compatibility with existing themes)
453// Default tab colors (for minimal themes)
454fn default_tab_active_fg() -> ColorDef {
455    ColorDef::Named("Yellow".to_string())
456}
457fn default_tab_active_bg() -> ColorDef {
458    ColorDef::Named("Blue".to_string())
459}
460fn default_tab_inactive_fg() -> ColorDef {
461    ColorDef::Named("White".to_string())
462}
463fn default_tab_inactive_bg() -> ColorDef {
464    ColorDef::Named("DarkGray".to_string())
465}
466fn default_tab_separator_bg() -> ColorDef {
467    ColorDef::Named("Black".to_string())
468}
469fn default_tab_close_hover_fg() -> ColorDef {
470    ColorDef::Rgb(255, 100, 100) // Red-ish color for close button hover
471}
472fn default_tab_hover_bg() -> ColorDef {
473    ColorDef::Rgb(70, 70, 75) // Slightly lighter than inactive tab bg for hover
474}
475
476// Default menu colors (for backward compatibility with existing themes)
477fn default_menu_bg() -> ColorDef {
478    ColorDef::Rgb(60, 60, 65)
479}
480fn default_menu_fg() -> ColorDef {
481    ColorDef::Rgb(220, 220, 220)
482}
483fn default_menu_active_bg() -> ColorDef {
484    ColorDef::Rgb(60, 60, 60)
485}
486fn default_menu_active_fg() -> ColorDef {
487    ColorDef::Rgb(255, 255, 255)
488}
489fn default_menu_dropdown_bg() -> ColorDef {
490    ColorDef::Rgb(50, 50, 50)
491}
492fn default_menu_dropdown_fg() -> ColorDef {
493    ColorDef::Rgb(220, 220, 220)
494}
495fn default_menu_highlight_bg() -> ColorDef {
496    ColorDef::Rgb(70, 130, 180)
497}
498fn default_menu_highlight_fg() -> ColorDef {
499    ColorDef::Rgb(255, 255, 255)
500}
501fn default_menu_border_fg() -> ColorDef {
502    ColorDef::Rgb(100, 100, 100)
503}
504fn default_menu_separator_fg() -> ColorDef {
505    ColorDef::Rgb(80, 80, 80)
506}
507fn default_menu_hover_bg() -> ColorDef {
508    ColorDef::Rgb(55, 55, 55)
509}
510fn default_menu_hover_fg() -> ColorDef {
511    ColorDef::Rgb(255, 255, 255)
512}
513fn default_menu_disabled_fg() -> ColorDef {
514    ColorDef::Rgb(100, 100, 100) // Gray for disabled items
515}
516fn default_menu_disabled_bg() -> ColorDef {
517    ColorDef::Rgb(50, 50, 50) // Same as dropdown bg
518}
519// Default status bar colors
520fn default_status_bar_fg() -> ColorDef {
521    ColorDef::Named("White".to_string())
522}
523fn default_status_bar_bg() -> ColorDef {
524    ColorDef::Named("DarkGray".to_string())
525}
526
527// Default prompt colors
528fn default_prompt_fg() -> ColorDef {
529    ColorDef::Named("White".to_string())
530}
531fn default_prompt_bg() -> ColorDef {
532    ColorDef::Named("Black".to_string())
533}
534fn default_prompt_selection_fg() -> ColorDef {
535    ColorDef::Named("White".to_string())
536}
537fn default_prompt_selection_bg() -> ColorDef {
538    ColorDef::Rgb(58, 79, 120)
539}
540
541// Default popup colors
542fn default_popup_border_fg() -> ColorDef {
543    ColorDef::Named("Gray".to_string())
544}
545fn default_popup_bg() -> ColorDef {
546    ColorDef::Rgb(30, 30, 30)
547}
548fn default_popup_selection_bg() -> ColorDef {
549    ColorDef::Rgb(58, 79, 120)
550}
551fn default_popup_selection_fg() -> ColorDef {
552    ColorDef::Rgb(255, 255, 255) // White text on selected popup item
553}
554fn default_popup_text_fg() -> ColorDef {
555    ColorDef::Named("White".to_string())
556}
557
558// Default suggestion colors
559fn default_suggestion_bg() -> ColorDef {
560    ColorDef::Rgb(30, 30, 30)
561}
562fn default_suggestion_selected_bg() -> ColorDef {
563    ColorDef::Rgb(58, 79, 120)
564}
565
566// Default help colors
567fn default_help_bg() -> ColorDef {
568    ColorDef::Named("Black".to_string())
569}
570fn default_help_fg() -> ColorDef {
571    ColorDef::Named("White".to_string())
572}
573fn default_help_key_fg() -> ColorDef {
574    ColorDef::Named("Cyan".to_string())
575}
576fn default_help_separator_fg() -> ColorDef {
577    ColorDef::Named("DarkGray".to_string())
578}
579fn default_help_indicator_fg() -> ColorDef {
580    ColorDef::Named("Red".to_string())
581}
582fn default_help_indicator_bg() -> ColorDef {
583    ColorDef::Named("Black".to_string())
584}
585
586fn default_inline_code_bg() -> ColorDef {
587    ColorDef::Named("DarkGray".to_string())
588}
589
590// Default split separator colors
591fn default_split_separator_fg() -> ColorDef {
592    ColorDef::Rgb(100, 100, 100)
593}
594fn default_split_separator_hover_fg() -> ColorDef {
595    ColorDef::Rgb(100, 149, 237) // Cornflower blue for visibility
596}
597fn default_scrollbar_track_fg() -> ColorDef {
598    ColorDef::Named("DarkGray".to_string())
599}
600fn default_scrollbar_thumb_fg() -> ColorDef {
601    ColorDef::Named("Gray".to_string())
602}
603fn default_scrollbar_track_hover_fg() -> ColorDef {
604    ColorDef::Named("Gray".to_string())
605}
606fn default_scrollbar_thumb_hover_fg() -> ColorDef {
607    ColorDef::Named("White".to_string())
608}
609fn default_compose_margin_bg() -> ColorDef {
610    ColorDef::Rgb(18, 18, 18) // Darker than editor_bg for "desk" effect
611}
612fn default_semantic_highlight_bg() -> ColorDef {
613    ColorDef::Rgb(60, 60, 80) // Subtle dark highlight for word occurrences
614}
615fn default_terminal_bg() -> ColorDef {
616    ColorDef::Named("Default".to_string()) // Use terminal's default background (preserves transparency)
617}
618fn default_terminal_fg() -> ColorDef {
619    ColorDef::Named("Default".to_string()) // Use terminal's default foreground
620}
621fn default_status_warning_indicator_bg() -> ColorDef {
622    ColorDef::Rgb(181, 137, 0) // Solarized yellow/amber - noticeable but not harsh
623}
624fn default_status_warning_indicator_fg() -> ColorDef {
625    ColorDef::Rgb(0, 0, 0) // Black text on amber background
626}
627fn default_status_error_indicator_bg() -> ColorDef {
628    ColorDef::Rgb(220, 50, 47) // Solarized red - clearly an error
629}
630fn default_status_error_indicator_fg() -> ColorDef {
631    ColorDef::Rgb(255, 255, 255) // White text on red background
632}
633fn default_status_warning_indicator_hover_bg() -> ColorDef {
634    ColorDef::Rgb(211, 167, 30) // Lighter amber for hover
635}
636fn default_status_warning_indicator_hover_fg() -> ColorDef {
637    ColorDef::Rgb(0, 0, 0) // Black text on hover
638}
639fn default_status_error_indicator_hover_bg() -> ColorDef {
640    ColorDef::Rgb(250, 80, 77) // Lighter red for hover
641}
642fn default_status_error_indicator_hover_fg() -> ColorDef {
643    ColorDef::Rgb(255, 255, 255) // White text on hover
644}
645fn default_tab_drop_zone_bg() -> ColorDef {
646    ColorDef::Rgb(70, 130, 180) // Steel blue with transparency effect
647}
648fn default_tab_drop_zone_border() -> ColorDef {
649    ColorDef::Rgb(100, 149, 237) // Cornflower blue for border
650}
651fn default_settings_selected_bg() -> ColorDef {
652    ColorDef::Rgb(60, 60, 70) // Subtle highlight for selected settings item
653}
654fn default_settings_selected_fg() -> ColorDef {
655    ColorDef::Rgb(255, 255, 255) // White text on selected background
656}
657
658/// Search result highlighting colors
659#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
660pub struct SearchColors {
661    /// Search match background color
662    #[serde(default = "default_search_match_bg")]
663    pub match_bg: ColorDef,
664    /// Search match text color
665    #[serde(default = "default_search_match_fg")]
666    pub match_fg: ColorDef,
667}
668
669// Default search colors
670fn default_search_match_bg() -> ColorDef {
671    ColorDef::Rgb(100, 100, 20)
672}
673fn default_search_match_fg() -> ColorDef {
674    ColorDef::Rgb(255, 255, 255)
675}
676
677/// LSP diagnostic colors (errors, warnings, etc.)
678#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
679pub struct DiagnosticColors {
680    /// Error message text color
681    #[serde(default = "default_diagnostic_error_fg")]
682    pub error_fg: ColorDef,
683    /// Error highlight background
684    #[serde(default = "default_diagnostic_error_bg")]
685    pub error_bg: ColorDef,
686    /// Warning message text color
687    #[serde(default = "default_diagnostic_warning_fg")]
688    pub warning_fg: ColorDef,
689    /// Warning highlight background
690    #[serde(default = "default_diagnostic_warning_bg")]
691    pub warning_bg: ColorDef,
692    /// Info message text color
693    #[serde(default = "default_diagnostic_info_fg")]
694    pub info_fg: ColorDef,
695    /// Info highlight background
696    #[serde(default = "default_diagnostic_info_bg")]
697    pub info_bg: ColorDef,
698    /// Hint message text color
699    #[serde(default = "default_diagnostic_hint_fg")]
700    pub hint_fg: ColorDef,
701    /// Hint highlight background
702    #[serde(default = "default_diagnostic_hint_bg")]
703    pub hint_bg: ColorDef,
704}
705
706// Default diagnostic colors
707fn default_diagnostic_error_fg() -> ColorDef {
708    ColorDef::Named("Red".to_string())
709}
710fn default_diagnostic_error_bg() -> ColorDef {
711    ColorDef::Rgb(60, 20, 20)
712}
713fn default_diagnostic_warning_fg() -> ColorDef {
714    ColorDef::Named("Yellow".to_string())
715}
716fn default_diagnostic_warning_bg() -> ColorDef {
717    ColorDef::Rgb(60, 50, 0)
718}
719fn default_diagnostic_info_fg() -> ColorDef {
720    ColorDef::Named("Blue".to_string())
721}
722fn default_diagnostic_info_bg() -> ColorDef {
723    ColorDef::Rgb(0, 30, 60)
724}
725fn default_diagnostic_hint_fg() -> ColorDef {
726    ColorDef::Named("Gray".to_string())
727}
728fn default_diagnostic_hint_bg() -> ColorDef {
729    ColorDef::Rgb(30, 30, 30)
730}
731
732/// Syntax highlighting colors
733#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
734pub struct SyntaxColors {
735    /// Language keywords (if, for, fn, etc.)
736    #[serde(default = "default_syntax_keyword")]
737    pub keyword: ColorDef,
738    /// String literals
739    #[serde(default = "default_syntax_string")]
740    pub string: ColorDef,
741    /// Code comments
742    #[serde(default = "default_syntax_comment")]
743    pub comment: ColorDef,
744    /// Function names
745    #[serde(default = "default_syntax_function")]
746    pub function: ColorDef,
747    /// Type names
748    #[serde(rename = "type", default = "default_syntax_type")]
749    pub type_: ColorDef,
750    /// Variable names
751    #[serde(default = "default_syntax_variable")]
752    pub variable: ColorDef,
753    /// Constants and literals
754    #[serde(default = "default_syntax_constant")]
755    pub constant: ColorDef,
756    /// Operators (+, -, =, etc.)
757    #[serde(default = "default_syntax_operator")]
758    pub operator: ColorDef,
759}
760
761// Default syntax colors (VSCode Dark+ inspired)
762fn default_syntax_keyword() -> ColorDef {
763    ColorDef::Rgb(86, 156, 214)
764}
765fn default_syntax_string() -> ColorDef {
766    ColorDef::Rgb(206, 145, 120)
767}
768fn default_syntax_comment() -> ColorDef {
769    ColorDef::Rgb(106, 153, 85)
770}
771fn default_syntax_function() -> ColorDef {
772    ColorDef::Rgb(220, 220, 170)
773}
774fn default_syntax_type() -> ColorDef {
775    ColorDef::Rgb(78, 201, 176)
776}
777fn default_syntax_variable() -> ColorDef {
778    ColorDef::Rgb(156, 220, 254)
779}
780fn default_syntax_constant() -> ColorDef {
781    ColorDef::Rgb(79, 193, 255)
782}
783fn default_syntax_operator() -> ColorDef {
784    ColorDef::Rgb(212, 212, 212)
785}
786
787/// Comprehensive theme structure with all UI colors
788#[derive(Debug, Clone)]
789pub struct Theme {
790    /// Theme name (e.g., "dark", "light", "high-contrast")
791    pub name: String,
792
793    // Editor colors
794    pub editor_bg: Color,
795    pub editor_fg: Color,
796    pub cursor: Color,
797    pub inactive_cursor: Color,
798    pub selection_bg: Color,
799    pub current_line_bg: Color,
800    pub line_number_fg: Color,
801    pub line_number_bg: Color,
802
803    // Diff highlighting colors
804    pub diff_add_bg: Color,
805    pub diff_remove_bg: Color,
806    pub diff_modify_bg: Color,
807    /// Brighter background for inline diff highlighting on added content
808    pub diff_add_highlight_bg: Color,
809    /// Brighter background for inline diff highlighting on removed content
810    pub diff_remove_highlight_bg: Color,
811
812    // UI element colors
813    pub tab_active_fg: Color,
814    pub tab_active_bg: Color,
815    pub tab_inactive_fg: Color,
816    pub tab_inactive_bg: Color,
817    pub tab_separator_bg: Color,
818    pub tab_close_hover_fg: Color,
819    pub tab_hover_bg: Color,
820
821    // Menu bar colors
822    pub menu_bg: Color,
823    pub menu_fg: Color,
824    pub menu_active_bg: Color,
825    pub menu_active_fg: Color,
826    pub menu_dropdown_bg: Color,
827    pub menu_dropdown_fg: Color,
828    pub menu_highlight_bg: Color,
829    pub menu_highlight_fg: Color,
830    pub menu_border_fg: Color,
831    pub menu_separator_fg: Color,
832    pub menu_hover_bg: Color,
833    pub menu_hover_fg: Color,
834    pub menu_disabled_fg: Color,
835    pub menu_disabled_bg: Color,
836
837    pub status_bar_fg: Color,
838    pub status_bar_bg: Color,
839    pub prompt_fg: Color,
840    pub prompt_bg: Color,
841    pub prompt_selection_fg: Color,
842    pub prompt_selection_bg: Color,
843
844    pub popup_border_fg: Color,
845    pub popup_bg: Color,
846    pub popup_selection_bg: Color,
847    pub popup_selection_fg: Color,
848    pub popup_text_fg: Color,
849
850    pub suggestion_bg: Color,
851    pub suggestion_selected_bg: Color,
852
853    pub help_bg: Color,
854    pub help_fg: Color,
855    pub help_key_fg: Color,
856    pub help_separator_fg: Color,
857
858    pub help_indicator_fg: Color,
859    pub help_indicator_bg: Color,
860
861    /// Background color for inline code in help popups
862    pub inline_code_bg: Color,
863
864    pub split_separator_fg: Color,
865    pub split_separator_hover_fg: Color,
866
867    // Scrollbar colors
868    pub scrollbar_track_fg: Color,
869    pub scrollbar_thumb_fg: Color,
870    pub scrollbar_track_hover_fg: Color,
871    pub scrollbar_thumb_hover_fg: Color,
872
873    // Compose mode colors
874    pub compose_margin_bg: Color,
875
876    // Semantic highlighting (word under cursor)
877    pub semantic_highlight_bg: Color,
878
879    // Terminal colors (for embedded terminal buffers)
880    pub terminal_bg: Color,
881    pub terminal_fg: Color,
882
883    // Status bar warning/error indicator colors
884    pub status_warning_indicator_bg: Color,
885    pub status_warning_indicator_fg: Color,
886    pub status_error_indicator_bg: Color,
887    pub status_error_indicator_fg: Color,
888    pub status_warning_indicator_hover_bg: Color,
889    pub status_warning_indicator_hover_fg: Color,
890    pub status_error_indicator_hover_bg: Color,
891    pub status_error_indicator_hover_fg: Color,
892
893    // Tab drag-and-drop colors
894    pub tab_drop_zone_bg: Color,
895    pub tab_drop_zone_border: Color,
896
897    // Settings UI colors
898    pub settings_selected_bg: Color,
899    pub settings_selected_fg: Color,
900
901    // Search colors
902    pub search_match_bg: Color,
903    pub search_match_fg: Color,
904
905    // Diagnostic colors
906    pub diagnostic_error_fg: Color,
907    pub diagnostic_error_bg: Color,
908    pub diagnostic_warning_fg: Color,
909    pub diagnostic_warning_bg: Color,
910    pub diagnostic_info_fg: Color,
911    pub diagnostic_info_bg: Color,
912    pub diagnostic_hint_fg: Color,
913    pub diagnostic_hint_bg: Color,
914
915    // Syntax highlighting colors
916    pub syntax_keyword: Color,
917    pub syntax_string: Color,
918    pub syntax_comment: Color,
919    pub syntax_function: Color,
920    pub syntax_type: Color,
921    pub syntax_variable: Color,
922    pub syntax_constant: Color,
923    pub syntax_operator: Color,
924}
925
926impl From<ThemeFile> for Theme {
927    fn from(file: ThemeFile) -> Self {
928        Self {
929            name: file.name,
930            editor_bg: file.editor.bg.into(),
931            editor_fg: file.editor.fg.into(),
932            cursor: file.editor.cursor.into(),
933            inactive_cursor: file.editor.inactive_cursor.into(),
934            selection_bg: file.editor.selection_bg.into(),
935            current_line_bg: file.editor.current_line_bg.into(),
936            line_number_fg: file.editor.line_number_fg.into(),
937            line_number_bg: file.editor.line_number_bg.into(),
938            diff_add_bg: file.editor.diff_add_bg.clone().into(),
939            diff_remove_bg: file.editor.diff_remove_bg.clone().into(),
940            diff_modify_bg: file.editor.diff_modify_bg.into(),
941            // Compute brighter highlight colors from base diff colors
942            diff_add_highlight_bg: brighten_color(file.editor.diff_add_bg.into(), 40),
943            diff_remove_highlight_bg: brighten_color(file.editor.diff_remove_bg.into(), 40),
944            tab_active_fg: file.ui.tab_active_fg.into(),
945            tab_active_bg: file.ui.tab_active_bg.into(),
946            tab_inactive_fg: file.ui.tab_inactive_fg.into(),
947            tab_inactive_bg: file.ui.tab_inactive_bg.into(),
948            tab_separator_bg: file.ui.tab_separator_bg.into(),
949            tab_close_hover_fg: file.ui.tab_close_hover_fg.into(),
950            tab_hover_bg: file.ui.tab_hover_bg.into(),
951            menu_bg: file.ui.menu_bg.into(),
952            menu_fg: file.ui.menu_fg.into(),
953            menu_active_bg: file.ui.menu_active_bg.into(),
954            menu_active_fg: file.ui.menu_active_fg.into(),
955            menu_dropdown_bg: file.ui.menu_dropdown_bg.into(),
956            menu_dropdown_fg: file.ui.menu_dropdown_fg.into(),
957            menu_highlight_bg: file.ui.menu_highlight_bg.into(),
958            menu_highlight_fg: file.ui.menu_highlight_fg.into(),
959            menu_border_fg: file.ui.menu_border_fg.into(),
960            menu_separator_fg: file.ui.menu_separator_fg.into(),
961            menu_hover_bg: file.ui.menu_hover_bg.into(),
962            menu_hover_fg: file.ui.menu_hover_fg.into(),
963            menu_disabled_fg: file.ui.menu_disabled_fg.into(),
964            menu_disabled_bg: file.ui.menu_disabled_bg.into(),
965            status_bar_fg: file.ui.status_bar_fg.into(),
966            status_bar_bg: file.ui.status_bar_bg.into(),
967            prompt_fg: file.ui.prompt_fg.into(),
968            prompt_bg: file.ui.prompt_bg.into(),
969            prompt_selection_fg: file.ui.prompt_selection_fg.into(),
970            prompt_selection_bg: file.ui.prompt_selection_bg.into(),
971            popup_border_fg: file.ui.popup_border_fg.into(),
972            popup_bg: file.ui.popup_bg.into(),
973            popup_selection_bg: file.ui.popup_selection_bg.into(),
974            popup_selection_fg: file.ui.popup_selection_fg.into(),
975            popup_text_fg: file.ui.popup_text_fg.into(),
976            suggestion_bg: file.ui.suggestion_bg.into(),
977            suggestion_selected_bg: file.ui.suggestion_selected_bg.into(),
978            help_bg: file.ui.help_bg.into(),
979            help_fg: file.ui.help_fg.into(),
980            help_key_fg: file.ui.help_key_fg.into(),
981            help_separator_fg: file.ui.help_separator_fg.into(),
982            help_indicator_fg: file.ui.help_indicator_fg.into(),
983            help_indicator_bg: file.ui.help_indicator_bg.into(),
984            inline_code_bg: file.ui.inline_code_bg.into(),
985            split_separator_fg: file.ui.split_separator_fg.into(),
986            split_separator_hover_fg: file.ui.split_separator_hover_fg.into(),
987            scrollbar_track_fg: file.ui.scrollbar_track_fg.into(),
988            scrollbar_thumb_fg: file.ui.scrollbar_thumb_fg.into(),
989            scrollbar_track_hover_fg: file.ui.scrollbar_track_hover_fg.into(),
990            scrollbar_thumb_hover_fg: file.ui.scrollbar_thumb_hover_fg.into(),
991            compose_margin_bg: file.ui.compose_margin_bg.into(),
992            semantic_highlight_bg: file.ui.semantic_highlight_bg.into(),
993            terminal_bg: file.ui.terminal_bg.into(),
994            terminal_fg: file.ui.terminal_fg.into(),
995            status_warning_indicator_bg: file.ui.status_warning_indicator_bg.into(),
996            status_warning_indicator_fg: file.ui.status_warning_indicator_fg.into(),
997            status_error_indicator_bg: file.ui.status_error_indicator_bg.into(),
998            status_error_indicator_fg: file.ui.status_error_indicator_fg.into(),
999            status_warning_indicator_hover_bg: file.ui.status_warning_indicator_hover_bg.into(),
1000            status_warning_indicator_hover_fg: file.ui.status_warning_indicator_hover_fg.into(),
1001            status_error_indicator_hover_bg: file.ui.status_error_indicator_hover_bg.into(),
1002            status_error_indicator_hover_fg: file.ui.status_error_indicator_hover_fg.into(),
1003            tab_drop_zone_bg: file.ui.tab_drop_zone_bg.into(),
1004            tab_drop_zone_border: file.ui.tab_drop_zone_border.into(),
1005            settings_selected_bg: file.ui.settings_selected_bg.into(),
1006            settings_selected_fg: file.ui.settings_selected_fg.into(),
1007            search_match_bg: file.search.match_bg.into(),
1008            search_match_fg: file.search.match_fg.into(),
1009            diagnostic_error_fg: file.diagnostic.error_fg.into(),
1010            diagnostic_error_bg: file.diagnostic.error_bg.into(),
1011            diagnostic_warning_fg: file.diagnostic.warning_fg.into(),
1012            diagnostic_warning_bg: file.diagnostic.warning_bg.into(),
1013            diagnostic_info_fg: file.diagnostic.info_fg.into(),
1014            diagnostic_info_bg: file.diagnostic.info_bg.into(),
1015            diagnostic_hint_fg: file.diagnostic.hint_fg.into(),
1016            diagnostic_hint_bg: file.diagnostic.hint_bg.into(),
1017            syntax_keyword: file.syntax.keyword.into(),
1018            syntax_string: file.syntax.string.into(),
1019            syntax_comment: file.syntax.comment.into(),
1020            syntax_function: file.syntax.function.into(),
1021            syntax_type: file.syntax.type_.into(),
1022            syntax_variable: file.syntax.variable.into(),
1023            syntax_constant: file.syntax.constant.into(),
1024            syntax_operator: file.syntax.operator.into(),
1025        }
1026    }
1027}
1028
1029impl From<Theme> for ThemeFile {
1030    fn from(theme: Theme) -> Self {
1031        Self {
1032            name: theme.name,
1033            editor: EditorColors {
1034                bg: theme.editor_bg.into(),
1035                fg: theme.editor_fg.into(),
1036                cursor: theme.cursor.into(),
1037                inactive_cursor: theme.inactive_cursor.into(),
1038                selection_bg: theme.selection_bg.into(),
1039                current_line_bg: theme.current_line_bg.into(),
1040                line_number_fg: theme.line_number_fg.into(),
1041                line_number_bg: theme.line_number_bg.into(),
1042                diff_add_bg: theme.diff_add_bg.into(),
1043                diff_remove_bg: theme.diff_remove_bg.into(),
1044                diff_modify_bg: theme.diff_modify_bg.into(),
1045            },
1046            ui: UiColors {
1047                tab_active_fg: theme.tab_active_fg.into(),
1048                tab_active_bg: theme.tab_active_bg.into(),
1049                tab_inactive_fg: theme.tab_inactive_fg.into(),
1050                tab_inactive_bg: theme.tab_inactive_bg.into(),
1051                tab_separator_bg: theme.tab_separator_bg.into(),
1052                tab_close_hover_fg: theme.tab_close_hover_fg.into(),
1053                tab_hover_bg: theme.tab_hover_bg.into(),
1054                menu_bg: theme.menu_bg.into(),
1055                menu_fg: theme.menu_fg.into(),
1056                menu_active_bg: theme.menu_active_bg.into(),
1057                menu_active_fg: theme.menu_active_fg.into(),
1058                menu_dropdown_bg: theme.menu_dropdown_bg.into(),
1059                menu_dropdown_fg: theme.menu_dropdown_fg.into(),
1060                menu_highlight_bg: theme.menu_highlight_bg.into(),
1061                menu_highlight_fg: theme.menu_highlight_fg.into(),
1062                menu_border_fg: theme.menu_border_fg.into(),
1063                menu_separator_fg: theme.menu_separator_fg.into(),
1064                menu_hover_bg: theme.menu_hover_bg.into(),
1065                menu_hover_fg: theme.menu_hover_fg.into(),
1066                menu_disabled_fg: theme.menu_disabled_fg.into(),
1067                menu_disabled_bg: theme.menu_disabled_bg.into(),
1068                status_bar_fg: theme.status_bar_fg.into(),
1069                status_bar_bg: theme.status_bar_bg.into(),
1070                prompt_fg: theme.prompt_fg.into(),
1071                prompt_bg: theme.prompt_bg.into(),
1072                prompt_selection_fg: theme.prompt_selection_fg.into(),
1073                prompt_selection_bg: theme.prompt_selection_bg.into(),
1074                popup_border_fg: theme.popup_border_fg.into(),
1075                popup_bg: theme.popup_bg.into(),
1076                popup_selection_bg: theme.popup_selection_bg.into(),
1077                popup_selection_fg: theme.popup_selection_fg.into(),
1078                popup_text_fg: theme.popup_text_fg.into(),
1079                suggestion_bg: theme.suggestion_bg.into(),
1080                suggestion_selected_bg: theme.suggestion_selected_bg.into(),
1081                help_bg: theme.help_bg.into(),
1082                help_fg: theme.help_fg.into(),
1083                help_key_fg: theme.help_key_fg.into(),
1084                help_separator_fg: theme.help_separator_fg.into(),
1085                help_indicator_fg: theme.help_indicator_fg.into(),
1086                help_indicator_bg: theme.help_indicator_bg.into(),
1087                inline_code_bg: theme.inline_code_bg.into(),
1088                split_separator_fg: theme.split_separator_fg.into(),
1089                split_separator_hover_fg: theme.split_separator_hover_fg.into(),
1090                scrollbar_track_fg: theme.scrollbar_track_fg.into(),
1091                scrollbar_thumb_fg: theme.scrollbar_thumb_fg.into(),
1092                scrollbar_track_hover_fg: theme.scrollbar_track_hover_fg.into(),
1093                scrollbar_thumb_hover_fg: theme.scrollbar_thumb_hover_fg.into(),
1094                compose_margin_bg: theme.compose_margin_bg.into(),
1095                semantic_highlight_bg: theme.semantic_highlight_bg.into(),
1096                terminal_bg: theme.terminal_bg.into(),
1097                terminal_fg: theme.terminal_fg.into(),
1098                status_warning_indicator_bg: theme.status_warning_indicator_bg.into(),
1099                status_warning_indicator_fg: theme.status_warning_indicator_fg.into(),
1100                status_error_indicator_bg: theme.status_error_indicator_bg.into(),
1101                status_error_indicator_fg: theme.status_error_indicator_fg.into(),
1102                status_warning_indicator_hover_bg: theme.status_warning_indicator_hover_bg.into(),
1103                status_warning_indicator_hover_fg: theme.status_warning_indicator_hover_fg.into(),
1104                status_error_indicator_hover_bg: theme.status_error_indicator_hover_bg.into(),
1105                status_error_indicator_hover_fg: theme.status_error_indicator_hover_fg.into(),
1106                tab_drop_zone_bg: theme.tab_drop_zone_bg.into(),
1107                tab_drop_zone_border: theme.tab_drop_zone_border.into(),
1108                settings_selected_bg: theme.settings_selected_bg.into(),
1109                settings_selected_fg: theme.settings_selected_fg.into(),
1110            },
1111            search: SearchColors {
1112                match_bg: theme.search_match_bg.into(),
1113                match_fg: theme.search_match_fg.into(),
1114            },
1115            diagnostic: DiagnosticColors {
1116                error_fg: theme.diagnostic_error_fg.into(),
1117                error_bg: theme.diagnostic_error_bg.into(),
1118                warning_fg: theme.diagnostic_warning_fg.into(),
1119                warning_bg: theme.diagnostic_warning_bg.into(),
1120                info_fg: theme.diagnostic_info_fg.into(),
1121                info_bg: theme.diagnostic_info_bg.into(),
1122                hint_fg: theme.diagnostic_hint_fg.into(),
1123                hint_bg: theme.diagnostic_hint_bg.into(),
1124            },
1125            syntax: SyntaxColors {
1126                keyword: theme.syntax_keyword.into(),
1127                string: theme.syntax_string.into(),
1128                comment: theme.syntax_comment.into(),
1129                function: theme.syntax_function.into(),
1130                type_: theme.syntax_type.into(),
1131                variable: theme.syntax_variable.into(),
1132                constant: theme.syntax_constant.into(),
1133                operator: theme.syntax_operator.into(),
1134            },
1135        }
1136    }
1137}
1138
1139impl Theme {
1140    /// Load a builtin theme by name (no I/O, uses embedded JSON).
1141    pub fn load_builtin(name: &str) -> Option<Self> {
1142        BUILTIN_THEMES
1143            .iter()
1144            .find(|t| t.name == name)
1145            .and_then(|t| serde_json::from_str::<ThemeFile>(t.json).ok())
1146            .map(|tf| tf.into())
1147    }
1148
1149    /// Parse theme from JSON string (no I/O).
1150    pub fn from_json(json: &str) -> Result<Self, String> {
1151        let theme_file: ThemeFile =
1152            serde_json::from_str(json).map_err(|e| format!("Failed to parse theme JSON: {}", e))?;
1153        Ok(theme_file.into())
1154    }
1155
1156    /// Resolve a theme key to a Color.
1157    ///
1158    /// Theme keys use dot notation: "section.field"
1159    /// Examples:
1160    /// - "ui.status_bar_fg" -> status_bar_fg
1161    /// - "editor.selection_bg" -> selection_bg
1162    /// - "syntax.keyword" -> syntax_keyword
1163    /// - "diagnostic.error_fg" -> diagnostic_error_fg
1164    ///
1165    /// Returns None if the key is not recognized.
1166    pub fn resolve_theme_key(&self, key: &str) -> Option<Color> {
1167        // Parse "section.field" format
1168        let parts: Vec<&str> = key.split('.').collect();
1169        if parts.len() != 2 {
1170            return None;
1171        }
1172
1173        let (section, field) = (parts[0], parts[1]);
1174
1175        match section {
1176            "editor" => match field {
1177                "bg" => Some(self.editor_bg),
1178                "fg" => Some(self.editor_fg),
1179                "cursor" => Some(self.cursor),
1180                "inactive_cursor" => Some(self.inactive_cursor),
1181                "selection_bg" => Some(self.selection_bg),
1182                "current_line_bg" => Some(self.current_line_bg),
1183                "line_number_fg" => Some(self.line_number_fg),
1184                "line_number_bg" => Some(self.line_number_bg),
1185                "diff_add_bg" => Some(self.diff_add_bg),
1186                "diff_remove_bg" => Some(self.diff_remove_bg),
1187                "diff_modify_bg" => Some(self.diff_modify_bg),
1188                _ => None,
1189            },
1190            "ui" => match field {
1191                "tab_active_fg" => Some(self.tab_active_fg),
1192                "tab_active_bg" => Some(self.tab_active_bg),
1193                "tab_inactive_fg" => Some(self.tab_inactive_fg),
1194                "tab_inactive_bg" => Some(self.tab_inactive_bg),
1195                "status_bar_fg" => Some(self.status_bar_fg),
1196                "status_bar_bg" => Some(self.status_bar_bg),
1197                "prompt_fg" => Some(self.prompt_fg),
1198                "prompt_bg" => Some(self.prompt_bg),
1199                "prompt_selection_fg" => Some(self.prompt_selection_fg),
1200                "prompt_selection_bg" => Some(self.prompt_selection_bg),
1201                "popup_bg" => Some(self.popup_bg),
1202                "popup_border_fg" => Some(self.popup_border_fg),
1203                "popup_selection_bg" => Some(self.popup_selection_bg),
1204                "popup_selection_fg" => Some(self.popup_selection_fg),
1205                "popup_text_fg" => Some(self.popup_text_fg),
1206                "menu_bg" => Some(self.menu_bg),
1207                "menu_fg" => Some(self.menu_fg),
1208                "menu_active_bg" => Some(self.menu_active_bg),
1209                "menu_active_fg" => Some(self.menu_active_fg),
1210                "help_bg" => Some(self.help_bg),
1211                "help_fg" => Some(self.help_fg),
1212                "help_key_fg" => Some(self.help_key_fg),
1213                "split_separator_fg" => Some(self.split_separator_fg),
1214                "scrollbar_thumb_fg" => Some(self.scrollbar_thumb_fg),
1215                "semantic_highlight_bg" => Some(self.semantic_highlight_bg),
1216                _ => None,
1217            },
1218            "syntax" => match field {
1219                "keyword" => Some(self.syntax_keyword),
1220                "string" => Some(self.syntax_string),
1221                "comment" => Some(self.syntax_comment),
1222                "function" => Some(self.syntax_function),
1223                "type" => Some(self.syntax_type),
1224                "variable" => Some(self.syntax_variable),
1225                "constant" => Some(self.syntax_constant),
1226                "operator" => Some(self.syntax_operator),
1227                _ => None,
1228            },
1229            "diagnostic" => match field {
1230                "error_fg" => Some(self.diagnostic_error_fg),
1231                "error_bg" => Some(self.diagnostic_error_bg),
1232                "warning_fg" => Some(self.diagnostic_warning_fg),
1233                "warning_bg" => Some(self.diagnostic_warning_bg),
1234                "info_fg" => Some(self.diagnostic_info_fg),
1235                "info_bg" => Some(self.diagnostic_info_bg),
1236                "hint_fg" => Some(self.diagnostic_hint_fg),
1237                "hint_bg" => Some(self.diagnostic_hint_bg),
1238                _ => None,
1239            },
1240            "search" => match field {
1241                "match_bg" => Some(self.search_match_bg),
1242                "match_fg" => Some(self.search_match_fg),
1243                _ => None,
1244            },
1245            _ => None,
1246        }
1247    }
1248}
1249
1250// =============================================================================
1251// Theme Schema Generation for Plugin API
1252// =============================================================================
1253
1254/// Returns the raw JSON Schema for ThemeFile, generated by schemars.
1255/// The schema uses standard JSON Schema format with $ref for type references.
1256/// Plugins are responsible for parsing and resolving $ref references.
1257pub fn get_theme_schema() -> serde_json::Value {
1258    use schemars::schema_for;
1259    let schema = schema_for!(ThemeFile);
1260    serde_json::to_value(&schema).unwrap_or_default()
1261}
1262
1263/// Returns a map of built-in theme names to their JSON content.
1264pub fn get_builtin_themes() -> serde_json::Value {
1265    let mut map = serde_json::Map::new();
1266    for theme in BUILTIN_THEMES {
1267        map.insert(
1268            theme.name.to_string(),
1269            serde_json::Value::String(theme.json.to_string()),
1270        );
1271    }
1272    serde_json::Value::Object(map)
1273}
1274
1275#[cfg(test)]
1276mod tests {
1277    use super::*;
1278
1279    #[test]
1280    fn test_load_builtin_theme() {
1281        let dark = Theme::load_builtin(THEME_DARK).expect("Dark theme must exist");
1282        assert_eq!(dark.name, THEME_DARK);
1283
1284        let light = Theme::load_builtin(THEME_LIGHT).expect("Light theme must exist");
1285        assert_eq!(light.name, THEME_LIGHT);
1286
1287        let high_contrast =
1288            Theme::load_builtin(THEME_HIGH_CONTRAST).expect("High contrast theme must exist");
1289        assert_eq!(high_contrast.name, THEME_HIGH_CONTRAST);
1290    }
1291
1292    #[test]
1293    fn test_builtin_themes_match_schema() {
1294        for theme in BUILTIN_THEMES {
1295            let _: ThemeFile = serde_json::from_str(theme.json)
1296                .unwrap_or_else(|_| panic!("Theme '{}' does not match schema", theme.name));
1297        }
1298    }
1299
1300    #[test]
1301    fn test_from_json() {
1302        let json = r#"{"name":"test","editor":{},"ui":{},"search":{},"diagnostic":{},"syntax":{}}"#;
1303        let theme = Theme::from_json(json).expect("Should parse minimal theme");
1304        assert_eq!(theme.name, "test");
1305    }
1306
1307    #[test]
1308    fn test_default_reset_color() {
1309        // Test that "Default" maps to Color::Reset
1310        let color: Color = ColorDef::Named("Default".to_string()).into();
1311        assert_eq!(color, Color::Reset);
1312
1313        // Test that "Reset" also maps to Color::Reset
1314        let color: Color = ColorDef::Named("Reset".to_string()).into();
1315        assert_eq!(color, Color::Reset);
1316    }
1317}