guardy 0.2.4

Fast, secure git hooks in Rust with secret scanning and protected file synchronization
Documentation
//! Live progress display for scanning operations

use std::thread;
use std::time::Duration;

use indicatif::{ProgressBar, ProgressStyle};

/// Creates and manages a live progress display for scanning
pub struct ProgressDisplay {
    progress_bar: ProgressBar,
}

impl ProgressDisplay {
    /// Create a new progress display
    pub fn new(show_progress: bool) -> Self {
        let progress_bar = if show_progress {
            let pb = ProgressBar::new_spinner();
            pb.set_style(
                ProgressStyle::default_spinner()
                    .template("🔍 [{elapsed_precise}] {spinner} {msg}")
                    .expect("Invalid template")
                    .tick_strings(&["", "", "", "", "", "", "", "", "", ""]),
            );
            // Enable continuous animation even when processing is slow
            pb.enable_steady_tick(Duration::from_millis(100));
            pb
        } else {
            ProgressBar::hidden()
        };

        Self { progress_bar }
    }

    /// Start the background progress updating thread
    pub fn start_background_updates(&self) -> thread::JoinHandle<()> {
        let pb = self.progress_bar.clone();

        thread::spawn(move || {
            while !pb.is_finished() {
                let (
                    files_processed,
                    binary_filtered,
                    size_filtered,
                    path_filtered,
                    processing_failed,
                    matches_found,
                ) = super::counters::get_counts();

                let total_skipped = binary_filtered + size_filtered + path_filtered;

                // Format message with clear visual separation and emphasis
                let message = if matches_found > 0 {
                    if processing_failed > 0 {
                        format!(
                            "Scanning... {} files | {} skipped | {} errors | ⚠️  {} MATCHES FOUND",
                            files_processed, total_skipped, processing_failed, matches_found
                        )
                    } else {
                        format!(
                            "Scanning... {} files | {} skipped | ⚠️  {} MATCHES FOUND",
                            files_processed, total_skipped, matches_found
                        )
                    }
                } else if processing_failed > 0 {
                    format!(
                        "Scanning... {} files | {} skipped | ⚠️  {} errors | ✓ No secrets yet",
                        files_processed, total_skipped, processing_failed
                    )
                } else {
                    format!(
                        "Scanning... {} files | {} skipped | ✓ No secrets yet",
                        files_processed, total_skipped
                    )
                };

                pb.set_message(message);
                pb.tick();

                thread::sleep(Duration::from_millis(100)); // Update every 100ms
            }
        })
    }

    /// Finish the progress display and print final stats
    pub fn finish(&self, stats: &super::types::ScanStats) {
        // Stop the progress bar and clear it
        self.progress_bar.finish_and_clear();

        // Print completion message that persists (won't be overwritten by later output)
        let duration = std::time::Duration::from_millis(stats.scan_duration_ms);
        let formatted_time = crate::shared::time::format_time(duration);

        println!(
            "✅ Scan complete: {} files scanned | {} skipped | {} matches | {}",
            stats.files_scanned, stats.files_skipped, stats.total_matches, formatted_time
        );
    }
}