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