eazygit 0.5.1

A fast TUI for Git with staging, conflicts, rebase, and palette-first UX
Documentation
//! Theme configuration.

mod structs;
mod colors;
mod builtins;
pub mod loader;

pub use structs::{ThemeConfig, PartialThemeConfig};
use ratatui::style::Color;

impl ThemeConfig {
    /// Get the list of built-in theme names.
    pub fn builtins() -> &'static [&'static str] {
        builtins::get_builtins()
    }

    /// Load a named theme or return default.
    pub fn load_named(name: &str) -> Self {
        builtins::load_named_theme(name)
    }

    /// Load an external TOML theme file.
    pub fn load_external(path: &std::path::Path, name: &str) -> Option<Self> {
        loader::load_external_theme(path, name)
    }

    /// Merge user overrides.
    pub fn merge_partial(&mut self, other: PartialThemeConfig) {
        loader::merge_partial(self, other)
    }

    /// Parse a color string into a ratatui Color.
    pub fn parse_color(s: &str) -> Color {
        colors::parse_color(s)
    }

    pub fn is_valid_color_str(s: &str) -> bool {
        colors::is_valid_color_str(s)
    }
}

// Color getters
impl ThemeConfig {
    pub fn border_color(&self) -> Color {
        Self::parse_color(&self.border)
    }

    pub fn fg_color(&self) -> Color {
        Self::parse_color(&self.fg)
    }

    pub fn bg_color(&self) -> Color {
        if self.enable_transparency {
            Color::Reset
        } else {
            Self::parse_color(&self.bg)
        }
    }

    pub fn muted_color(&self) -> Color {
        Self::parse_color(&self.muted)
    }

    pub fn accent_color(&self) -> Color {
        Self::parse_color(&self.accent)
    }

    pub fn border_focused_color(&self) -> Color {
        Self::parse_color(&self.border_focused)
    }

    pub fn selection_bg_color(&self) -> Color {
        Self::parse_color(&self.selection_bg)
    }

    pub fn selection_fg_color(&self) -> Color {
        Self::parse_color(&self.selection_fg)
    }

    pub fn diff_add_color(&self) -> Color {
        Self::parse_color(&self.diff_add)
    }

    pub fn diff_remove_color(&self) -> Color {
        Self::parse_color(&self.diff_remove)
    }

    pub fn diff_hunk_color(&self) -> Color {
        Self::parse_color(&self.diff_hunk)
    }

    pub fn error_color(&self) -> Color {
        Self::parse_color(&self.error)
    }

    pub fn success_color(&self) -> Color {
        Self::parse_color(&self.success)
    }

    pub fn diff_context_color(&self) -> Color {
        Self::parse_color(&self.diff_context)
    }

    pub fn warning_color(&self) -> Color {
        Self::parse_color(&self.warning)
    }

    pub fn footer_color(&self) -> Color {
        Self::parse_color(&self.footer)
    }

    pub fn header_color(&self) -> Color {
        Self::parse_color(&self.header)
    }

    pub fn staged_color(&self) -> Color {
        Self::parse_color(&self.staged)
    }

    pub fn unstaged_color(&self) -> Color {
        Self::parse_color(&self.unstaged)
    }

    pub fn untracked_color(&self) -> Color {
        Self::parse_color(&self.untracked)
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::path::Path;
    use std::fs;

    #[test]
    fn loads_external_nord_theme() {
        // Mock test - only runs if file exists (like original test)
        let path = Path::new("themes/nord.toml");
        if path.exists() {
            let theme = ThemeConfig::load_external(path, "nord").expect("load nord");
            assert_eq!(theme.selection_bg, "#3B4252");
            assert_eq!(theme.fg, "#ECEFF4");
            assert_eq!(theme.bg, "#2E3440");
        }
    }

    #[test]
    fn skips_invalid_color_and_keeps_base() {
        let tmp = std::env::temp_dir().join("eazygit_invalid_theme.toml");
        let content = r##"
[extend]
base = "nord"

[palette]
selection_bg = "#GGGGGG"  # invalid
header = "#123456"
"##;
        fs::write(&tmp, content).expect("write temp theme");
        let theme = ThemeConfig::load_external(&tmp, "temp").expect("load temp");
        // Invalid selection_bg should be skipped, so stays as base Nord value.
        // Nord selection_bg = #3B4252
        assert_eq!(theme.selection_bg, "#3B4252");
        // Valid header should apply.
        assert_eq!(theme.header, "#123456");
        let _ = fs::remove_file(&tmp);
    }
}