Move SearchMode and SearchHistory to separate modules

Piotr Osiewicz created

Change summary

crates/search/src/buffer_search.rs  |   5 
crates/search/src/history.rs        | 184 +++++++++++++++++++++++++++++
crates/search/src/mode.rs           |  73 +++++++++++
crates/search/src/project_search.rs |  86 -------------
crates/search/src/search.rs         | 194 ------------------------------
5 files changed, 269 insertions(+), 273 deletions(-)

Detailed changes

crates/search/src/buffer_search.rs 🔗

@@ -1,6 +1,6 @@
 use crate::{
-    NextHistoryQuery, PreviousHistoryQuery, SearchHistory, SearchOptions, SelectAllMatches,
-    SelectNextMatch, SelectPrevMatch, ToggleCaseSensitive, ToggleRegex, ToggleWholeWord,
+    history::SearchHistory, NextHistoryQuery, PreviousHistoryQuery, SearchOptions,
+    SelectAllMatches, SelectNextMatch, SelectPrevMatch, ToggleCaseSensitive, ToggleWholeWord,
 };
 use collections::HashMap;
 use editor::Editor;
@@ -50,7 +50,6 @@ pub fn init(cx: &mut AppContext) {
     cx.add_action(BufferSearchBar::previous_history_query);
     add_toggle_option_action::<ToggleCaseSensitive>(SearchOptions::CASE_SENSITIVE, cx);
     add_toggle_option_action::<ToggleWholeWord>(SearchOptions::WHOLE_WORD, cx);
-    add_toggle_option_action::<ToggleRegex>(SearchOptions::REGEX, cx);
 }
 
 fn add_toggle_option_action<A: Action>(option: SearchOptions, cx: &mut AppContext) {

crates/search/src/history.rs 🔗

@@ -0,0 +1,184 @@
+use smallvec::SmallVec;
+const SEARCH_HISTORY_LIMIT: usize = 20;
+
+#[derive(Default, Debug, Clone)]
+pub struct SearchHistory {
+    history: SmallVec<[String; SEARCH_HISTORY_LIMIT]>,
+    selected: Option<usize>,
+}
+
+impl SearchHistory {
+    pub fn add(&mut self, search_string: String) {
+        if let Some(i) = self.selected {
+            if search_string == self.history[i] {
+                return;
+            }
+        }
+
+        if let Some(previously_searched) = self.history.last_mut() {
+            if search_string.find(previously_searched.as_str()).is_some() {
+                *previously_searched = search_string;
+                self.selected = Some(self.history.len() - 1);
+                return;
+            }
+        }
+
+        self.history.push(search_string);
+        if self.history.len() > SEARCH_HISTORY_LIMIT {
+            self.history.remove(0);
+        }
+        self.selected = Some(self.history.len() - 1);
+    }
+
+    pub fn next(&mut self) -> Option<&str> {
+        let history_size = self.history.len();
+        if history_size == 0 {
+            return None;
+        }
+
+        let selected = self.selected?;
+        if selected == history_size - 1 {
+            return None;
+        }
+        let next_index = selected + 1;
+        self.selected = Some(next_index);
+        Some(&self.history[next_index])
+    }
+
+    pub fn current(&self) -> Option<&str> {
+        Some(&self.history[self.selected?])
+    }
+
+    pub fn previous(&mut self) -> Option<&str> {
+        let history_size = self.history.len();
+        if history_size == 0 {
+            return None;
+        }
+
+        let prev_index = match self.selected {
+            Some(selected_index) => {
+                if selected_index == 0 {
+                    return None;
+                } else {
+                    selected_index - 1
+                }
+            }
+            None => history_size - 1,
+        };
+
+        self.selected = Some(prev_index);
+        Some(&self.history[prev_index])
+    }
+
+    pub fn reset_selection(&mut self) {
+        self.selected = None;
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn test_add() {
+        let mut search_history = SearchHistory::default();
+        assert_eq!(
+            search_history.current(),
+            None,
+            "No current selection should be set fo the default search history"
+        );
+
+        search_history.add("rust".to_string());
+        assert_eq!(
+            search_history.current(),
+            Some("rust"),
+            "Newly added item should be selected"
+        );
+
+        // check if duplicates are not added
+        search_history.add("rust".to_string());
+        assert_eq!(
+            search_history.history.len(),
+            1,
+            "Should not add a duplicate"
+        );
+        assert_eq!(search_history.current(), Some("rust"));
+
+        // check if new string containing the previous string replaces it
+        search_history.add("rustlang".to_string());
+        assert_eq!(
+            search_history.history.len(),
+            1,
+            "Should replace previous item if it's a substring"
+        );
+        assert_eq!(search_history.current(), Some("rustlang"));
+
+        // push enough items to test SEARCH_HISTORY_LIMIT
+        for i in 0..SEARCH_HISTORY_LIMIT * 2 {
+            search_history.add(format!("item{i}"));
+        }
+        assert!(search_history.history.len() <= SEARCH_HISTORY_LIMIT);
+    }
+
+    #[test]
+    fn test_next_and_previous() {
+        let mut search_history = SearchHistory::default();
+        assert_eq!(
+            search_history.next(),
+            None,
+            "Default search history should not have a next item"
+        );
+
+        search_history.add("Rust".to_string());
+        assert_eq!(search_history.next(), None);
+        search_history.add("JavaScript".to_string());
+        assert_eq!(search_history.next(), None);
+        search_history.add("TypeScript".to_string());
+        assert_eq!(search_history.next(), None);
+
+        assert_eq!(search_history.current(), Some("TypeScript"));
+
+        assert_eq!(search_history.previous(), Some("JavaScript"));
+        assert_eq!(search_history.current(), Some("JavaScript"));
+
+        assert_eq!(search_history.previous(), Some("Rust"));
+        assert_eq!(search_history.current(), Some("Rust"));
+
+        assert_eq!(search_history.previous(), None);
+        assert_eq!(search_history.current(), Some("Rust"));
+
+        assert_eq!(search_history.next(), Some("JavaScript"));
+        assert_eq!(search_history.current(), Some("JavaScript"));
+
+        assert_eq!(search_history.next(), Some("TypeScript"));
+        assert_eq!(search_history.current(), Some("TypeScript"));
+
+        assert_eq!(search_history.next(), None);
+        assert_eq!(search_history.current(), Some("TypeScript"));
+    }
+
+    #[test]
+    fn test_reset_selection() {
+        let mut search_history = SearchHistory::default();
+        search_history.add("Rust".to_string());
+        search_history.add("JavaScript".to_string());
+        search_history.add("TypeScript".to_string());
+
+        assert_eq!(search_history.current(), Some("TypeScript"));
+        search_history.reset_selection();
+        assert_eq!(search_history.current(), None);
+        assert_eq!(
+            search_history.previous(),
+            Some("TypeScript"),
+            "Should start from the end after reset on previous item query"
+        );
+
+        search_history.previous();
+        assert_eq!(search_history.current(), Some("JavaScript"));
+        search_history.previous();
+        assert_eq!(search_history.current(), Some("Rust"));
+
+        search_history.reset_selection();
+        assert_eq!(search_history.current(), None);
+    }
+}

crates/search/src/mode.rs 🔗

@@ -0,0 +1,73 @@
+use gpui::Action;
+
+use crate::{ActivateRegexMode, ActivateSemanticMode, ActivateTextMode};
+// TODO: Update the default search mode to get from config
+#[derive(Copy, Clone, Default, PartialEq)]
+pub(crate) enum SearchMode {
+    #[default]
+    Text,
+    Semantic,
+    Regex,
+}
+
+#[derive(Copy, Clone, Debug, PartialEq)]
+pub(crate) enum Side {
+    Left,
+    Right,
+}
+
+impl SearchMode {
+    pub(crate) fn label(&self) -> &'static str {
+        match self {
+            SearchMode::Text => "Text",
+            SearchMode::Semantic => "Semantic",
+            SearchMode::Regex => "Regex",
+        }
+    }
+
+    pub(crate) fn region_id(&self) -> usize {
+        match self {
+            SearchMode::Text => 3,
+            SearchMode::Semantic => 4,
+            SearchMode::Regex => 5,
+        }
+    }
+
+    pub(crate) fn tooltip_text(&self) -> &'static str {
+        match self {
+            SearchMode::Text => "Activate Text Search",
+            SearchMode::Semantic => "Activate Semantic Search",
+            SearchMode::Regex => "Activate Regex Search",
+        }
+    }
+
+    pub(crate) fn activate_action(&self) -> Box<dyn Action> {
+        match self {
+            SearchMode::Text => Box::new(ActivateTextMode),
+            SearchMode::Semantic => Box::new(ActivateSemanticMode),
+            SearchMode::Regex => Box::new(ActivateRegexMode),
+        }
+    }
+
+    pub(crate) fn border_left(&self) -> bool {
+        match self {
+            SearchMode::Text => false,
+            _ => true,
+        }
+    }
+
+    pub(crate) fn border_right(&self) -> bool {
+        match self {
+            SearchMode::Regex => false,
+            _ => true,
+        }
+    }
+
+    pub(crate) fn button_side(&self) -> Option<Side> {
+        match self {
+            SearchMode::Text => Some(Side::Left),
+            SearchMode::Semantic => None,
+            SearchMode::Regex => Some(Side::Right),
+        }
+    }
+}

