From 0c03430b764905e72967b16668a4c0170674ed68 Mon Sep 17 00:00:00 2001 From: 7000pctAUTO Date: Sun, 1 Feb 2026 12:20:56 +0000 Subject: [PATCH] Initial upload: Auto Commit Message Generator with CI/CD workflow --- src/prompt.rs | 139 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 139 insertions(+) create mode 100644 src/prompt.rs diff --git a/src/prompt.rs b/src/prompt.rs new file mode 100644 index 0000000..95930c4 --- /dev/null +++ b/src/prompt.rs @@ -0,0 +1,139 @@ +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!(); +}