search_history.rs

 1use std::collections::VecDeque;
 2
 3/// Determines the behavior to use when inserting a new query into the search history.
 4#[derive(Default, Debug, Clone, PartialEq)]
 5pub enum QueryInsertionBehavior {
 6    #[default]
 7    /// Always insert the query to the search history.
 8    AlwaysInsert,
 9    /// Replace the previous query in the search history, if the new query contains the previous query.
10    ReplacePreviousIfContains,
11}
12
13/// A cursor that stores an index to the currently selected query in the search history.
14/// This can be passed to the search history to update the selection accordingly,
15/// e.g. when using the up and down arrow keys to navigate the search history.
16///
17/// Note: The cursor can point to the wrong query, if the maximum length of the history is exceeded
18/// and the old query is overwritten.
19#[derive(Default, Debug, Clone, PartialEq, Eq, Hash)]
20pub struct SearchHistoryCursor {
21    selection: Option<usize>,
22}
23
24impl SearchHistoryCursor {
25    /// Resets the selection to `None`.
26    pub fn reset(&mut self) {
27        self.selection = None;
28    }
29}
30
31#[derive(Debug, Clone)]
32pub struct SearchHistory {
33    history: VecDeque<String>,
34    max_history_len: Option<usize>,
35    insertion_behavior: QueryInsertionBehavior,
36}
37
38impl SearchHistory {
39    pub fn new(max_history_len: Option<usize>, insertion_behavior: QueryInsertionBehavior) -> Self {
40        SearchHistory {
41            max_history_len,
42            insertion_behavior,
43            history: VecDeque::new(),
44        }
45    }
46
47    pub fn add(&mut self, cursor: &mut SearchHistoryCursor, search_string: String) {
48        if self.insertion_behavior == QueryInsertionBehavior::ReplacePreviousIfContains
49            && let Some(previously_searched) = self.history.back_mut()
50            && search_string.contains(previously_searched.as_str())
51        {
52            *previously_searched = search_string;
53            cursor.selection = Some(self.history.len() - 1);
54            return;
55        }
56
57        if let Some(max_history_len) = self.max_history_len
58            && self.history.len() >= max_history_len
59        {
60            self.history.pop_front();
61        }
62        self.history.push_back(search_string);
63
64        cursor.selection = Some(self.history.len() - 1);
65    }
66
67    pub fn next(&mut self, cursor: &mut SearchHistoryCursor) -> Option<&str> {
68        let selected = cursor.selection?;
69        let next_index = selected + 1;
70
71        let next = self.history.get(next_index)?;
72        cursor.selection = Some(next_index);
73        Some(next)
74    }
75
76    pub fn current(&self, cursor: &SearchHistoryCursor) -> Option<&str> {
77        cursor
78            .selection
79            .and_then(|selected_ix| self.history.get(selected_ix).map(|s| s.as_str()))
80    }
81
82    /// Get the previous history entry using the given `SearchHistoryCursor`.
83    /// Uses the last element in the history when there is no cursor.
84    pub fn previous(&mut self, cursor: &mut SearchHistoryCursor) -> Option<&str> {
85        let prev_index = match cursor.selection {
86            Some(index) => index.checked_sub(1)?,
87            None => self.history.len().checked_sub(1)?,
88        };
89
90        let previous = self.history.get(prev_index)?;
91        cursor.selection = Some(prev_index);
92        Some(previous)
93    }
94
95    pub fn len(&self) -> usize {
96        self.history.len()
97    }
98}