crates/search/src/project_search.rs 🔗

@@ -1,5 +1,7 @@
 use crate::{
-    NextHistoryQuery, PreviousHistoryQuery, SearchHistory, SearchOptions, SelectNextMatch,
+    history::SearchHistory,
+    mode::{SearchMode, Side},
+    CycleMode, NextHistoryQuery, PreviousHistoryQuery, SearchOptions, SelectNextMatch,
     SelectPrevMatch, ToggleCaseSensitive, ToggleWholeWord,
 };
 use anyhow::{Context, Result};
@@ -49,16 +51,7 @@ use workspace::{
 
 actions!(
     project_search,
-    [
-        SearchInNew,
-        ToggleFocus,
-        NextField,
-        CycleMode,
-        ToggleFilters,
-        ActivateTextMode,
-        ActivateSemanticMode,
-        ActivateRegexMode
-    ]
+    [SearchInNew, ToggleFocus, NextField, ToggleFilters,]
 );
 
 #[derive(Default)]
@@ -147,77 +140,6 @@ struct SemanticSearchState {
     _progress_task: Task<()>,
 }
 
-// TODO: Update the default search mode to get from config
-#[derive(Copy, Clone, Default, PartialEq)]
-enum SearchMode {
-    #[default]
-    Text,
-    Semantic,
-    Regex,
-}
-
-#[derive(Copy, Clone, Debug, PartialEq)]
-enum Side {
-    Left,
-    Right,
-}
-
-impl SearchMode {
-    fn label(&self) -> &'static str {
-        match self {
-            SearchMode::Text => "Text",
-            SearchMode::Semantic => "Semantic",
-            SearchMode::Regex => "Regex",
-        }
-    }
-
-    fn region_id(&self) -> usize {
-        match self {
-            SearchMode::Text => 3,
-            SearchMode::Semantic => 4,
-            SearchMode::Regex => 5,
-        }
-    }
-
-    fn tooltip_text(&self) -> &'static str {
-        match self {
-            SearchMode::Text => "Activate Text Search",
-            SearchMode::Semantic => "Activate Semantic Search",
-            SearchMode::Regex => "Activate Regex Search",
-        }
-    }
-
-    fn activate_action(&self) -> Box<dyn Action> {
-        match self {
-            SearchMode::Text => Box::new(ActivateTextMode),
-            SearchMode::Semantic => Box::new(ActivateSemanticMode),
-            SearchMode::Regex => Box::new(ActivateRegexMode),
-        }
-    }
-
-    fn border_left(&self) -> bool {
-        match self {
-            SearchMode::Text => false,
-            _ => true,
-        }
-    }
-
-    fn border_right(&self) -> bool {
-        match self {
-            SearchMode::Regex => false,
-            _ => true,
-        }
-    }
-
-    fn button_side(&self) -> Option<Side> {
-        match self {
-            SearchMode::Text => Some(Side::Left),
-            SearchMode::Semantic => None,
-            SearchMode::Regex => Some(Side::Right),
-        }
-    }
-}
-
 pub struct ProjectSearchBar {
     active_project_search: Option<ViewHandle<ProjectSearchView>>,
     subscription: Option<Subscription>,

crates/search/src/search.rs 🔗

@@ -3,9 +3,10 @@ pub use buffer_search::BufferSearchBar;
 use gpui::{actions, Action, AppContext};
 use project::search::SearchQuery;
 pub use project_search::{ProjectSearchBar, ProjectSearchView};
-use smallvec::SmallVec;
 
 pub mod buffer_search;
+mod history;
+mod mode;
 pub mod project_search;
 pub(crate) mod search_bar;
 
@@ -17,14 +18,17 @@ pub fn init(cx: &mut AppContext) {
 actions!(
     search,
     [
+        CycleMode,
         ToggleWholeWord,
         ToggleCaseSensitive,
-        ToggleRegex,
         SelectNextMatch,
         SelectPrevMatch,
         SelectAllMatches,
         NextHistoryQuery,
         PreviousHistoryQuery,
+        ActivateTextMode,
+        ActivateSemanticMode,
+        ActivateRegexMode
     ]
 );
 
@@ -43,7 +47,6 @@ impl SearchOptions {
         match *self {
             SearchOptions::WHOLE_WORD => "Match Whole Word",
             SearchOptions::CASE_SENSITIVE => "Match Case",
-            SearchOptions::REGEX => "Use Regular Expression",
             _ => panic!("{:?} is not a named SearchOption", self),
         }
     }
@@ -52,7 +55,6 @@ impl SearchOptions {
         match *self {
             SearchOptions::WHOLE_WORD => Box::new(ToggleWholeWord),
             SearchOptions::CASE_SENSITIVE => Box::new(ToggleCaseSensitive),
-            SearchOptions::REGEX => Box::new(ToggleRegex),
             _ => panic!("{:?} is not a named SearchOption", self),
         }
     }
@@ -69,187 +71,3 @@ impl SearchOptions {
         options
     }
 }
-
-const SEARCH_HISTORY_LIMIT: usize = 20;
-
-#[derive(Default, Debug, Clone)]
-pub struct SearchHistory {
-    history: SmallVec<[String; SEARCH_HISTORY_LIMIT]>,
-    selected: Option<usize>,
-}
-
-impl SearchHistory {
-    pub fn add(&mut self, search_string: String) {
-        if let Some(i) = self.selected {
-            if search_string == self.history[i] {
-                return;
-            }
-        }
-
-        if let Some(previously_searched) = self.history.last_mut() {
-            if search_string.find(previously_searched.as_str()).is_some() {
-                *previously_searched = search_string;
-                self.selected = Some(self.history.len() - 1);
-                return;
-            }
-        }
-
-        self.history.push(search_string);
-        if self.history.len() > SEARCH_HISTORY_LIMIT {
-            self.history.remove(0);
-        }
-        self.selected = Some(self.history.len() - 1);
-    }
-
-    pub fn next(&mut self) -> Option<&str> {
-        let history_size = self.history.len();
-        if history_size == 0 {
-            return None;
-        }
-
-        let selected = self.selected?;
-        if selected == history_size - 1 {
-            return None;
-        }
-        let next_index = selected + 1;
-        self.selected = Some(next_index);
-        Some(&self.history[next_index])
-    }
-
-    pub fn current(&self) -> Option<&str> {
-        Some(&self.history[self.selected?])
-    }
-
-    pub fn previous(&mut self) -> Option<&str> {
-        let history_size = self.history.len();
-        if history_size == 0 {
-            return None;
-        }
-
-        let prev_index = match self.selected {
-            Some(selected_index) => {
-                if selected_index == 0 {
-                    return None;
-                } else {
-                    selected_index - 1
-                }
-            }
-            None => history_size - 1,
-        };
-
-        self.selected = Some(prev_index);
-        Some(&self.history[prev_index])
-    }
-
-    pub fn reset_selection(&mut self) {
-        self.selected = None;
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn test_add() {
-        let mut search_history = SearchHistory::default();
-        assert_eq!(
-            search_history.current(),
-            None,
-            "No current selection should be set fo the default search history"
-        );
-
-        search_history.add("rust".to_string());
-        assert_eq!(
-            search_history.current(),
-            Some("rust"),
-            "Newly added item should be selected"
-        );
-
-        // check if duplicates are not added
-        search_history.add("rust".to_string());
-        assert_eq!(
-            search_history.history.len(),
-            1,
-            "Should not add a duplicate"
-        );
-        assert_eq!(search_history.current(), Some("rust"));
-
-        // check if new string containing the previous string replaces it
-        search_history.add("rustlang".to_string());
-        assert_eq!(
-            search_history.history.len(),
-            1,
-            "Should replace previous item if it's a substring"
-        );
-        assert_eq!(search_history.current(), Some("rustlang"));
-
-        // push enough items to test SEARCH_HISTORY_LIMIT
-        for i in 0..SEARCH_HISTORY_LIMIT * 2 {
-            search_history.add(format!("item{i}"));
-        }
-        assert!(search_history.history.len() <= SEARCH_HISTORY_LIMIT);
-    }
-
-    #[test]
-    fn test_next_and_previous() {
-        let mut search_history = SearchHistory::default();
-        assert_eq!(
-            search_history.next(),
-            None,
-            "Default search history should not have a next item"
-        );
-
-        search_history.add("Rust".to_string());
-        assert_eq!(search_history.next(), None);
-        search_history.add("JavaScript".to_string());
-        assert_eq!(search_history.next(), None);
-        search_history.add("TypeScript".to_string());
-        assert_eq!(search_history.next(), None);
-
-        assert_eq!(search_history.current(), Some("TypeScript"));
-
-        assert_eq!(search_history.previous(), Some("JavaScript"));
-        assert_eq!(search_history.current(), Some("JavaScript"));
-
-        assert_eq!(search_history.previous(), Some("Rust"));
-        assert_eq!(search_history.current(), Some("Rust"));
-
-        assert_eq!(search_history.previous(), None);
-        assert_eq!(search_history.current(), Some("Rust"));
-
-        assert_eq!(search_history.next(), Some("JavaScript"));
-        assert_eq!(search_history.current(), Some("JavaScript"));
-
-        assert_eq!(search_history.next(), Some("TypeScript"));
-        assert_eq!(search_history.current(), Some("TypeScript"));
-
-        assert_eq!(search_history.next(), None);
-        assert_eq!(search_history.current(), Some("TypeScript"));
-    }
-
-    #[test]
-    fn test_reset_selection() {
-        let mut search_history = SearchHistory::default();
-        search_history.add("Rust".to_string());
-        search_history.add("JavaScript".to_string());
-        search_history.add("TypeScript".to_string());
-
-        assert_eq!(search_history.current(), Some("TypeScript"));
-        search_history.reset_selection();
-        assert_eq!(search_history.current(), None);
-        assert_eq!(
-            search_history.previous(),
-            Some("TypeScript"),
-            "Should start from the end after reset on previous item query"
-        );
-
-        search_history.previous();
-        assert_eq!(search_history.current(), Some("JavaScript"));
-        search_history.previous();
-        assert_eq!(search_history.current(), Some("Rust"));
-
-        search_history.reset_selection();
-        assert_eq!(search_history.current(), None);
-    }
-}