sql_cli/ui/search/
vim_search_adapter.rs

1//! Adapter to make `VimSearchManager` work with `StateDispatcher`
2
3use crate::app_state_container::AppStateContainer;
4use crate::buffer::{AppMode, Buffer, BufferAPI};
5use crate::data::data_view::DataView;
6use crate::state::{StateEvent, StateSubscriber};
7use crate::ui::search::vim_search_manager::VimSearchManager;
8use crate::ui::state::shadow_state::SearchType;
9use crate::ui::viewport_manager::ViewportManager;
10use crossterm::event::KeyCode;
11use tracing::{debug, info};
12
13/// Adapter that connects `VimSearchManager` to the state dispatcher
14pub struct VimSearchAdapter {
15    manager: VimSearchManager,
16    is_active: bool,
17}
18
19impl Default for VimSearchAdapter {
20    fn default() -> Self {
21        Self::new()
22    }
23}
24
25impl VimSearchAdapter {
26    #[must_use]
27    pub fn new() -> Self {
28        Self {
29            manager: VimSearchManager::new(),
30            is_active: false,
31        }
32    }
33
34    /// Create with a specific manager (for testing or advanced use)
35    #[must_use]
36    pub fn with_manager(manager: VimSearchManager) -> Self {
37        Self {
38            manager,
39            is_active: false,
40        }
41    }
42
43    /// Check if vim search should handle a key based on `AppStateContainer` state
44    pub fn should_handle_key(&self, state: &AppStateContainer) -> bool {
45        // Use AppStateContainer's vim search check
46        let should_handle = state.vim_search_should_handle_key();
47
48        debug!(
49            "VimSearchAdapter: should_handle_key? mode={:?}, pattern='{}', active={}, result={}",
50            state.get_mode(),
51            state.get_search_pattern(),
52            self.is_active,
53            should_handle
54        );
55
56        should_handle || self.is_active
57    }
58
59    /// Check if vim search should handle a key based on Buffer state (legacy - for compatibility)
60    pub fn should_handle_key_buffer(&self, buffer: &dyn BufferAPI) -> bool {
61        // Check Buffer's state, not internal state
62        let in_search_mode = buffer.get_mode() == AppMode::Search;
63        let has_pattern = !buffer.get_search_pattern().is_empty();
64
65        debug!(
66            "VimSearchAdapter: should_handle_key_buffer? mode={:?}, pattern='{}', active={}",
67            buffer.get_mode(),
68            buffer.get_search_pattern(),
69            self.is_active
70        );
71
72        // Only handle keys if we're in search mode OR have an active pattern
73        in_search_mode || (self.is_active && has_pattern)
74    }
75
76    /// Clear the search manager when search ends
77    pub fn clear(&mut self) {
78        info!("VimSearchAdapter: Clearing vim search");
79        self.manager.clear();
80        self.is_active = false;
81    }
82
83    /// Get the inner manager
84    #[must_use]
85    pub fn manager(&self) -> &VimSearchManager {
86        &self.manager
87    }
88
89    /// Get mutable reference to inner manager
90    pub fn manager_mut(&mut self) -> &mut VimSearchManager {
91        &mut self.manager
92    }
93
94    /// Handle a key press through `AppStateContainer` (simplified interface)
95    /// Returns true if key was handled, false to let it fall through
96    pub fn handle_key(&mut self, key: KeyCode, state: &mut AppStateContainer) -> bool {
97        let mode = state.get_mode();
98
99        // Special handling for Escape key
100        if key == KeyCode::Esc {
101            match mode {
102                AppMode::Results if self.is_active || self.is_navigating() => {
103                    // In Results mode with active search: Signal that Escape was pressed
104                    // But DON'T handle it here - return false to let StateCoordinator handle it
105                    info!("VimSearchAdapter: Escape detected in Results mode with active search - signaling for StateCoordinator");
106                    // Return false so it falls through to handle_results_input
107                    // where StateCoordinator will properly clear everything
108                    return false;
109                }
110                AppMode::Search => {
111                    // In Search mode: exit to Results
112                    info!("VimSearchAdapter: Exiting search mode to Results");
113                    state.exit_vim_search();
114                    self.clear();
115                    return true;
116                }
117                _ => {
118                    // Not our concern
119                    return false;
120                }
121            }
122        }
123
124        // For other keys, only handle if we should
125        if !self.should_handle_key(state) {
126            debug!("VimSearchAdapter: Not handling key - search not active");
127            return false;
128        }
129
130        // For n/N keys, let them go through the normal action system
131        // The TUI will map them to NextSearchMatch/PreviousSearchMatch actions
132        // which will then call vim_search_next()/vim_search_previous()
133        match key {
134            KeyCode::Char('n' | 'N') => {
135                info!(
136                    "VimSearchAdapter: Navigation key '{}' - letting TUI handle via action system",
137                    if key == KeyCode::Char('n') { "n" } else { "N" }
138                );
139                // Don't handle - let TUI map to NextSearchMatch/PreviousSearchMatch actions
140                false
141            }
142            KeyCode::Enter => {
143                info!("VimSearchAdapter: Confirming search - will need dataview and viewport from TUI");
144                // Signal that this key was handled - the TUI needs to provide dataview and viewport
145                true
146            }
147            _ => false,
148        }
149    }
150
151    /// Handle a key press - delegates to `VimSearchManager` if appropriate (legacy)
152    pub fn handle_key_legacy(
153        &mut self,
154        key: KeyCode,
155        dataview: &DataView,
156        viewport: &mut ViewportManager,
157        buffer: &dyn BufferAPI,
158    ) -> bool {
159        // First check if we should handle keys at all
160        if !self.should_handle_key_buffer(buffer) {
161            debug!("VimSearchAdapter: Not handling key - search not active");
162            return false;
163        }
164
165        // Delegate to VimSearchManager for actual search operations
166        match key {
167            KeyCode::Char('n') => {
168                info!("VimSearchAdapter: Delegating 'n' (next match) to VimSearchManager");
169                self.manager.next_match(viewport);
170                true
171            }
172            KeyCode::Char('N') => {
173                info!("VimSearchAdapter: Delegating 'N' (previous match) to VimSearchManager");
174                self.manager.previous_match(viewport);
175                true
176            }
177            KeyCode::Enter => {
178                info!("VimSearchAdapter: Delegating Enter (confirm search) to VimSearchManager");
179                self.manager.confirm_search(dataview, viewport);
180                true
181            }
182            KeyCode::Esc => {
183                info!("VimSearchAdapter: Search cancelled");
184                self.clear();
185                false // Let TUI handle mode change
186            }
187            _ => {
188                // For typing characters in search mode
189                if self.manager.is_typing() {
190                    if let KeyCode::Char(c) = key {
191                        // Update pattern - this would need to be connected to Buffer's search_state
192                        debug!("VimSearchAdapter: Character '{}' typed in search", c);
193                        // Note: Pattern updates should go through Buffer
194                        true
195                    } else {
196                        false
197                    }
198                } else {
199                    false
200                }
201            }
202        }
203    }
204
205    /// Start a new search
206    pub fn start_search(&mut self) {
207        info!("VimSearchAdapter: Starting new search");
208        self.is_active = true;
209        self.manager.start_search();
210    }
211
212    /// Update search pattern and find matches
213    pub fn update_pattern(
214        &mut self,
215        pattern: String,
216        dataview: &DataView,
217        viewport: &mut ViewportManager,
218    ) {
219        debug!("VimSearchAdapter: Updating pattern to '{}'", pattern);
220        self.manager.update_pattern(pattern, dataview, viewport);
221    }
222
223    /// Confirm the current search
224    pub fn confirm_search(&mut self, dataview: &DataView, viewport: &mut ViewportManager) -> bool {
225        info!("VimSearchAdapter: Confirming search");
226        self.manager.confirm_search(dataview, viewport)
227    }
228
229    /// Check if the adapter is active (has vim search running)
230    #[must_use]
231    pub fn is_active(&self) -> bool {
232        self.is_active || self.manager.is_active()
233    }
234
235    /// Check if we're currently navigating through search results
236    #[must_use]
237    pub fn is_navigating(&self) -> bool {
238        self.manager.is_navigating()
239    }
240
241    /// Get the current search pattern
242    #[must_use]
243    pub fn get_pattern(&self) -> Option<String> {
244        self.manager.get_pattern()
245    }
246
247    /// Get match information (current, total)
248    #[must_use]
249    pub fn get_match_info(&self) -> Option<(usize, usize)> {
250        self.manager.get_match_info()
251    }
252
253    /// Cancel the current search
254    pub fn cancel_search(&mut self) {
255        info!("VimSearchAdapter: Cancelling search");
256        self.manager.cancel_search();
257        self.is_active = false;
258    }
259
260    /// Exit navigation mode
261    pub fn exit_navigation(&mut self) {
262        info!("VimSearchAdapter: Exiting navigation");
263        self.manager.exit_navigation();
264    }
265
266    /// Mark search as complete (after Apply/Enter)
267    /// Keeps matches for n/N navigation and stays active
268    pub fn mark_search_complete(&mut self) {
269        info!("VimSearchAdapter: Marking search as complete, keeping matches for navigation");
270        // Keep is_active = true so n/N continue to work
271        // The manager stays active with matches
272        // Only clear() or cancel_search() will deactivate
273    }
274
275    /// Navigate to next match
276    pub fn next_match(
277        &mut self,
278        viewport: &mut ViewportManager,
279    ) -> Option<crate::ui::search::vim_search_manager::SearchMatch> {
280        self.manager.next_match(viewport)
281    }
282
283    /// Navigate to previous match  
284    pub fn previous_match(
285        &mut self,
286        viewport: &mut ViewportManager,
287    ) -> Option<crate::ui::search::vim_search_manager::SearchMatch> {
288        self.manager.previous_match(viewport)
289    }
290
291    /// Set search state from external source (for compatibility)
292    pub fn set_search_state_from_external(
293        &mut self,
294        pattern: String,
295        matches: Vec<(usize, usize)>,
296        dataview: &DataView,
297    ) {
298        self.manager
299            .set_search_state_from_external(pattern, matches, dataview);
300        self.is_active = true; // Activate when search state is set externally
301    }
302
303    /// Resume the last search
304    pub fn resume_last_search(
305        &mut self,
306        dataview: &DataView,
307        viewport: &mut ViewportManager,
308    ) -> bool {
309        let result = self.manager.resume_last_search(dataview, viewport);
310        if result {
311            self.is_active = true;
312        }
313        result
314    }
315
316    /// Reset to the first match
317    pub fn reset_to_first_match(
318        &mut self,
319        viewport: &mut ViewportManager,
320    ) -> Option<crate::ui::search::vim_search_manager::SearchMatch> {
321        self.manager.reset_to_first_match(viewport)
322    }
323}
324
325impl StateSubscriber for VimSearchAdapter {
326    fn on_state_event(&mut self, event: &StateEvent, buffer: &Buffer) {
327        match event {
328            StateEvent::SearchStarted { search_type } => {
329                if matches!(search_type, SearchType::Vim) {
330                    info!("VimSearchAdapter: Activating for vim search");
331                    self.is_active = true;
332                    self.manager.start_search();
333                }
334            }
335
336            StateEvent::SearchEnded { search_type } => {
337                if matches!(search_type, SearchType::Vim) {
338                    info!("VimSearchAdapter: Search ended, clearing");
339                    self.clear();
340                }
341            }
342
343            StateEvent::ModeChanged { from: _, to } => {
344                // If we exit to Results mode and search is empty, clear
345                if *to == AppMode::Results
346                    && buffer.search_state.pattern.is_empty()
347                    && self.is_active
348                {
349                    info!("VimSearchAdapter: Mode changed to Results with empty search, clearing");
350                    self.clear();
351                }
352
353                // If we enter Search mode, activate
354                if *to == AppMode::Search {
355                    info!("VimSearchAdapter: Mode changed to Search, activating");
356                    self.is_active = true;
357                    if !self.manager.is_active() {
358                        self.manager.start_search();
359                    }
360                }
361            }
362
363            _ => {}
364        }
365    }
366
367    fn name(&self) -> &'static str {
368        "VimSearchAdapter"
369    }
370}