sql_cli/ui/search/
search_operations.rs

1//! Search and filter operations
2//!
3//! This module contains search and filter operations extracted from the monolithic TUI
4//! to improve maintainability and testability. The search system is complex with multiple
5//! modes and state dependencies, so we start with extracting coordination logic.
6
7use crate::app_state_container::AppStateContainer;
8use crate::buffer::BufferAPI;
9use crate::data::data_view::DataView;
10use crate::widgets::search_modes_widget::SearchMode;
11// Arc import removed - no longer needed
12
13/// Context for search operations
14/// Provides the minimal interface needed for search operations
15pub struct SearchContext<'a> {
16    pub state_container: &'a AppStateContainer,
17    pub buffer: &'a mut dyn BufferAPI,
18    pub current_data: Option<&'a DataView>,
19}
20
21/// Result of a search operation
22#[derive(Debug, Clone, PartialEq)]
23pub struct SearchResult {
24    pub matches_found: usize,
25    pub first_match: Option<(usize, usize)>, // (row, col)
26    pub status_message: String,
27}
28
29/// Result of search action execution
30#[derive(Debug, Clone, PartialEq)]
31pub enum SearchActionResult {
32    /// Search was executed successfully
33    Success(SearchResult),
34    /// Search was started but needs follow-up action
35    InProgress(String),
36    /// Search failed
37    Error(String),
38}
39
40/// Execute a search action based on mode and pattern
41/// This coordinates the search process but delegates the actual searching
42pub fn execute_search_action(
43    mode: SearchMode,
44    pattern: String,
45    ctx: &mut SearchContext,
46) -> SearchActionResult {
47    match mode {
48        SearchMode::Search => {
49            // Set search pattern in state container
50            ctx.state_container.start_search(pattern.clone());
51            ctx.buffer.set_search_pattern(pattern);
52
53            // Perform the actual search (this will need to be extracted separately)
54            let search_result = perform_search_with_context(ctx);
55
56            match search_result {
57                Ok(result) => SearchActionResult::Success(result),
58                Err(e) => SearchActionResult::Error(e),
59            }
60        }
61        SearchMode::Filter => {
62            SearchActionResult::InProgress(format!("Filter mode with pattern: {}", pattern))
63        }
64        SearchMode::FuzzyFilter => {
65            SearchActionResult::InProgress(format!("Fuzzy filter mode with pattern: {}", pattern))
66        }
67        SearchMode::ColumnSearch => {
68            SearchActionResult::InProgress(format!("Column search mode with pattern: {}", pattern))
69        }
70    }
71}
72
73/// Perform search with the given context
74/// This is where the actual search logic will eventually be extracted
75fn perform_search_with_context(ctx: &mut SearchContext) -> Result<SearchResult, String> {
76    if let Some(dataview) = ctx.current_data {
77        // Convert DataView rows to Vec<Vec<String>> for search
78        let data: Vec<Vec<String>> = (0..dataview.row_count())
79            .filter_map(|i| dataview.get_row(i))
80            .map(|row| row.values.iter().map(|v| v.to_string()).collect())
81            .collect();
82
83        // Perform search using AppStateContainer
84        let matches = ctx.state_container.perform_search(&data);
85
86        // Convert matches to our format
87        let buffer_matches: Vec<(usize, usize)> = matches
88            .iter()
89            .map(|(row, col, _, _)| (*row, *col))
90            .collect();
91
92        if !buffer_matches.is_empty() {
93            let first_match = buffer_matches[0];
94
95            // Update buffer state
96            ctx.buffer.set_search_matches(buffer_matches.clone());
97            ctx.buffer.set_search_match_index(0);
98            ctx.buffer.set_current_match(Some(first_match));
99
100            Ok(SearchResult {
101                matches_found: buffer_matches.len(),
102                first_match: Some(first_match),
103                status_message: format!("Found {} matches", buffer_matches.len()),
104            })
105        } else {
106            ctx.buffer.set_search_matches(Vec::new());
107
108            Ok(SearchResult {
109                matches_found: 0,
110                first_match: None,
111                status_message: "No matches found".to_string(),
112            })
113        }
114    } else {
115        Err("No data available for search".to_string())
116    }
117}
118
119/// Apply search result to UI state
120pub fn apply_search_result(result: &SearchResult, ctx: &mut SearchContext) {
121    if let Some((row, _col)) = result.first_match {
122        ctx.state_container.set_table_selected_row(Some(row));
123    }
124
125    ctx.buffer.set_status_message(result.status_message.clone());
126}
127
128#[cfg(test)]
129mod tests {
130    use super::*;
131
132    #[test]
133    fn test_search_action_result_types() {
134        // Test that our result types work correctly
135        let success = SearchActionResult::Success(SearchResult {
136            matches_found: 5,
137            first_match: Some((0, 1)),
138            status_message: "Found 5 matches".to_string(),
139        });
140
141        match success {
142            SearchActionResult::Success(result) => {
143                assert_eq!(result.matches_found, 5);
144                assert_eq!(result.first_match, Some((0, 1)));
145                assert_eq!(result.status_message, "Found 5 matches");
146            }
147            _ => panic!("Expected Success result"),
148        }
149
150        let in_progress = SearchActionResult::InProgress("Filtering...".to_string());
151        match in_progress {
152            SearchActionResult::InProgress(msg) => {
153                assert_eq!(msg, "Filtering...");
154            }
155            _ => panic!("Expected InProgress result"),
156        }
157
158        let error = SearchActionResult::Error("No data".to_string());
159        match error {
160            SearchActionResult::Error(msg) => {
161                assert_eq!(msg, "No data");
162            }
163            _ => panic!("Expected Error result"),
164        }
165    }
166
167    #[test]
168    fn test_search_result_creation() {
169        let result = SearchResult {
170            matches_found: 0,
171            first_match: None,
172            status_message: "No matches found".to_string(),
173        };
174
175        assert_eq!(result.matches_found, 0);
176        assert_eq!(result.first_match, None);
177        assert_eq!(result.status_message, "No matches found");
178    }
179}