eazygit 0.5.1

A fast TUI for Git with staging, conflicts, rebase, and palette-first UX
Documentation
//! Event bus for cross-component communication.
//!
//! Allows components to subscribe to and publish events without
//! direct coupling, following the publish-subscribe pattern.

use std::collections::HashMap;
use std::any::{Any, TypeId};

/// Simple event bus for cross-component communication.
pub struct EventBus {
    /// Subscribers by event type
    handlers: HashMap<TypeId, Vec<Box<dyn Fn(&dyn Any) + Send + Sync>>>,
}

impl EventBus {
    /// Create a new event bus.
    pub fn new() -> Self {
        Self {
            handlers: HashMap::new(),
        }
    }
    
    /// Subscribe to events of type T.
    pub fn subscribe<T, F>(&mut self, handler: F)
    where
        T: 'static,
        F: Fn(&T) + Send + Sync + 'static,
    {
        let type_id = TypeId::of::<T>();
        let boxed_handler: Box<dyn Fn(&dyn Any) + Send + Sync> = Box::new(move |event| {
            if let Some(typed_event) = event.downcast_ref::<T>() {
                handler(typed_event);
            }
        });
        
        self.handlers
            .entry(type_id)
            .or_insert_with(Vec::new)
            .push(boxed_handler);
    }
    
    /// Publish an event to all subscribers.
    pub fn publish<T: 'static>(&self, event: &T) {
        let type_id = TypeId::of::<T>();
        if let Some(handlers) = self.handlers.get(&type_id) {
            for handler in handlers {
                handler(event);
            }
        }
    }
    
    /// Get subscriber count for an event type.
    pub fn subscriber_count<T: 'static>(&self) -> usize {
        let type_id = TypeId::of::<T>();
        self.handlers.get(&type_id).map(|h| h.len()).unwrap_or(0)
    }
    
    /// Clear all subscribers.
    pub fn clear(&mut self) {
        self.handlers.clear();
    }
}

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

// Common event types

/// Event fired when a file's status changes.
#[derive(Debug, Clone)]
pub struct FileStatusChanged {
    pub path: String,
    pub staged: bool,
}

/// Event fired when commits change.
#[derive(Debug, Clone)]
pub struct CommitsChanged {
    pub count: usize,
}

/// Event fired when branches change.
#[derive(Debug, Clone)]
pub struct BranchesChanged {
    pub current: String,
    pub count: usize,
}

/// Event fired when focus changes.
#[derive(Debug, Clone)]
pub struct FocusChanged {
    pub from: String,
    pub to: String,
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::sync::atomic::{AtomicUsize, Ordering};
    use std::sync::Arc;
    
    #[test]
    fn test_subscribe_and_publish() {
        let mut bus = EventBus::new();
        let counter = Arc::new(AtomicUsize::new(0));
        let counter_clone = counter.clone();
        
        bus.subscribe::<FileStatusChanged, _>(move |_event| {
            counter_clone.fetch_add(1, Ordering::SeqCst);
        });
        
        bus.publish(&FileStatusChanged {
            path: "test.rs".to_string(),
            staged: true,
        });
        
        assert_eq!(counter.load(Ordering::SeqCst), 1);
    }
    
    #[test]
    fn test_subscriber_count() {
        let mut bus = EventBus::new();
        
        bus.subscribe::<FileStatusChanged, _>(|_| {});
        bus.subscribe::<FileStatusChanged, _>(|_| {});
        
        assert_eq!(bus.subscriber_count::<FileStatusChanged>(), 2);
        assert_eq!(bus.subscriber_count::<CommitsChanged>(), 0);
    }
}