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 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)
|
(editor.unwrap(), search_bar, cx)
|
||||||
}
|
}
|
||||||
|
@ -2957,7 +2957,7 @@ mod tests {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
search_bar.search_options,
|
search_bar.search_options,
|
||||||
SearchOptions::REGEX | SearchOptions::CASE_SENSITIVE,
|
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!(
|
assert_eq!(
|
||||||
search_bar.search_options,
|
search_bar.search_options,
|
||||||
SearchOptions::REGEX,
|
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,
|
SearchOption, SearchOptions, SearchSource, SelectNextMatch, SelectPreviousMatch,
|
||||||
ToggleCaseSensitive, ToggleIncludeIgnored, ToggleRegex, ToggleReplace, ToggleWholeWord,
|
ToggleCaseSensitive, ToggleIncludeIgnored, ToggleRegex, ToggleReplace, ToggleWholeWord,
|
||||||
buffer_search::Deploy,
|
buffer_search::Deploy,
|
||||||
|
pattern_items::PatternItems,
|
||||||
search_bar::{ActionButtonState, input_base_styles, render_action_button, render_text_input},
|
search_bar::{ActionButtonState, input_base_styles, render_action_button, render_text_input},
|
||||||
};
|
};
|
||||||
use anyhow::Context as _;
|
use anyhow::Context as _;
|
||||||
|
@ -210,6 +211,7 @@ pub struct ProjectSearchView {
|
||||||
replacement_editor: Entity<Editor>,
|
replacement_editor: Entity<Editor>,
|
||||||
results_editor: Entity<Editor>,
|
results_editor: Entity<Editor>,
|
||||||
search_options: SearchOptions,
|
search_options: SearchOptions,
|
||||||
|
pattern_items: PatternItems,
|
||||||
panels_with_errors: HashMap<InputPanel, String>,
|
panels_with_errors: HashMap<InputPanel, String>,
|
||||||
active_match_index: Option<usize>,
|
active_match_index: Option<usize>,
|
||||||
search_id: 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
|
// Subscribe to query_editor in order to reraise editor events for workspace item activation purposes
|
||||||
subscriptions.push(
|
subscriptions.push(
|
||||||
cx.subscribe(&query_editor, |this, _, event: &EditorEvent, cx| {
|
cx.subscribe(&query_editor, |this, _, event: &EditorEvent, cx| {
|
||||||
if let EditorEvent::Edited { .. } = event
|
if let EditorEvent::Edited { .. } = event {
|
||||||
&& EditorSettings::get_global(cx).use_smartcase_search
|
this.apply_pattern_items(&this.search_query_text_raw(cx));
|
||||||
{
|
|
||||||
let query = this.search_query_text(cx);
|
if EditorSettings::get_global(cx).use_smartcase_search {
|
||||||
if !query.is_empty()
|
let query = this.search_query_text(cx);
|
||||||
&& this.search_options.contains(SearchOptions::CASE_SENSITIVE)
|
if !query.is_empty()
|
||||||
!= contains_uppercase(&query)
|
&& this.search_options.contains(SearchOptions::CASE_SENSITIVE)
|
||||||
{
|
!= contains_uppercase(&query)
|
||||||
this.toggle_search_option(SearchOptions::CASE_SENSITIVE, cx);
|
{
|
||||||
|
this.toggle_search_option(SearchOptions::CASE_SENSITIVE, cx);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
cx.emit(ViewEvent::EditorEvent(event.clone()))
|
cx.emit(ViewEvent::EditorEvent(event.clone()))
|
||||||
|
@ -880,6 +884,7 @@ impl ProjectSearchView {
|
||||||
query_editor,
|
query_editor,
|
||||||
results_editor,
|
results_editor,
|
||||||
search_options: options,
|
search_options: options,
|
||||||
|
pattern_items: Default::default(),
|
||||||
panels_with_errors: HashMap::default(),
|
panels_with_errors: HashMap::default(),
|
||||||
active_match_index: None,
|
active_match_index: None,
|
||||||
included_files_editor,
|
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)
|
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> {
|
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`.
|
// 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 {
|
let open_buffers = if self.included_opened_only {
|
||||||
Some(self.open_buffers(cx))
|
Some(self.open_buffers(cx))
|
||||||
} else {
|
} 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(
|
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) {
|
fn init_test(cx: &mut TestAppContext) {
|
||||||
cx.update(|cx| {
|
cx.update(|cx| {
|
||||||
let settings = SettingsStore::test(cx);
|
let settings = SettingsStore::test(cx);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue