sql_cli/state/
dispatcher.rs

1//! State dispatcher for pub-sub pattern
2
3use crate::buffer::Buffer;
4use crate::state::coordinator::StateCoordinator;
5use crate::state::events::{StateChange, StateEvent};
6use std::cell::RefCell;
7use std::rc::{Rc, Weak};
8use tracing::{debug, info, warn};
9
10/// Trait for components that subscribe to state changes
11pub trait StateSubscriber {
12    /// Handle a state event
13    fn on_state_event(&mut self, event: &StateEvent, buffer: &Buffer);
14
15    /// Get subscriber name for debugging
16    fn name(&self) -> &str;
17}
18
19/// Commands that subscribers can return to be executed
20#[derive(Debug)]
21pub enum SubscriberCommand {
22    None,
23    ClearSearch,
24    UpdateViewport,
25    RefreshDisplay,
26}
27
28/// State dispatcher that coordinates buffer state changes with subscribers
29pub struct StateDispatcher {
30    /// Weak reference to the buffer (to avoid circular references)
31    buffer: Weak<RefCell<Buffer>>,
32
33    /// List of subscribers
34    subscribers: Vec<Box<dyn StateSubscriber>>,
35
36    /// Event history for debugging
37    event_history: Vec<StateEvent>,
38
39    /// Maximum event history size
40    max_history: usize,
41}
42
43impl Default for StateDispatcher {
44    fn default() -> Self {
45        Self::new()
46    }
47}
48
49impl StateDispatcher {
50    #[must_use]
51    pub fn new() -> Self {
52        Self {
53            buffer: Weak::new(),
54            subscribers: Vec::new(),
55            event_history: Vec::new(),
56            max_history: 100,
57        }
58    }
59
60    /// Set the buffer this dispatcher coordinates
61    pub fn set_buffer(&mut self, buffer: Rc<RefCell<Buffer>>) {
62        self.buffer = Rc::downgrade(&buffer);
63    }
64
65    /// Add a subscriber
66    pub fn subscribe(&mut self, subscriber: Box<dyn StateSubscriber>) {
67        info!("StateDispatcher: Adding subscriber: {}", subscriber.name());
68        self.subscribers.push(subscriber);
69    }
70
71    /// Dispatch a state event
72    pub fn dispatch(&mut self, event: StateEvent) {
73        debug!("StateDispatcher: Dispatching event: {:?}", event);
74
75        // Record event in history
76        self.event_history.push(event.clone());
77        if self.event_history.len() > self.max_history {
78            self.event_history.remove(0);
79        }
80
81        // Get buffer reference
82        let buffer_rc = if let Some(b) = self.buffer.upgrade() {
83            b
84        } else {
85            warn!("StateDispatcher: Buffer reference lost!");
86            return;
87        };
88
89        // Process event to get state changes
90        let change = {
91            let buffer = buffer_rc.borrow();
92            buffer.process_event(&event)
93        };
94
95        // Apply changes to buffer
96        if !matches!(
97            change,
98            StateChange {
99                mode: None,
100                search_state: None,
101                filter_state: None,
102                fuzzy_filter_state: None,
103                clear_all_searches: false
104            }
105        ) {
106            info!("StateDispatcher: Applying state change: {:?}", change);
107            buffer_rc.borrow_mut().apply_change(change);
108        }
109
110        // Notify all subscribers
111        let buffer = buffer_rc.borrow();
112        for subscriber in &mut self.subscribers {
113            debug!(
114                "StateDispatcher: Notifying subscriber: {}",
115                subscriber.name()
116            );
117            subscriber.on_state_event(&event, &buffer);
118        }
119    }
120
121    /// Dispatch a mode change event
122    pub fn dispatch_mode_change(
123        &mut self,
124        from: crate::buffer::AppMode,
125        to: crate::buffer::AppMode,
126    ) {
127        self.dispatch(StateEvent::ModeChanged { from, to });
128    }
129
130    /// Dispatch a search start event
131    pub fn dispatch_search_start(
132        &mut self,
133        search_type: crate::ui::state::shadow_state::SearchType,
134    ) {
135        self.dispatch(StateEvent::SearchStarted { search_type });
136    }
137
138    /// Dispatch a search end event
139    pub fn dispatch_search_end(&mut self, search_type: crate::ui::state::shadow_state::SearchType) {
140        self.dispatch(StateEvent::SearchEnded { search_type });
141    }
142
143    /// Get event history for debugging
144    #[must_use]
145    pub fn get_event_history(&self) -> &[StateEvent] {
146        &self.event_history
147    }
148}
149
150/// Example subscriber for `VimSearchManager`
151pub struct VimSearchSubscriber {
152    active: bool,
153}
154
155impl Default for VimSearchSubscriber {
156    fn default() -> Self {
157        Self::new()
158    }
159}
160
161impl VimSearchSubscriber {
162    #[must_use]
163    pub fn new() -> Self {
164        Self { active: false }
165    }
166}
167
168impl StateSubscriber for VimSearchSubscriber {
169    fn on_state_event(&mut self, event: &StateEvent, buffer: &Buffer) {
170        match event {
171            StateEvent::SearchStarted { search_type } => {
172                if matches!(search_type, crate::ui::state::shadow_state::SearchType::Vim) {
173                    info!("VimSearchSubscriber: Activating for vim search");
174                    self.active = true;
175                }
176            }
177            StateEvent::SearchEnded { search_type } => {
178                if matches!(search_type, crate::ui::state::shadow_state::SearchType::Vim) {
179                    info!("VimSearchSubscriber: Deactivating - search ended");
180                    self.active = false;
181                }
182            }
183            StateEvent::ModeChanged { from: _, to } => {
184                // Check if we should deactivate based on buffer state
185                if *to == crate::buffer::AppMode::Results && buffer.search_state.pattern.is_empty()
186                {
187                    if self.active {
188                        info!("VimSearchSubscriber: Deactivating - mode changed to Results with empty search");
189                    }
190                    self.active = false;
191                }
192            }
193            _ => {}
194        }
195    }
196
197    fn name(&self) -> &'static str {
198        "VimSearchSubscriber"
199    }
200}