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