mirror of
https://github.com/zed-industries/zed.git
synced 2025-01-27 12:54:42 +00:00
search: Allow running a search with different options
Refactor search options to use bitflags so that we can represent the entire set of settings in one place.
This commit is contained in:
parent
20d8a2a1ec
commit
75fe77c11d
5 changed files with 196 additions and 100 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -6428,6 +6428,7 @@ name = "search"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"bitflags",
|
||||||
"client",
|
"client",
|
||||||
"collections",
|
"collections",
|
||||||
"editor",
|
"editor",
|
||||||
|
|
|
@ -9,6 +9,7 @@ path = "src/search.rs"
|
||||||
doctest = false
|
doctest = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
bitflags = "1"
|
||||||
collections = { path = "../collections" }
|
collections = { path = "../collections" }
|
||||||
editor = { path = "../editor" }
|
editor = { path = "../editor" }
|
||||||
gpui = { path = "../gpui" }
|
gpui = { path = "../gpui" }
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
SearchOption, SelectNextMatch, SelectPrevMatch, ToggleCaseSensitive, ToggleRegex,
|
SearchOptions, SelectNextMatch, SelectPrevMatch, ToggleCaseSensitive, ToggleRegex,
|
||||||
ToggleWholeWord,
|
ToggleWholeWord,
|
||||||
};
|
};
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
|
@ -42,12 +42,12 @@ pub fn init(cx: &mut AppContext) {
|
||||||
cx.add_action(BufferSearchBar::select_next_match_on_pane);
|
cx.add_action(BufferSearchBar::select_next_match_on_pane);
|
||||||
cx.add_action(BufferSearchBar::select_prev_match_on_pane);
|
cx.add_action(BufferSearchBar::select_prev_match_on_pane);
|
||||||
cx.add_action(BufferSearchBar::handle_editor_cancel);
|
cx.add_action(BufferSearchBar::handle_editor_cancel);
|
||||||
add_toggle_option_action::<ToggleCaseSensitive>(SearchOption::CaseSensitive, cx);
|
add_toggle_option_action::<ToggleCaseSensitive>(SearchOptions::CASE_SENSITIVE, cx);
|
||||||
add_toggle_option_action::<ToggleWholeWord>(SearchOption::WholeWord, cx);
|
add_toggle_option_action::<ToggleWholeWord>(SearchOptions::WHOLE_WORD, cx);
|
||||||
add_toggle_option_action::<ToggleRegex>(SearchOption::Regex, cx);
|
add_toggle_option_action::<ToggleRegex>(SearchOptions::REGEX, cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_toggle_option_action<A: Action>(option: SearchOption, cx: &mut AppContext) {
|
fn add_toggle_option_action<A: Action>(option: SearchOptions, cx: &mut AppContext) {
|
||||||
cx.add_action(move |pane: &mut Pane, _: &A, cx: &mut ViewContext<Pane>| {
|
cx.add_action(move |pane: &mut Pane, _: &A, cx: &mut ViewContext<Pane>| {
|
||||||
if let Some(search_bar) = pane.toolbar().read(cx).item_of_type::<BufferSearchBar>() {
|
if let Some(search_bar) = pane.toolbar().read(cx).item_of_type::<BufferSearchBar>() {
|
||||||
if search_bar.update(cx, |search_bar, cx| search_bar.show(false, false, cx)) {
|
if search_bar.update(cx, |search_bar, cx| search_bar.show(false, false, cx)) {
|
||||||
|
@ -69,9 +69,8 @@ pub struct BufferSearchBar {
|
||||||
seachable_items_with_matches:
|
seachable_items_with_matches:
|
||||||
HashMap<Box<dyn WeakSearchableItemHandle>, Vec<Box<dyn Any + Send>>>,
|
HashMap<Box<dyn WeakSearchableItemHandle>, Vec<Box<dyn Any + Send>>>,
|
||||||
pending_search: Option<Task<()>>,
|
pending_search: Option<Task<()>>,
|
||||||
case_sensitive: bool,
|
search_options: SearchOptions,
|
||||||
whole_word: bool,
|
default_options: SearchOptions,
|
||||||
regex: bool,
|
|
||||||
query_contains_error: bool,
|
query_contains_error: bool,
|
||||||
dismissed: bool,
|
dismissed: bool,
|
||||||
}
|
}
|
||||||
|
@ -153,19 +152,19 @@ impl View for BufferSearchBar {
|
||||||
.with_children(self.render_search_option(
|
.with_children(self.render_search_option(
|
||||||
supported_options.case,
|
supported_options.case,
|
||||||
"Case",
|
"Case",
|
||||||
SearchOption::CaseSensitive,
|
SearchOptions::CASE_SENSITIVE,
|
||||||
cx,
|
cx,
|
||||||
))
|
))
|
||||||
.with_children(self.render_search_option(
|
.with_children(self.render_search_option(
|
||||||
supported_options.word,
|
supported_options.word,
|
||||||
"Word",
|
"Word",
|
||||||
SearchOption::WholeWord,
|
SearchOptions::WHOLE_WORD,
|
||||||
cx,
|
cx,
|
||||||
))
|
))
|
||||||
.with_children(self.render_search_option(
|
.with_children(self.render_search_option(
|
||||||
supported_options.regex,
|
supported_options.regex,
|
||||||
"Regex",
|
"Regex",
|
||||||
SearchOption::Regex,
|
SearchOptions::REGEX,
|
||||||
cx,
|
cx,
|
||||||
))
|
))
|
||||||
.contained()
|
.contained()
|
||||||
|
@ -250,9 +249,8 @@ impl BufferSearchBar {
|
||||||
active_searchable_item_subscription: None,
|
active_searchable_item_subscription: None,
|
||||||
active_match_index: None,
|
active_match_index: None,
|
||||||
seachable_items_with_matches: Default::default(),
|
seachable_items_with_matches: Default::default(),
|
||||||
case_sensitive: false,
|
default_options: SearchOptions::NONE,
|
||||||
whole_word: false,
|
search_options: SearchOptions::NONE,
|
||||||
regex: false,
|
|
||||||
pending_search: None,
|
pending_search: None,
|
||||||
query_contains_error: false,
|
query_contains_error: false,
|
||||||
dismissed: true,
|
dismissed: true,
|
||||||
|
@ -280,6 +278,17 @@ impl BufferSearchBar {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn show(&mut self, focus: bool, suggest_query: bool, cx: &mut ViewContext<Self>) -> bool {
|
pub fn show(&mut self, focus: bool, suggest_query: bool, cx: &mut ViewContext<Self>) -> bool {
|
||||||
|
self.show_with_options(focus, suggest_query, self.default_options, cx)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn show_with_options(
|
||||||
|
&mut self,
|
||||||
|
focus: bool,
|
||||||
|
suggest_query: bool,
|
||||||
|
search_option: SearchOptions,
|
||||||
|
cx: &mut ViewContext<Self>,
|
||||||
|
) -> bool {
|
||||||
|
self.search_options = search_option;
|
||||||
let searchable_item = if let Some(searchable_item) = &self.active_searchable_item {
|
let searchable_item = if let Some(searchable_item) = &self.active_searchable_item {
|
||||||
SearchableItemHandle::boxed_clone(searchable_item.as_ref())
|
SearchableItemHandle::boxed_clone(searchable_item.as_ref())
|
||||||
} else {
|
} else {
|
||||||
|
@ -320,7 +329,7 @@ impl BufferSearchBar {
|
||||||
&self,
|
&self,
|
||||||
option_supported: bool,
|
option_supported: bool,
|
||||||
icon: &'static str,
|
icon: &'static str,
|
||||||
option: SearchOption,
|
option: SearchOptions,
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) -> Option<AnyElement<Self>> {
|
) -> Option<AnyElement<Self>> {
|
||||||
if !option_supported {
|
if !option_supported {
|
||||||
|
@ -328,9 +337,9 @@ impl BufferSearchBar {
|
||||||
}
|
}
|
||||||
|
|
||||||
let tooltip_style = theme::current(cx).tooltip.clone();
|
let tooltip_style = theme::current(cx).tooltip.clone();
|
||||||
let is_active = self.is_search_option_enabled(option);
|
let is_active = self.search_options.contains(option);
|
||||||
Some(
|
Some(
|
||||||
MouseEventHandler::<Self, _>::new(option as usize, cx, |state, cx| {
|
MouseEventHandler::<Self, _>::new(option.bits as usize, cx, |state, cx| {
|
||||||
let theme = theme::current(cx);
|
let theme = theme::current(cx);
|
||||||
let style = theme
|
let style = theme
|
||||||
.search
|
.search
|
||||||
|
@ -346,7 +355,7 @@ impl BufferSearchBar {
|
||||||
})
|
})
|
||||||
.with_cursor_style(CursorStyle::PointingHand)
|
.with_cursor_style(CursorStyle::PointingHand)
|
||||||
.with_tooltip::<Self>(
|
.with_tooltip::<Self>(
|
||||||
option as usize,
|
option.bits as usize,
|
||||||
format!("Toggle {}", option.label()),
|
format!("Toggle {}", option.label()),
|
||||||
Some(option.to_toggle_action()),
|
Some(option.to_toggle_action()),
|
||||||
tooltip_style,
|
tooltip_style,
|
||||||
|
@ -461,21 +470,10 @@ impl BufferSearchBar {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_search_option_enabled(&self, search_option: SearchOption) -> bool {
|
fn toggle_search_option(&mut self, search_option: SearchOptions, cx: &mut ViewContext<Self>) {
|
||||||
match search_option {
|
self.search_options.toggle(search_option);
|
||||||
SearchOption::WholeWord => self.whole_word,
|
self.default_options = self.search_options;
|
||||||
SearchOption::CaseSensitive => self.case_sensitive,
|
|
||||||
SearchOption::Regex => self.regex,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn toggle_search_option(&mut self, search_option: SearchOption, cx: &mut ViewContext<Self>) {
|
|
||||||
let value = match search_option {
|
|
||||||
SearchOption::WholeWord => &mut self.whole_word,
|
|
||||||
SearchOption::CaseSensitive => &mut self.case_sensitive,
|
|
||||||
SearchOption::Regex => &mut self.regex,
|
|
||||||
};
|
|
||||||
*value = !*value;
|
|
||||||
self.update_matches(false, cx);
|
self.update_matches(false, cx);
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
@ -571,11 +569,11 @@ impl BufferSearchBar {
|
||||||
self.active_match_index.take();
|
self.active_match_index.take();
|
||||||
active_searchable_item.clear_matches(cx);
|
active_searchable_item.clear_matches(cx);
|
||||||
} else {
|
} else {
|
||||||
let query = if self.regex {
|
let query = if self.search_options.contains(SearchOptions::REGEX) {
|
||||||
match SearchQuery::regex(
|
match SearchQuery::regex(
|
||||||
query,
|
query,
|
||||||
self.whole_word,
|
self.search_options.contains(SearchOptions::WHOLE_WORD),
|
||||||
self.case_sensitive,
|
self.search_options.contains(SearchOptions::CASE_SENSITIVE),
|
||||||
Vec::new(),
|
Vec::new(),
|
||||||
Vec::new(),
|
Vec::new(),
|
||||||
) {
|
) {
|
||||||
|
@ -589,8 +587,8 @@ impl BufferSearchBar {
|
||||||
} else {
|
} else {
|
||||||
SearchQuery::text(
|
SearchQuery::text(
|
||||||
query,
|
query,
|
||||||
self.whole_word,
|
self.search_options.contains(SearchOptions::WHOLE_WORD),
|
||||||
self.case_sensitive,
|
self.search_options.contains(SearchOptions::CASE_SENSITIVE),
|
||||||
Vec::new(),
|
Vec::new(),
|
||||||
Vec::new(),
|
Vec::new(),
|
||||||
)
|
)
|
||||||
|
@ -656,8 +654,7 @@ mod tests {
|
||||||
use language::Buffer;
|
use language::Buffer;
|
||||||
use unindent::Unindent as _;
|
use unindent::Unindent as _;
|
||||||
|
|
||||||
#[gpui::test]
|
fn init_test(cx: &mut TestAppContext) -> (ViewHandle<Editor>, ViewHandle<BufferSearchBar>) {
|
||||||
async fn test_search_simple(cx: &mut TestAppContext) {
|
|
||||||
crate::project_search::tests::init_test(cx);
|
crate::project_search::tests::init_test(cx);
|
||||||
|
|
||||||
let buffer = cx.add_model(|cx| {
|
let buffer = cx.add_model(|cx| {
|
||||||
|
@ -684,6 +681,13 @@ mod tests {
|
||||||
search_bar
|
search_bar
|
||||||
});
|
});
|
||||||
|
|
||||||
|
(editor, search_bar)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
async fn test_search_simple(cx: &mut TestAppContext) {
|
||||||
|
let (editor, search_bar) = init_test(cx);
|
||||||
|
|
||||||
// Search for a string that appears with different casing.
|
// Search for a string that appears with different casing.
|
||||||
// By default, search is case-insensitive.
|
// By default, search is case-insensitive.
|
||||||
search_bar.update(cx, |search_bar, cx| {
|
search_bar.update(cx, |search_bar, cx| {
|
||||||
|
@ -708,7 +712,7 @@ mod tests {
|
||||||
|
|
||||||
// Switch to a case sensitive search.
|
// Switch to a case sensitive search.
|
||||||
search_bar.update(cx, |search_bar, cx| {
|
search_bar.update(cx, |search_bar, cx| {
|
||||||
search_bar.toggle_search_option(SearchOption::CaseSensitive, cx);
|
search_bar.toggle_search_option(SearchOptions::CASE_SENSITIVE, cx);
|
||||||
});
|
});
|
||||||
editor.next_notification(cx).await;
|
editor.next_notification(cx).await;
|
||||||
editor.update(cx, |editor, cx| {
|
editor.update(cx, |editor, cx| {
|
||||||
|
@ -765,7 +769,7 @@ mod tests {
|
||||||
|
|
||||||
// Switch to a whole word search.
|
// Switch to a whole word search.
|
||||||
search_bar.update(cx, |search_bar, cx| {
|
search_bar.update(cx, |search_bar, cx| {
|
||||||
search_bar.toggle_search_option(SearchOption::WholeWord, cx);
|
search_bar.toggle_search_option(SearchOptions::WHOLE_WORD, cx);
|
||||||
});
|
});
|
||||||
editor.next_notification(cx).await;
|
editor.next_notification(cx).await;
|
||||||
editor.update(cx, |editor, cx| {
|
editor.update(cx, |editor, cx| {
|
||||||
|
@ -966,4 +970,99 @@ mod tests {
|
||||||
assert_eq!(search_bar.active_match_index, Some(2));
|
assert_eq!(search_bar.active_match_index, Some(2));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
async fn test_search_with_options(cx: &mut TestAppContext) {
|
||||||
|
let (editor, search_bar) = init_test(cx);
|
||||||
|
|
||||||
|
// show with options should make current search case sensitive
|
||||||
|
search_bar.update(cx, |search_bar, cx| {
|
||||||
|
search_bar.show_with_options(false, false, SearchOptions::CASE_SENSITIVE, cx);
|
||||||
|
search_bar.set_query("us", cx);
|
||||||
|
});
|
||||||
|
editor.next_notification(cx).await;
|
||||||
|
editor.update(cx, |editor, cx| {
|
||||||
|
assert_eq!(
|
||||||
|
editor.all_background_highlights(cx),
|
||||||
|
&[(
|
||||||
|
DisplayPoint::new(2, 43)..DisplayPoint::new(2, 45),
|
||||||
|
Color::red(),
|
||||||
|
)]
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// show should return to the default options (case insensitive)
|
||||||
|
search_bar.update(cx, |search_bar, cx| {
|
||||||
|
search_bar.show(true, true, cx);
|
||||||
|
});
|
||||||
|
editor.next_notification(cx).await;
|
||||||
|
editor.update(cx, |editor, cx| {
|
||||||
|
assert_eq!(
|
||||||
|
editor.all_background_highlights(cx),
|
||||||
|
&[
|
||||||
|
(
|
||||||
|
DisplayPoint::new(2, 17)..DisplayPoint::new(2, 19),
|
||||||
|
Color::red(),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
DisplayPoint::new(2, 43)..DisplayPoint::new(2, 45),
|
||||||
|
Color::red(),
|
||||||
|
)
|
||||||
|
]
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// toggling a search option (even in show_with_options mode) should update the defaults
|
||||||
|
search_bar.update(cx, |search_bar, cx| {
|
||||||
|
search_bar.set_query("regex", cx);
|
||||||
|
search_bar.show_with_options(false, false, SearchOptions::CASE_SENSITIVE, cx);
|
||||||
|
search_bar.toggle_search_option(SearchOptions::WHOLE_WORD, cx)
|
||||||
|
});
|
||||||
|
editor.next_notification(cx).await;
|
||||||
|
editor.update(cx, |editor, cx| {
|
||||||
|
assert_eq!(
|
||||||
|
editor.all_background_highlights(cx),
|
||||||
|
&[(
|
||||||
|
DisplayPoint::new(0, 35)..DisplayPoint::new(0, 40),
|
||||||
|
Color::red(),
|
||||||
|
),]
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// defaults should still include whole word
|
||||||
|
search_bar.update(cx, |search_bar, cx| {
|
||||||
|
search_bar.show(true, true, cx);
|
||||||
|
});
|
||||||
|
editor.next_notification(cx).await;
|
||||||
|
editor.update(cx, |editor, cx| {
|
||||||
|
assert_eq!(
|
||||||
|
editor.all_background_highlights(cx),
|
||||||
|
&[(
|
||||||
|
DisplayPoint::new(0, 35)..DisplayPoint::new(0, 40),
|
||||||
|
Color::red(),
|
||||||
|
),]
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// removing whole word changes the search again
|
||||||
|
search_bar.update(cx, |search_bar, cx| {
|
||||||
|
search_bar.toggle_search_option(SearchOptions::WHOLE_WORD, cx)
|
||||||
|
});
|
||||||
|
editor.next_notification(cx).await;
|
||||||
|
editor.update(cx, |editor, cx| {
|
||||||
|
assert_eq!(
|
||||||
|
editor.all_background_highlights(cx),
|
||||||
|
&[
|
||||||
|
(
|
||||||
|
DisplayPoint::new(0, 35)..DisplayPoint::new(0, 40),
|
||||||
|
Color::red(),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
DisplayPoint::new(0, 44)..DisplayPoint::new(0, 49),
|
||||||
|
Color::red()
|
||||||
|
)
|
||||||
|
]
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
SearchOption, SelectNextMatch, SelectPrevMatch, ToggleCaseSensitive, ToggleRegex,
|
SearchOptions, SelectNextMatch, SelectPrevMatch, ToggleCaseSensitive, ToggleRegex,
|
||||||
ToggleWholeWord,
|
ToggleWholeWord,
|
||||||
};
|
};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
@ -51,12 +51,12 @@ pub fn init(cx: &mut AppContext) {
|
||||||
cx.add_action(ProjectSearchBar::select_prev_match);
|
cx.add_action(ProjectSearchBar::select_prev_match);
|
||||||
cx.capture_action(ProjectSearchBar::tab);
|
cx.capture_action(ProjectSearchBar::tab);
|
||||||
cx.capture_action(ProjectSearchBar::tab_previous);
|
cx.capture_action(ProjectSearchBar::tab_previous);
|
||||||
add_toggle_option_action::<ToggleCaseSensitive>(SearchOption::CaseSensitive, cx);
|
add_toggle_option_action::<ToggleCaseSensitive>(SearchOptions::CASE_SENSITIVE, cx);
|
||||||
add_toggle_option_action::<ToggleWholeWord>(SearchOption::WholeWord, cx);
|
add_toggle_option_action::<ToggleWholeWord>(SearchOptions::WHOLE_WORD, cx);
|
||||||
add_toggle_option_action::<ToggleRegex>(SearchOption::Regex, cx);
|
add_toggle_option_action::<ToggleRegex>(SearchOptions::REGEX, cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_toggle_option_action<A: Action>(option: SearchOption, cx: &mut AppContext) {
|
fn add_toggle_option_action<A: Action>(option: SearchOptions, cx: &mut AppContext) {
|
||||||
cx.add_action(move |pane: &mut Pane, _: &A, cx: &mut ViewContext<Pane>| {
|
cx.add_action(move |pane: &mut Pane, _: &A, cx: &mut ViewContext<Pane>| {
|
||||||
if let Some(search_bar) = pane.toolbar().read(cx).item_of_type::<ProjectSearchBar>() {
|
if let Some(search_bar) = pane.toolbar().read(cx).item_of_type::<ProjectSearchBar>() {
|
||||||
if search_bar.update(cx, |search_bar, cx| {
|
if search_bar.update(cx, |search_bar, cx| {
|
||||||
|
@ -89,9 +89,7 @@ pub struct ProjectSearchView {
|
||||||
model: ModelHandle<ProjectSearch>,
|
model: ModelHandle<ProjectSearch>,
|
||||||
query_editor: ViewHandle<Editor>,
|
query_editor: ViewHandle<Editor>,
|
||||||
results_editor: ViewHandle<Editor>,
|
results_editor: ViewHandle<Editor>,
|
||||||
case_sensitive: bool,
|
search_options: SearchOptions,
|
||||||
whole_word: bool,
|
|
||||||
regex: bool,
|
|
||||||
panels_with_errors: HashSet<InputPanel>,
|
panels_with_errors: HashSet<InputPanel>,
|
||||||
active_match_index: Option<usize>,
|
active_match_index: Option<usize>,
|
||||||
search_id: usize,
|
search_id: usize,
|
||||||
|
@ -408,9 +406,7 @@ impl ProjectSearchView {
|
||||||
let project;
|
let project;
|
||||||
let excerpts;
|
let excerpts;
|
||||||
let mut query_text = String::new();
|
let mut query_text = String::new();
|
||||||
let mut regex = false;
|
let mut options = SearchOptions::NONE;
|
||||||
let mut case_sensitive = false;
|
|
||||||
let mut whole_word = false;
|
|
||||||
|
|
||||||
{
|
{
|
||||||
let model = model.read(cx);
|
let model = model.read(cx);
|
||||||
|
@ -418,9 +414,7 @@ impl ProjectSearchView {
|
||||||
excerpts = model.excerpts.clone();
|
excerpts = model.excerpts.clone();
|
||||||
if let Some(active_query) = model.active_query.as_ref() {
|
if let Some(active_query) = model.active_query.as_ref() {
|
||||||
query_text = active_query.as_str().to_string();
|
query_text = active_query.as_str().to_string();
|
||||||
regex = active_query.is_regex();
|
options = SearchOptions::from_query(active_query);
|
||||||
case_sensitive = active_query.case_sensitive();
|
|
||||||
whole_word = active_query.whole_word();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
cx.observe(&model, |this, _, cx| this.model_changed(cx))
|
cx.observe(&model, |this, _, cx| this.model_changed(cx))
|
||||||
|
@ -496,9 +490,7 @@ impl ProjectSearchView {
|
||||||
model,
|
model,
|
||||||
query_editor,
|
query_editor,
|
||||||
results_editor,
|
results_editor,
|
||||||
case_sensitive,
|
search_options: options,
|
||||||
whole_word,
|
|
||||||
regex,
|
|
||||||
panels_with_errors: HashSet::new(),
|
panels_with_errors: HashSet::new(),
|
||||||
active_match_index: None,
|
active_match_index: None,
|
||||||
query_editor_was_focused: false,
|
query_editor_was_focused: false,
|
||||||
|
@ -594,11 +586,11 @@ impl ProjectSearchView {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if self.regex {
|
if self.search_options.contains(SearchOptions::REGEX) {
|
||||||
match SearchQuery::regex(
|
match SearchQuery::regex(
|
||||||
text,
|
text,
|
||||||
self.whole_word,
|
self.search_options.contains(SearchOptions::WHOLE_WORD),
|
||||||
self.case_sensitive,
|
self.search_options.contains(SearchOptions::CASE_SENSITIVE),
|
||||||
included_files,
|
included_files,
|
||||||
excluded_files,
|
excluded_files,
|
||||||
) {
|
) {
|
||||||
|
@ -615,8 +607,8 @@ impl ProjectSearchView {
|
||||||
} else {
|
} else {
|
||||||
Some(SearchQuery::text(
|
Some(SearchQuery::text(
|
||||||
text,
|
text,
|
||||||
self.whole_word,
|
self.search_options.contains(SearchOptions::WHOLE_WORD),
|
||||||
self.case_sensitive,
|
self.search_options.contains(SearchOptions::CASE_SENSITIVE),
|
||||||
included_files,
|
included_files,
|
||||||
excluded_files,
|
excluded_files,
|
||||||
))
|
))
|
||||||
|
@ -765,9 +757,7 @@ impl ProjectSearchBar {
|
||||||
search_view.query_editor.update(cx, |editor, cx| {
|
search_view.query_editor.update(cx, |editor, cx| {
|
||||||
editor.set_text(old_query.as_str(), cx);
|
editor.set_text(old_query.as_str(), cx);
|
||||||
});
|
});
|
||||||
search_view.regex = old_query.is_regex();
|
search_view.search_options = SearchOptions::from_query(&old_query);
|
||||||
search_view.whole_word = old_query.whole_word();
|
|
||||||
search_view.case_sensitive = old_query.case_sensitive();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
new_query
|
new_query
|
||||||
|
@ -855,15 +845,10 @@ impl ProjectSearchBar {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn toggle_search_option(&mut self, option: SearchOption, cx: &mut ViewContext<Self>) -> bool {
|
fn toggle_search_option(&mut self, option: SearchOptions, cx: &mut ViewContext<Self>) -> bool {
|
||||||
if let Some(search_view) = self.active_project_search.as_ref() {
|
if let Some(search_view) = self.active_project_search.as_ref() {
|
||||||
search_view.update(cx, |search_view, cx| {
|
search_view.update(cx, |search_view, cx| {
|
||||||
let value = match option {
|
search_view.search_options.toggle(option);
|
||||||
SearchOption::WholeWord => &mut search_view.whole_word,
|
|
||||||
SearchOption::CaseSensitive => &mut search_view.case_sensitive,
|
|
||||||
SearchOption::Regex => &mut search_view.regex,
|
|
||||||
};
|
|
||||||
*value = !*value;
|
|
||||||
search_view.search(cx);
|
search_view.search(cx);
|
||||||
});
|
});
|
||||||
cx.notify();
|
cx.notify();
|
||||||
|
@ -920,12 +905,12 @@ impl ProjectSearchBar {
|
||||||
fn render_option_button(
|
fn render_option_button(
|
||||||
&self,
|
&self,
|
||||||
icon: &'static str,
|
icon: &'static str,
|
||||||
option: SearchOption,
|
option: SearchOptions,
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) -> AnyElement<Self> {
|
) -> AnyElement<Self> {
|
||||||
let tooltip_style = theme::current(cx).tooltip.clone();
|
let tooltip_style = theme::current(cx).tooltip.clone();
|
||||||
let is_active = self.is_option_enabled(option, cx);
|
let is_active = self.is_option_enabled(option, cx);
|
||||||
MouseEventHandler::<Self, _>::new(option as usize, cx, |state, cx| {
|
MouseEventHandler::<Self, _>::new(option.bits as usize, cx, |state, cx| {
|
||||||
let theme = theme::current(cx);
|
let theme = theme::current(cx);
|
||||||
let style = theme
|
let style = theme
|
||||||
.search
|
.search
|
||||||
|
@ -941,7 +926,7 @@ impl ProjectSearchBar {
|
||||||
})
|
})
|
||||||
.with_cursor_style(CursorStyle::PointingHand)
|
.with_cursor_style(CursorStyle::PointingHand)
|
||||||
.with_tooltip::<Self>(
|
.with_tooltip::<Self>(
|
||||||
option as usize,
|
option.bits as usize,
|
||||||
format!("Toggle {}", option.label()),
|
format!("Toggle {}", option.label()),
|
||||||
Some(option.to_toggle_action()),
|
Some(option.to_toggle_action()),
|
||||||
tooltip_style,
|
tooltip_style,
|
||||||
|
@ -950,14 +935,9 @@ impl ProjectSearchBar {
|
||||||
.into_any()
|
.into_any()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_option_enabled(&self, option: SearchOption, cx: &AppContext) -> bool {
|
fn is_option_enabled(&self, option: SearchOptions, cx: &AppContext) -> bool {
|
||||||
if let Some(search) = self.active_project_search.as_ref() {
|
if let Some(search) = self.active_project_search.as_ref() {
|
||||||
let search = search.read(cx);
|
search.read(cx).search_options.contains(option)
|
||||||
match option {
|
|
||||||
SearchOption::WholeWord => search.whole_word,
|
|
||||||
SearchOption::CaseSensitive => search.case_sensitive,
|
|
||||||
SearchOption::Regex => search.regex,
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
@ -1048,17 +1028,17 @@ impl View for ProjectSearchBar {
|
||||||
Flex::row()
|
Flex::row()
|
||||||
.with_child(self.render_option_button(
|
.with_child(self.render_option_button(
|
||||||
"Case",
|
"Case",
|
||||||
SearchOption::CaseSensitive,
|
SearchOptions::CASE_SENSITIVE,
|
||||||
cx,
|
cx,
|
||||||
))
|
))
|
||||||
.with_child(self.render_option_button(
|
.with_child(self.render_option_button(
|
||||||
"Word",
|
"Word",
|
||||||
SearchOption::WholeWord,
|
SearchOptions::WHOLE_WORD,
|
||||||
cx,
|
cx,
|
||||||
))
|
))
|
||||||
.with_child(self.render_option_button(
|
.with_child(self.render_option_button(
|
||||||
"Regex",
|
"Regex",
|
||||||
SearchOption::Regex,
|
SearchOptions::REGEX,
|
||||||
cx,
|
cx,
|
||||||
))
|
))
|
||||||
.contained()
|
.contained()
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
|
use bitflags::bitflags;
|
||||||
pub use buffer_search::BufferSearchBar;
|
pub use buffer_search::BufferSearchBar;
|
||||||
use gpui::{actions, Action, AppContext};
|
use gpui::{actions, Action, AppContext};
|
||||||
|
use project::search::SearchQuery;
|
||||||
pub use project_search::{ProjectSearchBar, ProjectSearchView};
|
pub use project_search::{ProjectSearchBar, ProjectSearchView};
|
||||||
|
|
||||||
pub mod buffer_search;
|
pub mod buffer_search;
|
||||||
|
@ -21,27 +23,40 @@ actions!(
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
#[derive(Clone, Copy, PartialEq)]
|
bitflags! {
|
||||||
pub enum SearchOption {
|
#[derive(Default)]
|
||||||
WholeWord,
|
pub struct SearchOptions: u8 {
|
||||||
CaseSensitive,
|
const NONE = 0b000;
|
||||||
Regex,
|
const WHOLE_WORD = 0b001;
|
||||||
|
const CASE_SENSITIVE = 0b010;
|
||||||
|
const REGEX = 0b100;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SearchOption {
|
impl SearchOptions {
|
||||||
pub fn label(&self) -> &'static str {
|
pub fn label(&self) -> &'static str {
|
||||||
match self {
|
match *self {
|
||||||
SearchOption::WholeWord => "Match Whole Word",
|
SearchOptions::WHOLE_WORD => "Match Whole Word",
|
||||||
SearchOption::CaseSensitive => "Match Case",
|
SearchOptions::CASE_SENSITIVE => "Match Case",
|
||||||
SearchOption::Regex => "Use Regular Expression",
|
SearchOptions::REGEX => "Use Regular Expression",
|
||||||
|
_ => panic!("{:?} is not a named SearchOption", self),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_toggle_action(&self) -> Box<dyn Action> {
|
pub fn to_toggle_action(&self) -> Box<dyn Action> {
|
||||||
match self {
|
match *self {
|
||||||
SearchOption::WholeWord => Box::new(ToggleWholeWord),
|
SearchOptions::WHOLE_WORD => Box::new(ToggleWholeWord),
|
||||||
SearchOption::CaseSensitive => Box::new(ToggleCaseSensitive),
|
SearchOptions::CASE_SENSITIVE => Box::new(ToggleCaseSensitive),
|
||||||
SearchOption::Regex => Box::new(ToggleRegex),
|
SearchOptions::REGEX => Box::new(ToggleRegex),
|
||||||
|
_ => panic!("{:?} is not a named SearchOption", self),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn from_query(query: &SearchQuery) -> SearchOptions {
|
||||||
|
let mut options = SearchOptions::NONE;
|
||||||
|
options.set(SearchOptions::WHOLE_WORD, query.whole_word());
|
||||||
|
options.set(SearchOptions::CASE_SENSITIVE, query.case_sensitive());
|
||||||
|
options.set(SearchOptions::REGEX, query.is_regex());
|
||||||
|
options
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue