diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index a660a6dce8..05d27b824c 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -22,6 +22,7 @@ use smallvec::SmallVec; use std::{ any::{Any, TypeId}, borrow::Cow, + collections::HashSet, mem, ops::Range, path::PathBuf, @@ -76,6 +77,13 @@ struct ProjectSearch { search_id: usize, } +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +enum InputPanel { + Query, + Exclude, + Include, +} + pub struct ProjectSearchView { model: ModelHandle, query_editor: ViewHandle, @@ -83,7 +91,7 @@ pub struct ProjectSearchView { case_sensitive: bool, whole_word: bool, regex: bool, - query_contains_error: bool, + panels_with_errors: HashSet, active_match_index: Option, search_id: usize, query_editor_was_focused: bool, @@ -493,7 +501,7 @@ impl ProjectSearchView { case_sensitive, whole_word, regex, - query_contains_error: false, + panels_with_errors: HashSet::new(), active_match_index: None, query_editor_was_focused: false, included_files_editor, @@ -564,7 +572,7 @@ impl ProjectSearchView { fn build_search_query(&mut self, cx: &mut ViewContext) -> Option { let text = self.query_editor.read(cx).text(cx); - let Ok(included_files) = self + let included_files = match self .included_files_editor .read(cx) .text(cx) @@ -572,12 +580,19 @@ impl ProjectSearchView { .map(str::trim) .filter(|glob_str| !glob_str.is_empty()) .map(|glob_str| glob::Pattern::new(glob_str)) - .collect::>() else { - self.query_contains_error = true; + .collect::>() + { + Ok(included_files) => { + self.panels_with_errors.remove(&InputPanel::Include); + included_files + } + Err(_e) => { + self.panels_with_errors.insert(InputPanel::Include); cx.notify(); - return None - }; - let Ok(excluded_files) = self + return None; + } + }; + let excluded_files = match self .excluded_files_editor .read(cx) .text(cx) @@ -585,11 +600,18 @@ impl ProjectSearchView { .map(str::trim) .filter(|glob_str| !glob_str.is_empty()) .map(|glob_str| glob::Pattern::new(glob_str)) - .collect::>() else { - self.query_contains_error = true; + .collect::>() + { + Ok(excluded_files) => { + self.panels_with_errors.remove(&InputPanel::Exclude); + excluded_files + } + Err(_e) => { + self.panels_with_errors.insert(InputPanel::Exclude); cx.notify(); - return None - }; + return None; + } + }; if self.regex { match SearchQuery::regex( text, @@ -598,9 +620,12 @@ impl ProjectSearchView { included_files, excluded_files, ) { - Ok(query) => Some(query), - Err(_) => { - self.query_contains_error = true; + Ok(query) => { + self.panels_with_errors.remove(&InputPanel::Query); + Some(query) + } + Err(_e) => { + self.panels_with_errors.insert(InputPanel::Query); cx.notify(); None } @@ -968,11 +993,23 @@ impl View for ProjectSearchBar { if let Some(search) = self.active_project_search.as_ref() { let search = search.read(cx); let theme = cx.global::().theme.clone(); - let editor_container = if search.query_contains_error { + let query_container_style = if search.panels_with_errors.contains(&InputPanel::Query) { theme.search.invalid_editor } else { theme.search.editor.input.container }; + let include_container_style = + if search.panels_with_errors.contains(&InputPanel::Include) { + theme.search.invalid_include_exclude_editor + } else { + theme.search.include_exclude_editor.input.container + }; + let exclude_container_style = + if search.panels_with_errors.contains(&InputPanel::Exclude) { + theme.search.invalid_include_exclude_editor + } else { + theme.search.include_exclude_editor.input.container + }; let included_files_view = ChildView::new(&search.included_files_editor, cx) .aligned() @@ -1010,7 +1047,7 @@ impl View for ProjectSearchBar { .aligned() })) .contained() - .with_style(editor_container) + .with_style(query_container_style) .aligned() .constrained() .with_min_width(theme.search.editor.min_width) @@ -1053,7 +1090,7 @@ impl View for ProjectSearchBar { Flex::row() .with_child(included_files_view) .contained() - .with_style(theme.search.include_exclude_editor.input.container) + .with_style(include_container_style) .aligned() .constrained() .with_min_width(theme.search.include_exclude_editor.min_width) @@ -1064,7 +1101,7 @@ impl View for ProjectSearchBar { Flex::row() .with_child(excluded_files_view) .contained() - .with_style(theme.search.include_exclude_editor.input.container) + .with_style(exclude_container_style) .aligned() .constrained() .with_min_width(theme.search.include_exclude_editor.min_width) diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index 2e1a945957..8760ea54ea 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -320,6 +320,7 @@ pub struct Search { pub invalid_editor: ContainerStyle, pub option_button_group: ContainerStyle, pub include_exclude_editor: FindEditor, + pub invalid_include_exclude_editor: ContainerStyle, pub include_exclude_inputs: ContainedText, pub option_button: Interactive, pub match_background: Color, diff --git a/styles/src/styleTree/search.ts b/styles/src/styleTree/search.ts index 71f1fc3fa9..6fc8a95d7d 100644 --- a/styles/src/styleTree/search.ts +++ b/styles/src/styleTree/search.ts @@ -26,6 +26,12 @@ export default function search(colorScheme: ColorScheme) { }, } + const includeExcludeEditor = { + ...editor, + minWidth: 100, + maxWidth: 250, + }; + return { // TODO: Add an activeMatchBackground on the rust side to differenciate between active and inactive matchBackground: withOpacity(foreground(layer, "accent"), 0.4), @@ -64,10 +70,10 @@ export default function search(colorScheme: ColorScheme) { ...editor, border: border(layer, "negative"), }, - includeExcludeEditor: { - ...editor, - minWidth: 100, - maxWidth: 250, + includeExcludeEditor, + invalidIncludeExcludeEditor: { + ...includeExcludeEditor, + border: border(layer, "negative"), }, matchIndex: { ...text(layer, "mono", "variant"),