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 and embedded JSON content.
19pub struct BuiltinTheme {
20    pub name: &'static str,
21    pub json: &'static str,
22}
23
24/// All builtin themes embedded at compile time.
25pub 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
56/// Convert a ratatui Color to RGB values.
57/// Returns None for Reset or Indexed colors.
58pub 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
81/// Brighten a color by adding an amount to each RGB component.
82/// Clamps values to 255.
83pub 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/// Serializable color representation
96#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
97#[serde(untagged)]
98pub enum ColorDef {
99    /// RGB color as [r, g, b]
100    Rgb(u8, u8, u8),
101    /// Named color
102    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 uses the terminal's default color (preserves transparency)
127                "Default" | "Reset" => Color::Reset,
128                _ => Color::White, // Default fallback
129            },
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                // Fallback for indexed colors
157                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/// Serializable theme definition (matches JSON structure)
168#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
169pub struct ThemeFile {
170    /// Theme name
171    pub name: String,
172    /// Editor area colors
173    pub editor: EditorColors,
174    /// UI element colors (tabs, menus, status bar, etc.)
175    pub ui: UiColors,
176    /// Search result highlighting colors
177    pub search: SearchColors,
178    /// LSP diagnostic colors (errors, warnings, etc.)
179    pub diagnostic: DiagnosticColors,
180    /// Syntax highlighting colors
181    pub syntax: SyntaxColors,
182}
183
184/// Editor area colors
185#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
186pub struct EditorColors {
187    /// Editor background color
188    #[serde(default = "default_editor_bg")]
189    pub bg: ColorDef,
190    /// Default text color
191    #[serde(default = "default_editor_fg")]
192    pub fg: ColorDef,
193    /// Cursor color
194    #[serde(default = "default_cursor")]
195    pub cursor: ColorDef,
196    /// Cursor color in unfocused splits
197    #[serde(default = "default_inactive_cursor")]
198    pub inactive_cursor: ColorDef,
199    /// Selected text background
200    #[serde(default = "default_selection_bg")]
201    pub selection_bg: ColorDef,
202    /// Background of the line containing cursor
203    #[serde(default = "default_current_line_bg")]
204    pub current_line_bg: ColorDef,
205    /// Line number text color
206    #[serde(default = "default_line_number_fg")]
207    pub line_number_fg: ColorDef,
208    /// Line number gutter background
209    #[serde(default = "default_line_number_bg")]
210    pub line_number_bg: ColorDef,
211    /// Diff added line background
212    #[serde(default = "default_diff_add_bg")]
213    pub diff_add_bg: ColorDef,
214    /// Diff removed line background
215    #[serde(default = "default_diff_remove_bg")]
216    pub diff_remove_bg: ColorDef,
217    /// Diff modified line background
218    #[serde(default = "default_diff_modify_bg")]
219    pub diff_modify_bg: ColorDef,
220}
221
222// Default editor colors (for minimal themes)
223fn 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) // Dark green
249}
250fn default_diff_remove_bg() -> ColorDef {
251    ColorDef::Rgb(70, 35, 35) // Dark red
252}
253fn default_diff_modify_bg() -> ColorDef {
254    ColorDef::Rgb(40, 38, 30) // Very subtle yellow tint, close to dark bg
255}
256
257/// UI element colors (tabs, menus, status bar, etc.)
258#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
259pub struct UiColors {
260    /// Active tab text color
261    #[serde(default = "default_tab_active_fg")]
262    pub tab_active_fg: ColorDef,
263    /// Active tab background color
264    #[serde(default = "default_tab_active_bg")]
265    pub tab_active_bg: ColorDef,
266    /// Inactive tab text color
267    #[serde(default = "default_tab_inactive_fg")]
268    pub tab_inactive_fg: ColorDef,
269    /// Inactive tab background color
270    #[serde(default = "default_tab_inactive_bg")]
271    pub tab_inactive_bg: ColorDef,
272    /// Tab bar separator color
273    #[serde(default = "default_tab_separator_bg")]
274    pub tab_separator_bg: ColorDef,
275    /// Tab close button hover color
276    #[serde(default = "default_tab_close_hover_fg")]
277    pub tab_close_hover_fg: ColorDef,
278    /// Tab hover background color
279    #[serde(default = "default_tab_hover_bg")]
280    pub tab_hover_bg: ColorDef,
281    /// Menu bar background
282    #[serde(default = "default_menu_bg")]
283    pub menu_bg: ColorDef,
284    /// Menu bar text color
285    #[serde(default = "default_menu_fg")]
286    pub menu_fg: ColorDef,
287    /// Active menu item background
288    #[serde(default = "default_menu_active_bg")]
289    pub menu_active_bg: ColorDef,
290    /// Active menu item text color
291    #[serde(default = "default_menu_active_fg")]
292    pub menu_active_fg: ColorDef,
293    /// Dropdown menu background
294    #[serde(default = "default_menu_dropdown_bg")]
295    pub menu_dropdown_bg: ColorDef,
296    /// Dropdown menu text color
297    #[serde(default = "default_menu_dropdown_fg")]
298    pub menu_dropdown_fg: ColorDef,
299    /// Highlighted menu item background
300    #[serde(default = "default_menu_highlight_bg")]
301    pub menu_highlight_bg: ColorDef,
302    /// Highlighted menu item text color
303    #[serde(default = "default_menu_highlight_fg")]
304    pub menu_highlight_fg: ColorDef,
305    /// Menu border color
306    #[serde(default = "default_menu_border_fg")]
307    pub menu_border_fg: ColorDef,
308    /// Menu separator line color
309    #[serde(default = "default_menu_separator_fg")]
310    pub menu_separator_fg: ColorDef,
311    /// Menu item hover background
312    #[serde(default = "default_menu_hover_bg")]
313    pub menu_hover_bg: ColorDef,
314    /// Menu item hover text color
315    #[serde(default = "default_menu_hover_fg")]
316    pub menu_hover_fg: ColorDef,
317    /// Disabled menu item text color
318    #[serde(default = "default_menu_disabled_fg")]
319    pub menu_disabled_fg: ColorDef,
320    /// Disabled menu item background
321    #[serde(default = "default_menu_disabled_bg")]
322    pub menu_disabled_bg: ColorDef,
323    /// Status bar text color
324    #[serde(default = "default_status_bar_fg")]
325    pub status_bar_fg: ColorDef,
326    /// Status bar background color
327    #[serde(default = "default_status_bar_bg")]
328    pub status_bar_bg: ColorDef,
329    /// Command prompt text color
330    #[serde(default = "default_prompt_fg")]
331    pub prompt_fg: ColorDef,
332    /// Command prompt background
333    #[serde(default = "default_prompt_bg")]
334    pub prompt_bg: ColorDef,
335    /// Prompt selected text color
336    #[serde(default = "default_prompt_selection_fg")]
337    pub prompt_selection_fg: ColorDef,
338    /// Prompt selection background
339    #[serde(default = "default_prompt_selection_bg")]
340    pub prompt_selection_bg: ColorDef,
341    /// Popup window border color
342    #[serde(default = "default_popup_border_fg")]
343    pub popup_border_fg: ColorDef,
344    /// Popup window background
345    #[serde(default = "default_popup_bg")]
346    pub popup_bg: ColorDef,
347    /// Popup selected item background
348    #[serde(default = "default_popup_selection_bg")]
349    pub popup_selection_bg: ColorDef,
350    /// Popup window text color
351    #[serde(default = "default_popup_text_fg")]
352    pub popup_text_fg: ColorDef,
353    /// Autocomplete suggestion background
354    #[serde(default = "default_suggestion_bg")]
355    pub suggestion_bg: ColorDef,
356    /// Selected suggestion background
357    #[serde(default = "default_suggestion_selected_bg")]
358    pub suggestion_selected_bg: ColorDef,
359    /// Help panel background
360    #[serde(default = "default_help_bg")]
361    pub help_bg: ColorDef,
362    /// Help panel text color
363    #[serde(default = "default_help_fg")]
364    pub help_fg: ColorDef,
365    /// Help keybinding text color
366    #[serde(default = "default_help_key_fg")]
367    pub help_key_fg: ColorDef,
368    /// Help panel separator color
369    #[serde(default = "default_help_separator_fg")]
370    pub help_separator_fg: ColorDef,
371    /// Help indicator text color
372    #[serde(default = "default_help_indicator_fg")]
373    pub help_indicator_fg: ColorDef,
374    /// Help indicator background
375    #[serde(default = "default_help_indicator_bg")]
376    pub help_indicator_bg: ColorDef,
377    /// Inline code block background
378    #[serde(default = "default_inline_code_bg")]
379    pub inline_code_bg: ColorDef,
380    /// Split pane separator color
381    #[serde(default = "default_split_separator_fg")]
382    pub split_separator_fg: ColorDef,
383    /// Split separator hover color
384    #[serde(default = "default_split_separator_hover_fg")]
385    pub split_separator_hover_fg: ColorDef,
386    /// Scrollbar track color
387    #[serde(default = "default_scrollbar_track_fg")]
388    pub scrollbar_track_fg: ColorDef,
389    /// Scrollbar thumb color
390    #[serde(default = "default_scrollbar_thumb_fg")]
391    pub scrollbar_thumb_fg: ColorDef,
392    /// Scrollbar track hover color
393    #[serde(default = "default_scrollbar_track_hover_fg")]
394    pub scrollbar_track_hover_fg: ColorDef,
395    /// Scrollbar thumb hover color
396    #[serde(default = "default_scrollbar_thumb_hover_fg")]
397    pub scrollbar_thumb_hover_fg: ColorDef,
398    /// Compose mode margin background
399    #[serde(default = "default_compose_margin_bg")]
400    pub compose_margin_bg: ColorDef,
401    /// Word under cursor highlight
402    #[serde(default = "default_semantic_highlight_bg")]
403    pub semantic_highlight_bg: ColorDef,
404    /// Embedded terminal background (use Default for transparency)
405    #[serde(default = "default_terminal_bg")]
406    pub terminal_bg: ColorDef,
407    /// Embedded terminal default text color
408    #[serde(default = "default_terminal_fg")]
409    pub terminal_fg: ColorDef,
410    /// Warning indicator background in status bar
411    #[serde(default = "default_status_warning_indicator_bg")]
412    pub status_warning_indicator_bg: ColorDef,
413    /// Warning indicator text color in status bar
414    #[serde(default = "default_status_warning_indicator_fg")]
415    pub status_warning_indicator_fg: ColorDef,
416    /// Error indicator background in status bar
417    #[serde(default = "default_status_error_indicator_bg")]
418    pub status_error_indicator_bg: ColorDef,
419    /// Error indicator text color in status bar
420    #[serde(default = "default_status_error_indicator_fg")]
421    pub status_error_indicator_fg: ColorDef,
422    /// Warning indicator hover background
423    #[serde(default = "default_status_warning_indicator_hover_bg")]
424    pub status_warning_indicator_hover_bg: ColorDef,
425    /// Warning indicator hover text color
426    #[serde(default = "default_status_warning_indicator_hover_fg")]
427    pub status_warning_indicator_hover_fg: ColorDef,
428    /// Error indicator hover background
429    #[serde(default = "default_status_error_indicator_hover_bg")]
430    pub status_error_indicator_hover_bg: ColorDef,
431    /// Error indicator hover text color
432    #[serde(default = "default_status_error_indicator_hover_fg")]
433    pub status_error_indicator_hover_fg: ColorDef,
434    /// Tab drop zone background during drag
435    #[serde(default = "default_tab_drop_zone_bg")]
436    pub tab_drop_zone_bg: ColorDef,
437    /// Tab drop zone border during drag
438    #[serde(default = "default_tab_drop_zone_border")]
439    pub tab_drop_zone_border: ColorDef,
440}
441
442// Default tab close hover color (for backward compatibility with existing themes)
443// Default tab colors (for minimal themes)
444fn 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) // Red-ish color for close button hover
461}
462fn default_tab_hover_bg() -> ColorDef {
463    ColorDef::Rgb(70, 70, 75) // Slightly lighter than inactive tab bg for hover
464}
465
466// Default menu colors (for backward compatibility with existing themes)
467fn 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) // Gray for disabled items
505}
506fn default_menu_disabled_bg() -> ColorDef {
507    ColorDef::Rgb(50, 50, 50) // Same as dropdown bg
508}
509// Default status bar colors
510fn 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
517// Default prompt colors
518fn 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
531// Default popup colors
532fn 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
545// Default suggestion colors
546fn 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
553// Default help colors
554fn 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
577// Default split separator colors
578fn 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) // Cornflower blue for visibility
583}
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) // Darker than editor_bg for "desk" effect
598}
599fn default_semantic_highlight_bg() -> ColorDef {
600    ColorDef::Rgb(60, 60, 80) // Subtle dark highlight for word occurrences
601}
602fn default_terminal_bg() -> ColorDef {
603    ColorDef::Named("Default".to_string()) // Use terminal's default background (preserves transparency)
604}
605fn default_terminal_fg() -> ColorDef {
606    ColorDef::Named("Default".to_string()) // Use terminal's default foreground
607}
608fn default_status_warning_indicator_bg() -> ColorDef {
609    ColorDef::Rgb(181, 137, 0) // Solarized yellow/amber - noticeable but not harsh
610}
611fn default_status_warning_indicator_fg() -> ColorDef {
612    ColorDef::Rgb(0, 0, 0) // Black text on amber background
613}
614fn default_status_error_indicator_bg() -> ColorDef {
615    ColorDef::Rgb(220, 50, 47) // Solarized red - clearly an error
616}
617fn default_status_error_indicator_fg() -> ColorDef {
618    ColorDef::Rgb(255, 255, 255) // White text on red background
619}
620fn default_status_warning_indicator_hover_bg() -> ColorDef {
621    ColorDef::Rgb(211, 167, 30) // Lighter amber for hover
622}
623fn default_status_warning_indicator_hover_fg() -> ColorDef {
624    ColorDef::Rgb(0, 0, 0) // Black text on hover
625}
626fn default_status_error_indicator_hover_bg() -> ColorDef {
627    ColorDef::Rgb(250, 80, 77) // Lighter red for hover
628}
629fn default_status_error_indicator_hover_fg() -> ColorDef {
630    ColorDef::Rgb(255, 255, 255) // White text on hover
631}
632fn default_tab_drop_zone_bg() -> ColorDef {
633    ColorDef::Rgb(70, 130, 180) // Steel blue with transparency effect
634}
635fn default_tab_drop_zone_border() -> ColorDef {
636    ColorDef::Rgb(100, 149, 237) // Cornflower blue for border
637}
638
639/// Search result highlighting colors
640#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
641pub struct SearchColors {
642    /// Search match background color
643    #[serde(default = "default_search_match_bg")]
644    pub match_bg: ColorDef,
645    /// Search match text color
646    #[serde(default = "default_search_match_fg")]
647    pub match_fg: ColorDef,
648}
649
650// Default search colors
651fn 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/// LSP diagnostic colors (errors, warnings, etc.)
659#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
660pub struct DiagnosticColors {
661    /// Error message text color
662    #[serde(default = "default_diagnostic_error_fg")]
663    pub error_fg: ColorDef,
664    /// Error highlight background
665    #[serde(default = "default_diagnostic_error_bg")]
666    pub error_bg: ColorDef,
667    /// Warning message text color
668    #[serde(default = "default_diagnostic_warning_fg")]
669    pub warning_fg: ColorDef,
670    /// Warning highlight background
671    #[serde(default = "default_diagnostic_warning_bg")]
672    pub warning_bg: ColorDef,
673    /// Info message text color
674    #[serde(default = "default_diagnostic_info_fg")]
675    pub info_fg: ColorDef,
676    /// Info highlight background
677    #[serde(default = "default_diagnostic_info_bg")]
678    pub info_bg: ColorDef,
679    /// Hint message text color
680    #[serde(default = "default_diagnostic_hint_fg")]
681    pub hint_fg: ColorDef,
682    /// Hint highlight background
683    #[serde(default = "default_diagnostic_hint_bg")]
684    pub hint_bg: ColorDef,
685}
686
687// Default diagnostic colors
688fn 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/// Syntax highlighting colors
714#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
715pub struct SyntaxColors {
716    /// Language keywords (if, for, fn, etc.)
717    #[serde(default = "default_syntax_keyword")]
718    pub keyword: ColorDef,
719    /// String literals
720    #[serde(default = "default_syntax_string")]
721    pub string: ColorDef,
722    /// Code comments
723    #[serde(default = "default_syntax_comment")]
724    pub comment: ColorDef,
725    /// Function names
726    #[serde(default = "default_syntax_function")]
727    pub function: ColorDef,
728    /// Type names
729    #[serde(rename = "type", default = "default_syntax_type")]
730    pub type_: ColorDef,
731    /// Variable names
732    #[serde(default = "default_syntax_variable")]
733    pub variable: ColorDef,
734    /// Constants and literals
735    #[serde(default = "default_syntax_constant")]
736    pub constant: ColorDef,
737    /// Operators (+, -, =, etc.)
738    #[serde(default = "default_syntax_operator")]
739    pub operator: ColorDef,
740}
741
742// Default syntax colors (VSCode Dark+ inspired)
743fn 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/// Comprehensive theme structure with all UI colors
769#[derive(Debug, Clone)]
770pub struct Theme {
771    /// Theme name (e.g., "dark", "light", "high-contrast")
772    pub name: String,
773
774    // Editor colors
775    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    // Diff highlighting colors
785    pub diff_add_bg: Color,
786    pub diff_remove_bg: Color,
787    pub diff_modify_bg: Color,
788    /// Brighter background for inline diff highlighting on added content
789    pub diff_add_highlight_bg: Color,
790    /// Brighter background for inline diff highlighting on removed content
791    pub diff_remove_highlight_bg: Color,
792
793    // UI element colors
794    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    // Menu bar colors
803    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    /// Background color for inline code in help popups
842    pub inline_code_bg: Color,
843
844    pub split_separator_fg: Color,
845    pub split_separator_hover_fg: Color,
846
847    // Scrollbar colors
848    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    // Compose mode colors
854    pub compose_margin_bg: Color,
855
856    // Semantic highlighting (word under cursor)
857    pub semantic_highlight_bg: Color,
858
859    // Terminal colors (for embedded terminal buffers)
860    pub terminal_bg: Color,
861    pub terminal_fg: Color,
862
863    // Status bar warning/error indicator colors
864    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    // Tab drag-and-drop colors
874    pub tab_drop_zone_bg: Color,
875    pub tab_drop_zone_border: Color,
876
877    // Search colors
878    pub search_match_bg: Color,
879    pub search_match_fg: Color,
880
881    // Diagnostic colors
882    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    // Syntax highlighting colors
892    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            // Compute brighter highlight colors from base diff colors
918            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    /// Load a builtin theme by name (no I/O, uses embedded JSON).
1111    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    /// Parse theme from JSON string (no I/O).
1120    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
1127// =============================================================================
1128// Theme Schema Generation for Plugin API
1129// =============================================================================
1130
1131/// Returns the raw JSON Schema for ThemeFile, generated by schemars.
1132/// The schema uses standard JSON Schema format with $ref for type references.
1133/// Plugins are responsible for parsing and resolving $ref references.
1134pub 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
1140/// Returns a map of built-in theme names to their JSON content.
1141pub 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        // Test that "Default" maps to Color::Reset
1187        let color: Color = ColorDef::Named("Default".to_string()).into();
1188        assert_eq!(color, Color::Reset);
1189
1190        // Test that "Reset" also maps to Color::Reset
1191        let color: Color = ColorDef::Named("Reset".to_string()).into();
1192        assert_eq!(color, Color::Reset);
1193    }
1194}