eazygit 0.5.1

A fast TUI for Git with staging, conflicts, rebase, and palette-first UX
Documentation
//! Command queue for managing async operations.
//!
//! Provides a queue for scheduling and managing background Git operations,
//! with priority levels and cancellation support.

use std::collections::VecDeque;
use std::time::Instant;

/// Priority levels for queued commands.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum Priority {
    /// Low priority (background refresh)
    Low = 0,
    /// Normal priority (user-initiated)
    Normal = 1,
    /// High priority (blocking operation)
    High = 2,
}

/// State of a queued command.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CommandState {
    /// Waiting to be executed
    Pending,
    /// Currently executing
    Running,
    /// Completed successfully
    Completed,
    /// Failed with error
    Failed,
    /// Cancelled by user
    Cancelled,
}

/// A command in the queue.
#[derive(Debug, Clone)]
pub struct QueuedCommand {
    /// Unique ID
    pub id: u64,
    /// Command name/description
    pub name: String,
    /// Priority level
    pub priority: Priority,
    /// Current state
    pub state: CommandState,
    /// When the command was queued
    pub queued_at: Instant,
    /// When execution started (if any)
    pub started_at: Option<Instant>,
    /// Error message (if failed)
    pub error: Option<String>,
}

/// Command queue manager.
pub struct CommandQueue {
    /// Pending commands (ordered by priority)
    pending: VecDeque<QueuedCommand>,
    /// Currently running command
    running: Option<QueuedCommand>,
    /// Completed/failed commands (recent history)
    history: VecDeque<QueuedCommand>,
    /// Max history size
    max_history: usize,
    /// Next command ID
    next_id: u64,
}

impl CommandQueue {
    /// Create a new command queue.
    pub fn new(max_history: usize) -> Self {
        Self {
            pending: VecDeque::new(),
            running: None,
            history: VecDeque::with_capacity(max_history),
            max_history,
            next_id: 0,
        }
    }
    
    /// Queue a new command.
    pub fn enqueue(&mut self, name: impl Into<String>, priority: Priority) -> u64 {
        let id = self.next_id;
        self.next_id += 1;
        
        let cmd = QueuedCommand {
            id,
            name: name.into(),
            priority,
            state: CommandState::Pending,
            queued_at: Instant::now(),
            started_at: None,
            error: None,
        };
        
        // Insert by priority (higher priority first)
        let pos = self.pending
            .iter()
            .position(|c| c.priority < priority)
            .unwrap_or(self.pending.len());
        self.pending.insert(pos, cmd);
        
        id
    }
    
    /// Get next pending command (does not remove it).
    pub fn peek(&self) -> Option<&QueuedCommand> {
        self.pending.front()
    }
    
    /// Start executing the next pending command.
    pub fn start_next(&mut self) -> Option<u64> {
        if self.running.is_some() {
            return None; // Already running
        }
        
        if let Some(mut cmd) = self.pending.pop_front() {
            cmd.state = CommandState::Running;
            cmd.started_at = Some(Instant::now());
            let id = cmd.id;
            self.running = Some(cmd);
            Some(id)
        } else {
            None
        }
    }
    
    /// Mark running command as completed.
    pub fn complete(&mut self) {
        if let Some(mut cmd) = self.running.take() {
            cmd.state = CommandState::Completed;
            self.add_to_history(cmd);
        }
    }
    
    /// Mark running command as failed.
    pub fn fail(&mut self, error: impl Into<String>) {
        if let Some(mut cmd) = self.running.take() {
            cmd.state = CommandState::Failed;
            cmd.error = Some(error.into());
            self.add_to_history(cmd);
        }
    }
    
    /// Cancel a pending command by ID.
    pub fn cancel(&mut self, id: u64) -> bool {
        if let Some(pos) = self.pending.iter().position(|c| c.id == id) {
            let mut cmd = self.pending.remove(pos).unwrap();
            cmd.state = CommandState::Cancelled;
            self.add_to_history(cmd);
            true
        } else {
            false
        }
    }
    
    /// Get pending count.
    pub fn pending_count(&self) -> usize {
        self.pending.len()
    }
    
    /// Check if any command is running.
    pub fn is_running(&self) -> bool {
        self.running.is_some()
    }
    
    /// Get running command.
    pub fn running(&self) -> Option<&QueuedCommand> {
        self.running.as_ref()
    }
    
    fn add_to_history(&mut self, cmd: QueuedCommand) {
        if self.history.len() >= self.max_history {
            self.history.pop_front();
        }
        self.history.push_back(cmd);
    }
}

impl Default for CommandQueue {
    fn default() -> Self {
        Self::new(50)
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    
    #[test]
    fn test_queue_priority() {
        let mut queue = CommandQueue::new(10);
        
        queue.enqueue("low", Priority::Low);
        queue.enqueue("high", Priority::High);
        queue.enqueue("normal", Priority::Normal);
        
        // High priority should be first
        assert_eq!(queue.peek().unwrap().name, "high");
    }
    
    #[test]
    fn test_queue_lifecycle() {
        let mut queue = CommandQueue::new(10);
        
        let id = queue.enqueue("test", Priority::Normal);
        assert_eq!(queue.pending_count(), 1);
        
        queue.start_next();
        assert!(queue.is_running());
        assert_eq!(queue.pending_count(), 0);
        
        queue.complete();
        assert!(!queue.is_running());
    }
}