use anyhow::Result; use colored::Colorize; use dialoguer::{Confirm, Select}; use crate::analyzer::{analyze_changes, get_all_commit_types, CommitType}; use crate::generator::{generate_alternative_messages, generate_message}; use crate::git::{GitRepo, StagedChanges}; pub fn select_commit_type(suggested: CommitType) -> Result { let all_types = get_all_commit_types(); let type_names: Vec = all_types.iter().map(|t| format!("{}", t)).collect(); let suggested_idx = all_types.iter().position(|t| *t == suggested).unwrap_or(0); println!("{}", "\nSuggested commit type:".bold().green()); println!(" {}", format!("{}", suggested).green().bold()); let selection = Select::with_theme(&dialoguer::theme::ColorfulTheme::default()) .with_prompt("Select commit type (use ↑/↓ to navigate, Enter to confirm)") .items(&type_names) .default(suggested_idx) .interact()?; Ok(all_types[selection]) } pub fn confirm_message(message: &str, alternatives: &[String]) -> Result<(bool, Option)> { println!("\n{}", "Generated commit message:".bold().green()); println!("\n{}\n", message.bright_white().on_blue()); if !alternatives.is_empty() { println!("{}", "Alternative messages:".bold().yellow()); for (i, alt) in alternatives.iter().enumerate() { println!(" {}: {}", (i + 1).to_string().yellow(), alt); } } let confirm = Confirm::with_theme(&dialoguer::theme::ColorfulTheme::default()) .with_prompt("Do you want to use this commit message?") .default(true) .interact()?; if confirm { Ok((true, None)) } else { let edit_prompt = Confirm::with_theme(&dialoguer::theme::ColorfulTheme::default()) .with_prompt("Would you like to edit the message manually?") .default(false) .interact()?; if edit_prompt { let edited = dialoguer::Editor::new() .edit(&format!( "\n# Edit your commit message below:\n{}\n", message )) .map_err(|e| anyhow::anyhow!("Failed to open editor: {}", e))?; match edited { Some(content) => { let cleaned = content .lines() .filter(|l| !l.starts_with('#')) .collect::>() .join("\n") .trim() .to_string(); if cleaned.is_empty() { Ok((false, None)) } else { Ok((true, Some(cleaned))) } } None => Ok((false, None)), } } else { Ok((false, None)) } } } pub fn interactive_commit( git_repo: &GitRepo, suggested_type: CommitType, staged: &StagedChanges, ) -> Result> { let selected_type = if git_repo.has_config()? { select_commit_type(suggested_type)? } else { suggested_type }; let analysis = analyze_changes(staged); let analysis = AnalysisResult { commit_type: selected_type, ..analysis }; let message = generate_message(&analysis, staged); let alternatives = generate_alternative_messages(&analysis, staged); let (confirmed, edited_message) = confirm_message(&message, &alternatives)?; if !confirmed { println!("{}", "\nCommit aborted by user.".yellow()); return Ok(None); } Ok(edited_message.or(Some(message))) } pub fn print_staged_summary(staged: &StagedChanges) { if staged.files.is_empty() { println!("{}", "No staged changes found.".yellow()); return; } println!("{}", "\nStaged changes:".bold().green()); for file in &staged.files { let status_str = match file.status { crate::git::FileStatus::Added => "A".green(), crate::git::FileStatus::Deleted => "D".red(), crate::git::FileStatus::Modified => "M".yellow(), crate::git::FileStatus::Renamed => "R".cyan(), crate::git::FileStatus::Unknown => "?".white(), }; println!( " {} {:>6} {:>6} {}", status_str, format!("+{}", file.additions).green(), format!("-{}", file.deletions).red(), file.path ); } println!(); }