sql_cli/
history_manager.rs

1use crate::history::{CommandHistory, HistoryMatch};
2
3/// Manages history search and navigation
4pub struct HistoryManager {
5    pub search_query: String,
6    pub matches: Vec<HistoryMatch>,
7    pub selected_index: usize,
8}
9
10impl HistoryManager {
11    #[must_use]
12    pub fn new() -> Self {
13        Self {
14            search_query: String::new(),
15            matches: Vec::new(),
16            selected_index: 0,
17        }
18    }
19
20    /// Update matches based on current search query
21    pub fn update_matches(&mut self, history: &CommandHistory) {
22        if self.search_query.is_empty() {
23            // Show all history entries when no search query
24            self.matches = history
25                .get_session_entries()
26                .iter()
27                .map(|entry| HistoryMatch {
28                    entry: entry.clone(),
29                    score: 100,
30                    indices: Vec::new(),
31                })
32                .collect();
33        } else {
34            // Perform fuzzy search
35            self.matches = history.search(&self.search_query);
36        }
37
38        // Reset selection if out of bounds
39        if self.selected_index >= self.matches.len() && !self.matches.is_empty() {
40            self.selected_index = self.matches.len() - 1;
41        }
42    }
43
44    /// Navigate to next match
45    pub fn next_match(&mut self) {
46        if !self.matches.is_empty() {
47            self.selected_index = (self.selected_index + 1) % self.matches.len();
48        }
49    }
50
51    /// Navigate to previous match
52    pub fn previous_match(&mut self) {
53        if !self.matches.is_empty() {
54            if self.selected_index == 0 {
55                self.selected_index = self.matches.len() - 1;
56            } else {
57                self.selected_index -= 1;
58            }
59        }
60    }
61
62    /// Get currently selected match
63    #[must_use]
64    pub fn get_selected(&self) -> Option<&HistoryMatch> {
65        if self.selected_index < self.matches.len() {
66            self.matches.get(self.selected_index)
67        } else {
68            None
69        }
70    }
71
72    /// Clear search and reset state
73    pub fn clear(&mut self) {
74        self.search_query.clear();
75        self.matches.clear();
76        self.selected_index = 0;
77    }
78
79    /// Set search query and update matches
80    pub fn set_search(&mut self, query: String, history: &CommandHistory) {
81        self.search_query = query;
82        self.update_matches(history);
83    }
84
85    /// Get visible range of matches for rendering
86    #[must_use]
87    pub fn get_visible_range(&self, height: usize) -> (usize, usize) {
88        if self.matches.is_empty() {
89            return (0, 0);
90        }
91
92        let total = self.matches.len();
93        let half_height = height / 2;
94
95        // Calculate start index to keep selected item centered
96        let start = if self.selected_index <= half_height {
97            0
98        } else if self.selected_index + half_height >= total {
99            total.saturating_sub(height)
100        } else {
101            self.selected_index - half_height
102        };
103
104        let end = (start + height).min(total);
105        (start, end)
106    }
107}
108
109impl Default for HistoryManager {
110    fn default() -> Self {
111        Self::new()
112    }
113}