summaryrefslogtreecommitdiffstats
path: root/src/core/src/modules/list/search/state.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/core/src/modules/list/search/state.rs')
-rw-r--r--src/core/src/modules/list/search/state.rs235
1 files changed, 235 insertions, 0 deletions
diff --git a/src/core/src/modules/list/search/state.rs b/src/core/src/modules/list/search/state.rs
new file mode 100644
index 0000000..0c6d483
--- /dev/null
+++ b/src/core/src/modules/list/search/state.rs
@@ -0,0 +1,235 @@
+use std::collections::HashMap;
+
+use todo_file::Version;
+
+use super::line_match::LineMatch;
+use crate::search::SearchState;
+
+/// Input thread state.
+#[derive(Clone, Debug)]
+pub(crate) struct State {
+ match_indexes: HashMap<usize, usize>,
+ match_start_hint: usize,
+ matches: Vec<LineMatch>,
+ search_term: String,
+ selected: Option<usize>,
+ state: SearchState,
+ todo_file_version: Version,
+}
+
+impl State {
+ pub(crate) fn new() -> Self {
+ Self {
+ match_indexes: HashMap::new(),
+ match_start_hint: 0,
+ matches: vec![],
+ search_term: String::new(),
+ selected: None,
+ state: SearchState::Inactive,
+ todo_file_version: Version::sentinel(),
+ }
+ }
+
+ pub(crate) fn reset(&mut self) {
+ self.match_indexes.clear();
+ self.matches.clear();
+ self.search_term.clear();
+ self.selected = None;
+ self.state = SearchState::Inactive;
+ self.todo_file_version = Version::sentinel();
+ }
+
+ pub(crate) fn try_invalidate_search(&mut self, version: &Version, search_term: &str) -> bool {
+ if &self.todo_file_version != version || self.search_term != search_term {
+ self.search_term = String::from(search_term);
+ self.matches.clear();
+ self.match_indexes.clear();
+ self.todo_file_version = *version;
+ true
+ }
+ else {
+ false
+ }
+ }
+
+ pub(crate) const fn search_state(&self) -> SearchState {
+ self.state
+ }
+
+ pub(crate) fn set_search_state(&mut self, state: SearchState) {
+ self.state = state;
+ }
+
+ pub(crate) fn push_match(&mut self, line_match: LineMatch) -> bool {
+ if line_match.hash() || line_match.content() {
+ _ = self.match_indexes.insert(line_match.index(), self.matches.len());
+ self.matches.push(line_match);
+ true
+ }
+ else {
+ false
+ }
+ }
+
+ pub(crate) const fn matches(&self) -> &Vec<LineMatch> {
+ &self.matches
+ }
+
+ pub(crate) fn number_matches(&self) -> usize {
+ self.matches.len()
+ }
+
+ /// Returns the match value for a line index
+ pub(crate) fn match_value_for_line(&self, index: usize) -> Option<LineMatch> {
+ let search_index = *self.match_indexes.get(&index)?;
+ self.match_value(search_index)
+ }
+
+ /// Returns the match value for a search match index
+ pub(crate) fn match_value(&self, search_index: usize) -> Option<LineMatch> {
+ self.matches.get(search_index).copied()
+ }
+
+ pub(crate) fn set_selected(&mut self, selected: usize) {
+ self.selected = Some(selected);
+ }
+
+ pub(crate) const fn selected(&self) -> Option<usize> {
+ self.selected
+ }
+
+ pub(crate) fn set_match_start_hint(&mut self, hint: usize) {
+ self.match_start_hint = hint;
+ }
+
+ pub(crate) const fn match_start_hint(&self) -> usize {
+ self.match_start_hint
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use claims::{assert_none, assert_some_eq};
+
+ use super::*;
+
+ #[test]
+ fn try_invalidate_search_with_no_change() {
+ let mut state = State::new();
+ assert!(!state.try_invalidate_search(&Version::sentinel(), ""));
+ }
+
+ #[test]
+ fn try_invalidate_search_with_change_in_term() {
+ let mut state = State::new();
+ assert!(state.try_invalidate_search(&Version::sentinel(), "foo"));
+ }
+
+ #[test]
+ fn try_invalidate_search_with_change_in_version() {
+ let mut state = State::new();
+ assert!(state.try_invalidate_search(&Version::new(), ""));
+ }
+
+ #[test]
+ fn try_invalidate_search_resets_state() {
+ let mut state = State::new();
+ state.matches.push(LineMatch::new(1, false, false));
+ _ = state.match_indexes.insert(1, 1);
+ let version = Version::new();
+ assert!(state.try_invalidate_search(&version, "foo"));
+ assert_eq!(state.search_term, "foo");
+ assert!(state.matches().is_empty());
+ assert!(state.match_indexes.is_empty());
+ assert_eq!(state.todo_file_version, version);
+ }
+
+ #[test]
+ fn search_state() {
+ let mut state = State::new();
+ state.set_search_state(SearchState::Active);
+ assert_eq!(state.search_state(), SearchState::Active);
+ }
+
+ #[test]
+ fn push_match_with_hash_match() {
+ let mut state = State::new();
+ assert!(state.push_match(LineMatch::new(1, true, false)));
+ assert!(!state.matches().is_empty());
+ assert_eq!(state.number_matches(), 1);
+ }
+
+ #[test]
+ fn push_match_with_content_match() {
+ let mut state = State::new();
+ assert!(state.push_match(LineMatch::new(1, false, true)));
+ assert!(!state.matches().is_empty());
+ assert_eq!(state.number_matches(), 1);
+ }
+
+ #[test]
+ fn push_match_with_hash_and_content_match() {
+ let mut state = State::new();
+ assert!(state.push_match(LineMatch::new(1, true, true)));
+ assert!(!state.matches().is_empty());
+ assert_eq!(state.number_matches(), 1);
+ }
+
+ #[test]
+ fn push_match_with_no_hash_and_no_content_match() {
+ let mut state = State::new();
+ assert!(!state.push_match(LineMatch::new(1, false, false)));
+ assert!(state.matches().is_empty());
+ assert_eq!(state.number_matches(), 0);
+ }
+
+ #[test]
+ fn match_value_for_line_index_miss() {
+ let mut state = State::new();
+ assert!(state.push_match(LineMatch::new(1, false, true)));
+ assert_none!(state.match_value_for_line(99));
+ }
+
+ #[test]
+ fn match_value_for_line_index_hit() {
+ let mut state = State::new();
+ let line_match = LineMatch::new(1, false, true);
+ assert!(state.push_match(line_match));
+ assert_some_eq!(state.match_value_for_line(1), line_match);
+ }
+
+ #[test]
+ fn match_value_miss() {
+ let mut state = State::new();
+ assert!(state.push_match(LineMatch::new(1, false, true)));
+ assert_none!(state.match_value(99));
+ }
+
+ #[test]
+ fn match_value_hit() {
+ let mut state = State::new();
+ let line_match = LineMatch::new(1, false, true);
+ assert!(state.push_match(line_match));
+ assert_some_eq!(state.match_value(0), line_match);
+ }
+
+ #[test]
+ fn selected_set() {
+ let mut state = State::new();
+ state.set_selected(42);
+ assert_some_eq!(state.selected(), 42);
+ }
+
+ #[test]
+ fn selected_not_set() {
+ let mut state = State::new();
+ assert_none!(state.selected());
+ }
+
+ #[test]
+ fn match_start_hint() {
+ let mut state = State::new();
+ state.set_match_start_hint(42);
+ assert_eq!(state.match_start_hint(), 42);
+ }
+}