feat: add pattern items to project search
Update the `search::project_search::ProjectSearchView` implementation in order to allow users to leverage pattern items in the search query. Add a very basic `test_pattern_items` test to `search::project_search` in order to test how the `apply_pattern_items` method affects the `ProjectSearchView.search_options` value depending on the query editor's text. Unfortunately I wasn't having much luck adding a text similar to the one for `BufferSearchBar` where we actually simulate the user's input and keystrokes, so definitely something that can be improved upon.
This commit is contained in:
parent
7f9e589491
commit
d28e52d9e7
2 changed files with 155 additions and 14 deletions
|
@ -1570,7 +1570,7 @@ mod tests {
|
|||
});
|
||||
let search_bar = window.root(cx).unwrap();
|
||||
|
||||
let cx = VisualTestContext::from_window(*window, cx).as_mut();
|
||||
let cx = VisualTestContext::from_window(*window, cx).into_mut();
|
||||
|
||||
(editor.unwrap(), search_bar, cx)
|
||||
}
|
||||
|
@ -2957,7 +2957,7 @@ mod tests {
|
|||
assert_eq!(
|
||||
search_bar.search_options,
|
||||
SearchOptions::REGEX | SearchOptions::CASE_SENSITIVE,
|
||||
"Should have case sensitivity enabled when \\C pattern item is present, even if preceeded by \\c"
|
||||
"Should have case sensitivity enabled when \\C pattern item is present, even if preceded by \\c"
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -2968,7 +2968,7 @@ mod tests {
|
|||
assert_eq!(
|
||||
search_bar.search_options,
|
||||
SearchOptions::REGEX,
|
||||
"Should have no case sensitivity enabled when \\c pattern item is present, even if preceeded by \\C"
|
||||
"Should have no case sensitivity enabled when \\c pattern item is present, even if preceded by \\C"
|
||||
);
|
||||
});
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ use crate::{
|
|||
SearchOption, SearchOptions, SearchSource, SelectNextMatch, SelectPreviousMatch,
|
||||
ToggleCaseSensitive, ToggleIncludeIgnored, ToggleRegex, ToggleReplace, ToggleWholeWord,
|
||||
buffer_search::Deploy,
|
||||
pattern_items::PatternItems,
|
||||
search_bar::{ActionButtonState, input_base_styles, render_action_button, render_text_input},
|
||||
};
|
||||
use anyhow::Context as _;
|
||||
|
@ -210,6 +211,7 @@ pub struct ProjectSearchView {
|
|||
replacement_editor: Entity<Editor>,
|
||||
results_editor: Entity<Editor>,
|
||||
search_options: SearchOptions,
|
||||
pattern_items: PatternItems,
|
||||
panels_with_errors: HashMap<InputPanel, String>,
|
||||
active_match_index: Option<usize>,
|
||||
search_id: usize,
|
||||
|
@ -775,15 +777,17 @@ impl ProjectSearchView {
|
|||
// Subscribe to query_editor in order to reraise editor events for workspace item activation purposes
|
||||
subscriptions.push(
|
||||
cx.subscribe(&query_editor, |this, _, event: &EditorEvent, cx| {
|
||||
if let EditorEvent::Edited { .. } = event
|
||||
&& EditorSettings::get_global(cx).use_smartcase_search
|
||||
{
|
||||
let query = this.search_query_text(cx);
|
||||
if !query.is_empty()
|
||||
&& this.search_options.contains(SearchOptions::CASE_SENSITIVE)
|
||||
!= contains_uppercase(&query)
|
||||
{
|
||||
this.toggle_search_option(SearchOptions::CASE_SENSITIVE, cx);
|
||||
if let EditorEvent::Edited { .. } = event {
|
||||
this.apply_pattern_items(&this.search_query_text_raw(cx));
|
||||
|
||||
if EditorSettings::get_global(cx).use_smartcase_search {
|
||||
let query = this.search_query_text(cx);
|
||||
if !query.is_empty()
|
||||
&& this.search_options.contains(SearchOptions::CASE_SENSITIVE)
|
||||
!= contains_uppercase(&query)
|
||||
{
|
||||
this.toggle_search_option(SearchOptions::CASE_SENSITIVE, cx);
|
||||
}
|
||||
}
|
||||
}
|
||||
cx.emit(ViewEvent::EditorEvent(event.clone()))
|
||||
|
@ -880,6 +884,7 @@ impl ProjectSearchView {
|
|||
query_editor,
|
||||
results_editor,
|
||||
search_options: options,
|
||||
pattern_items: Default::default(),
|
||||
panels_with_errors: HashMap::default(),
|
||||
active_match_index: None,
|
||||
included_files_editor,
|
||||
|
@ -1133,13 +1138,19 @@ impl ProjectSearchView {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn search_query_text(&self, cx: &App) -> String {
|
||||
/// Returns the search query text with pattern items.
|
||||
fn search_query_text_raw(&self, cx: &App) -> String {
|
||||
self.query_editor.read(cx).text(cx)
|
||||
}
|
||||
|
||||
/// Returns the search query text without pattern items.
|
||||
pub fn search_query_text(&self, cx: &App) -> String {
|
||||
PatternItems::clean_query(&self.search_query_text_raw(cx))
|
||||
}
|
||||
|
||||
fn build_search_query(&mut self, cx: &mut Context<Self>) -> Option<SearchQuery> {
|
||||
// Do not bail early in this function, as we want to fill out `self.panels_with_errors`.
|
||||
let text = self.query_editor.read(cx).text(cx);
|
||||
let text = self.search_query_text(cx);
|
||||
let open_buffers = if self.included_opened_only {
|
||||
Some(self.open_buffers(cx))
|
||||
} else {
|
||||
|
@ -1560,6 +1571,21 @@ impl ProjectSearchView {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Determines which pattern items are present in the search query and
|
||||
// updates the search options accordingly, only if the regex search option
|
||||
// is enabled.
|
||||
fn apply_pattern_items(&mut self, query: &str) {
|
||||
if self.search_options.contains(SearchOptions::REGEX) {
|
||||
// Determine what the search options were before the pattern items
|
||||
// were applied, so we can reapply them and determine which ones
|
||||
// actually have an effect on the search options, which are the ones
|
||||
// we need to keep track of.
|
||||
let search_options = self.pattern_items.revert(self.search_options);
|
||||
self.pattern_items = PatternItems::from_search_options(search_options, &query);
|
||||
self.search_options = self.pattern_items.apply(search_options);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn buffer_search_query(
|
||||
|
@ -4126,6 +4152,121 @@ pub mod tests {
|
|||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_pattern_items(cx: &mut TestAppContext) {
|
||||
init_test(cx);
|
||||
|
||||
let fs = FakeFs::new(cx.background_executor.clone());
|
||||
fs.insert_tree(
|
||||
path!("/dir"),
|
||||
json!({
|
||||
"one.rs": "const ONE: usize = 1;",
|
||||
"two.rs": "const TWO: usize = one::ONE + one::ONE;",
|
||||
"three.rs": "const THREE: usize = one::ONE + two::TWO;",
|
||||
"four.rs": "const FOUR: usize = one::ONE + three::THREE;",
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
|
||||
let window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let workspace = window.root(cx).unwrap();
|
||||
|
||||
let project_search = cx.new(|cx| ProjectSearch::new(project.clone(), cx));
|
||||
let project_search_settings = ProjectSearchSettings {
|
||||
filters_enabled: false,
|
||||
search_options: SearchOptions::REGEX,
|
||||
};
|
||||
let project_search_view = cx.add_window(|window, cx| {
|
||||
ProjectSearchView::new(
|
||||
workspace.downgrade(),
|
||||
project_search.clone(),
|
||||
window,
|
||||
cx,
|
||||
Some(project_search_settings),
|
||||
)
|
||||
});
|
||||
|
||||
project_search_view
|
||||
.update(cx, |view, window, cx| {
|
||||
// Verify that `test\\c` does not change search options, as
|
||||
// `SearchOptions::CASE_SENSITIVE` is disabled.
|
||||
view.query_editor.update(cx, |editor, cx| {
|
||||
editor.set_text("test\\c", window, cx);
|
||||
});
|
||||
|
||||
assert_eq!(view.search_query_text_raw(cx), "test\\c");
|
||||
assert_eq!(view.search_query_text(cx), "test");
|
||||
|
||||
view.apply_pattern_items(&view.search_query_text_raw(cx));
|
||||
|
||||
assert_eq!(
|
||||
view.search_options,
|
||||
SearchOptions::REGEX,
|
||||
"Case sensitivity should remain disabled if only pattern item is '\\c'"
|
||||
);
|
||||
|
||||
// Verify that `test\\c\\C\\c` does not change search options,
|
||||
// as even though `\\C` should enabled
|
||||
// `SearchOptions::CASE_SENSITIVE`, the last `\\c` should
|
||||
// disable it.
|
||||
view.query_editor.update(cx, |editor, cx| {
|
||||
editor.set_text("test\\c\\C\\c", window, cx);
|
||||
});
|
||||
|
||||
assert_eq!(view.search_query_text_raw(cx), "test\\c\\C\\c");
|
||||
assert_eq!(view.search_query_text(cx), "test");
|
||||
|
||||
view.apply_pattern_items(&view.search_query_text_raw(cx));
|
||||
|
||||
assert_eq!(
|
||||
view.search_options,
|
||||
SearchOptions::REGEX,
|
||||
"Case sensitivity should be disabled if '\\c' follows '\\C'"
|
||||
);
|
||||
|
||||
// Verify that a single `\\C` pattern item in the search query
|
||||
// enables the `SearchOptions::CASE_SENSITIVE` option.
|
||||
view.query_editor.update(cx, |editor, cx| {
|
||||
editor.set_text("test\\C", window, cx);
|
||||
});
|
||||
|
||||
assert_eq!(view.search_query_text_raw(cx), "test\\C");
|
||||
assert_eq!(view.search_query_text(cx), "test");
|
||||
|
||||
view.apply_pattern_items(&view.search_query_text_raw(cx));
|
||||
|
||||
assert_eq!(
|
||||
view.search_options,
|
||||
SearchOptions::REGEX | SearchOptions::CASE_SENSITIVE,
|
||||
"Case sensitivity should be enabled when '\\C' is used"
|
||||
);
|
||||
|
||||
// Since `SearchOptions::CASE_SENSITIVE` is now enabled, we can
|
||||
// check that a pattern item preceded by a backslash, for
|
||||
// example, `\\\\c`, does not affect the search option and is
|
||||
// not even recognized as a valid pattern item.
|
||||
// We need to clear the `pattern_items` before testing this, as
|
||||
// calling `apply_pattern_items` will revert it, so the
|
||||
// `CASE_SENSITIVE` option would be turned off.
|
||||
view.pattern_items = PatternItems::default();
|
||||
view.query_editor.update(cx, |editor, cx| {
|
||||
editor.set_text("test\\\\c", window, cx);
|
||||
});
|
||||
|
||||
assert_eq!(view.search_query_text_raw(cx), "test\\\\c");
|
||||
assert_eq!(view.search_query_text(cx), "test\\\\c");
|
||||
|
||||
view.apply_pattern_items(&view.search_query_text_raw(cx));
|
||||
|
||||
assert_eq!(
|
||||
view.search_options,
|
||||
SearchOptions::REGEX | SearchOptions::CASE_SENSITIVE,
|
||||
"Case sensitivity should remain enabled when pattern item is preceded by a backslash"
|
||||
);
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn init_test(cx: &mut TestAppContext) {
|
||||
cx.update(|cx| {
|
||||
let settings = SettingsStore::test(cx);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue