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