eazygit 0.5.1

A fast TUI for Git with staging, conflicts, rebase, and palette-first UX
Documentation
use std::path::PathBuf;
use anyhow::{Result, Context};
use std::fs;
use crate::config::settings::Settings;
use crate::config::structs::PartialConfig;

/// Get config directory path (~/.config/eazygit/).
pub fn config_dir() -> Option<PathBuf> {
    dirs::config_dir().map(|p| p.join("eazygit"))
}

/// Load configuration: check ~/.config/eazygit/config.toml, then ./eazygit.toml.
pub fn load() -> Result<Settings> {
    let mut settings = Settings::default();
    
    // 1. Load global config
    if let Some(config_dir) = config_dir() {
        let global_config = config_dir.join("config.toml");
        if global_config.exists() {
            let content = fs::read_to_string(&global_config)
                .context(format!("Failed to read config at {:?}", global_config))?;
            let partial: PartialConfig = toml::from_str(&content)
                .context("Failed to parse global config")?;
            apply_partial(&mut settings, partial);
        }
    }
    
    // 2. Load local config (override)
    let local_config = PathBuf::from("eazygit.toml");
    if local_config.exists() {
        let content = fs::read_to_string(&local_config)
            .context("Failed to read local eazygit.toml")?;
        let partial: PartialConfig = toml::from_str(&content)
            .context("Failed to parse local config")?;
        apply_partial(&mut settings, partial);
    }
    
    Ok(settings)
}

/// Persist the selected theme to config without clobbering other keys.
pub fn persist_theme(theme_name: &str) -> Result<()> {
    if let Some(config_dir) = config_dir() {
        if !config_dir.exists() {
            fs::create_dir_all(&config_dir)?;
        }
        let config_path = config_dir.join("config.toml");
        
        let mut content = if config_path.exists() {
            fs::read_to_string(&config_path)?
        } else {
            String::new()
        };
        
        // Simple string replacement to preserve comments
        // Look for: [ui] ... theme = "..."
        // Or if [ui] doesn't exist, append it.
        // If theme doesn't exist under [ui], add it.
        
        // Check if [ui] header exists
        if content.contains("[ui]") {
            // regex is heavy, let's do simple search/replace if theme key exists
            if content.contains("theme =") {
                 let lines: Vec<&str> = content.lines().collect();
                 let mut new_lines = Vec::new();
                 let mut in_ui = false;
                 
                 for line in lines {
                     if line.trim() == "[ui]" {
                         in_ui = true;
                         new_lines.push(line.to_string());
                     } else if line.trim().starts_with("[") {
                         in_ui = false;
                         new_lines.push(line.to_string());
                     } else if in_ui && line.trim().starts_with("theme") {
                         new_lines.push(format!("theme = \"{}\"", theme_name));
                     } else {
                         new_lines.push(line.to_string());
                     }
                 }
                 content = new_lines.join("\n");
            } else {
                // [ui] exists but no theme key inside it? Trickier without parser.
                // Just replacing "\[ui\]" with "[ui]\ntheme = ..." might work
                content = content.replace("[ui]", &format!("[ui]\ntheme = \"{}\"", theme_name));
            }
        } else {
            // Append [ui] section
            content.push_str(&format!("\n[ui]\ntheme = \"{}\"\n", theme_name));
        }
        
        fs::write(config_path, content)?;
    }
    Ok(())
}

fn apply_partial(cfg: &mut Settings, partial: PartialConfig) {
     if let Some(p) = partial.core {
        if let Some(v) = p.editor { cfg.core.editor = Some(v); }
        if let Some(v) = p.auto_fetch { cfg.core.auto_fetch = v; }
        if let Some(v) = p.auto_fetch_interval { cfg.core.auto_fetch_interval = v; }
        if let Some(v) = p.auto_fetch_remote { cfg.core.auto_fetch_remote = v; }
        if let Some(v) = p.format_command { cfg.core.format_command = Some(v); }
        if let Some(v) = p.format_on_revert { cfg.core.format_on_revert = v; }
         if let Some(v) = p.push_ff_only_enforce { cfg.core.push_ff_only_enforce = v; }
         if let Some(v) = p.pull_ff_only { cfg.core.pull_ff_only = v; }
         if let Some(v) = p.pull_autostash { cfg.core.pull_autostash = v; }
         if let Some(v) = p.pull_timeout_secs { cfg.core.pull_timeout_secs = v; }
         if let Some(v) = p.rebase_timeout_secs { cfg.core.rebase_timeout_secs = v; }
    }
    
    if let Some(p) = partial.diff {
         if let Some(v) = p.context_lines { cfg.diff.context_lines = v; }
         if let Some(v) = p.syntax_highlight { cfg.diff.syntax_highlight = v; }
    }
    
    if let Some(p) = partial.ui {
        if let Some(v) = p.theme { cfg.ui.theme = v; }
        if let Some(v) = p.show_help_footer { cfg.ui.show_help_footer = v; }
        if let Some(v) = p.font_family { cfg.ui.font_family = Some(v); }
        if let Some(v) = p.font_size { cfg.ui.font_size = Some(v); }
    }
    
    // Apply theme overrides from top-level or [theme] block
    if let Some(pt) = partial.theme {
        crate::config::theme::loader::merge_partial(&mut cfg.theme, pt);
    }
    
    // Apply legacy top-level theme fields if they exist and aren't covered by [theme]
    let legacy_partial = crate::config::theme::PartialThemeConfig {
        fg: partial.fg, bg: partial.bg, muted: partial.muted, accent: partial.accent,
        border: partial.border, border_focused: partial.border_focused,
        selection_bg: partial.selection_bg, selection_fg: partial.selection_fg,
        diff_add: partial.diff_add, diff_remove: partial.diff_remove,
        diff_context: partial.diff_context, diff_hunk: partial.diff_hunk,
        staged: partial.staged, unstaged: partial.unstaged, untracked: partial.untracked,
        header: partial.header, footer: partial.footer,
        error: partial.error, warning: partial.warning, success: partial.success,
    };
    crate::config::theme::loader::merge_partial(&mut cfg.theme, legacy_partial);

    if let Some(k) = partial.keymap {
        cfg.keymap.extend(k);
    }
    if let Some(c) = partial.commands {
        cfg.custom_commands.extend(c);
    }
}



/// Create default config file if it doesn't exist.
pub fn create_default_config() -> Result<()> {
    if let Some(config_dir) = config_dir() {
        if !config_dir.exists() {
            fs::create_dir_all(&config_dir)?;
        }
        let config_path = config_dir.join("config.toml");
        if !config_path.exists() {
            let default_content = r##"
# EazyGit Configuration

[core]
# editor = "vim"
auto_fetch = true
auto_fetch_interval = 60 # seconds

[ui]
theme = "default_dark"
# show_help_footer = true

[keymap]
# "shift-p" = "push"

[theme]
# Override specific colors here
# accent = "#ff00ff"
"##;
            fs::write(config_path, default_content.trim())?;
        }
    }
    Ok(())
}