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