reorganize search bar, enable filters and disable select all during invalid states

KCaverly and Piotr created

Co-authored-by: Piotr <piotr@zed.dev>

Change summary

crates/search/src/buffer_search.rs  |  25 +-
crates/search/src/project_search.rs | 229 +++++++++++++-----------------
crates/theme/src/theme.rs           |   2 
styles/src/style_tree/search.ts     |  71 ++++++---
4 files changed, 159 insertions(+), 168 deletions(-)

Detailed changes

crates/search/src/buffer_search.rs 🔗

@@ -222,17 +222,11 @@ impl View for BufferSearchBar {
 
         let icon_style = theme.search.editor_icon.clone();
         let side_column_min_width = 165.;
-        let button_height = 32.;
         let nav_column = Flex::row()
-            .with_child(
-                Flex::row()
-                    .with_children(match_count)
-                    .constrained()
-                    .with_min_width(100.),
-            )
+            .with_child(self.render_action_button("Select All", cx))
             .with_child(nav_button_for_direction("<", Direction::Prev, cx))
             .with_child(nav_button_for_direction(">", Direction::Next, cx))
-            .with_child(self.render_action_button("Select All", cx))
+            .with_child(Flex::row().with_children(match_count))
             .constrained()
             .with_height(theme.search.search_bar_row_height);
 
@@ -493,11 +487,20 @@ impl BufferSearchBar {
         let tooltip = "Select All Matches";
         let tooltip_style = theme::current(cx).tooltip.clone();
         let action_type_id = 0_usize;
-
+        let has_matches = self.active_match_index.is_some();
+        let cursor_style = if has_matches {
+            CursorStyle::PointingHand
+        } else {
+            CursorStyle::default()
+        };
         enum ActionButton {}
         MouseEventHandler::<ActionButton, _>::new(action_type_id, cx, |state, cx| {
             let theme = theme::current(cx);
-            let style = theme.search.action_button.style_for(state);
+            let style = theme
+                .search
+                .action_button
+                .in_state(has_matches)
+                .style_for(state);
             Label::new(icon, style.text.clone())
                 .aligned()
                 .contained()
@@ -506,7 +509,7 @@ impl BufferSearchBar {
         .on_click(MouseButton::Left, move |_, this, cx| {
             this.select_all_matches(&SelectAllMatches, cx)
         })
-        .with_cursor_style(CursorStyle::PointingHand)
+        .with_cursor_style(cursor_style)
         .with_tooltip::<ActionButton>(
             action_type_id,
             tooltip.to_string(),

crates/search/src/project_search.rs 🔗

@@ -1392,27 +1392,7 @@ impl View for ProjectSearchBar {
             } 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()
-                .left()
-                .flex(1.0, true);
-            let excluded_files_view = ChildView::new(&search.excluded_files_editor, cx)
-                .aligned()
-                .right()
-                .flex(1.0, true);
             let row_spacing = theme.workspace.toolbar.container.padding.bottom;
             let search = _search.read(cx);
             let filter_button = render_option_button_icon(
@@ -1455,19 +1435,15 @@ impl View for ProjectSearchBar {
 
             let search = _search.read(cx);
             let icon_style = theme.search.editor_icon.clone();
+
+            // Editor Functionality
             let query = Flex::row()
                 .with_child(
                     Svg::for_style(icon_style.icon)
                         .contained()
-                        .with_style(icon_style.container)
-                        .constrained(),
-                )
-                .with_child(
-                    ChildView::new(&search.query_editor, cx)
-                        .constrained()
-                        .flex(1., true)
-                        .into_any(),
+                        .with_style(icon_style.container),
                 )
+                .with_child(ChildView::new(&search.query_editor, cx).flex(1., true))
                 .with_child(
                     Flex::row()
                         .with_child(filter_button)
@@ -1477,48 +1453,84 @@ impl View for ProjectSearchBar {
                         .contained(),
                 )
                 .align_children_center()
-                .aligned()
-                .left()
                 .flex(1., true);
+
             let search = _search.read(cx);
-            let matches = search.active_match_index.map(|match_ix| {
-                Label::new(
-                    format!(
-                        "{}/{}",
-                        match_ix + 1,
-                        search.model.read(cx).match_ranges.len()
-                    ),
-                    theme.search.match_index.text.clone(),
-                )
-                .contained()
-                .with_style(theme.search.match_index.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)
+                .contained()
+                .flex(1.0, true);
+            let excluded_files_view = ChildView::new(&search.excluded_files_editor, cx)
+                .contained()
+                .flex(1.0, true);
             let filters = search.filters_enabled.then(|| {
                 Flex::row()
                     .with_child(
-                        Flex::row()
-                            .with_child(included_files_view)
+                        included_files_view
                             .contained()
                             .with_style(include_container_style)
-                            .aligned()
                             .constrained()
+                            .with_height(theme.search.search_bar_row_height)
                             .with_min_width(theme.search.include_exclude_editor.min_width)
-                            .with_max_width(theme.search.include_exclude_editor.max_width)
-                            .flex(1., false),
+                            .with_max_width(theme.search.include_exclude_editor.max_width),
                     )
                     .with_child(
-                        Flex::row()
-                            .with_child(excluded_files_view)
+                        excluded_files_view
                             .contained()
                             .with_style(exclude_container_style)
-                            .aligned()
                             .constrained()
+                            .with_height(theme.search.search_bar_row_height)
                             .with_min_width(theme.search.include_exclude_editor.min_width)
-                            .with_max_width(theme.search.include_exclude_editor.max_width)
-                            .flex(1., false),
+                            .with_max_width(theme.search.include_exclude_editor.max_width),
                     )
+                    .contained()
+                    .with_padding_top(3.)
             });
+
+            let editor_column = Flex::column()
+                .with_child(
+                    query
+                        .contained()
+                        .with_style(query_container_style)
+                        .constrained()
+                        .with_min_width(theme.search.editor.min_width)
+                        .with_max_width(theme.search.editor.max_width)
+                        .with_height(theme.search.search_bar_row_height)
+                        .flex(1., false),
+                )
+                .with_children(filters)
+                .contained()
+                .with_background_color(gpui::color::Color::blue())
+                .flex(1., false);
+
+            let matches = search.active_match_index.map(|match_ix| {
+                Label::new(
+                    format!(
+                        "{}/{}",
+                        match_ix + 1,
+                        search.model.read(cx).match_ranges.len()
+                    ),
+                    theme.search.match_index.text.clone(),
+                )
+                .contained()
+                .with_style(theme.search.match_index.container)
+                .aligned()
+            });
+
             let search_button_for_mode = |mode, cx: &mut ViewContext<ProjectSearchBar>| {
                 let is_active = if let Some(search) = self.active_project_search.as_ref() {
                     let search = search.read(cx);
@@ -1551,95 +1563,52 @@ impl View for ProjectSearchBar {
                     cx,
                 )
             };
-            let nav_column = Flex::column()
-                .with_child(
-                    Flex::row()
-                        .align_children_center()
-                        .with_child(
-                            Flex::row().align_children_center()
-                                .with_child(nav_button_for_direction("<", Direction::Prev, cx))
-                                .with_child(nav_button_for_direction(">", Direction::Next, cx))
-                                .aligned(),
-                        )
-                        .with_children(matches)
-                        .aligned()
-                )
-                .flex(1., true);
-            let editor_column = Flex::column()
-                .align_children_center()
+
+            let nav_column = Flex::row()
+                .with_child(nav_button_for_direction("<", Direction::Prev, cx))
+                .with_child(nav_button_for_direction(">", Direction::Next, cx))
+                .with_child(Flex::row().with_children(matches))
+                .constrained()
+                .with_height(theme.search.search_bar_row_height)
+                .contained()
+                .with_background_color(gpui::color::Color::red());
+
+            let side_column_min_width = 200.;
+            let mode_column = Flex::row()
                 .with_child(
                     Flex::row()
-                        .with_child(
-                            Flex::row()
-                                .with_child(query)
-                                .contained()
-                                .with_style(query_container_style)
-                                .aligned()
-                                .constrained()
-                                .with_min_width(theme.search.editor.min_width)
-                                .with_max_width(theme.search.editor.max_width)
-                                .with_max_height(theme.search.search_bar_row_height)
-                                .flex(1., false),
-                        )
+                        .with_child(search_button_for_mode(SearchMode::Text, cx))
+                        .with_children(semantic_index)
+                        .with_child(search_button_for_mode(SearchMode::Regex, cx))
                         .contained()
-                        .with_margin_bottom(row_spacing),
+                        .with_style(theme.search.modes_container),
                 )
-                .with_children(filters)
-                .contained()
+                .with_child(super::search_bar::render_close_button(
+                    "Dismiss Project Search",
+                    &theme.search,
+                    cx,
+                    |_, this, cx| {
+                        if let Some(search) = this.active_project_search.as_mut() {
+                            search.update(cx, |_, cx| cx.emit(ViewEvent::Dismiss))
+                        }
+                    },
+                    None,
+                ))
+                .constrained()
+                .with_height(theme.search.search_bar_row_height)
                 .aligned()
+                .right()
                 .top()
-                .flex(1., false);
-            let mode_column = Flex::column()
-                .with_child(
-                    Flex::row()
-                        .align_children_center()
-                        .with_child(
-                            Flex::row()
-                                .with_child(search_button_for_mode(SearchMode::Text, cx))
-                                .with_children(semantic_index)
-                                .with_child(search_button_for_mode(SearchMode::Regex, cx))
-                                .aligned()
-                                .left()
-                                .contained()
-                                .with_style(theme.search.modes_container),
-                        )
-                        .with_child(
-                            super::search_bar::render_close_button(
-                                "Dismiss Project Search",
-                                &theme.search,
-                                cx,
-                                |_, this, cx| {
-                                    if let Some(search) = this.active_project_search.as_mut() {
-                                        search.update(cx, |_, cx| cx.emit(ViewEvent::Dismiss))
-                                    }
-                                },
-                                None,
-                            )
-                            .aligned()
-                            .right(),
-                        )
-                        .constrained()
-                        .with_height(theme.search.search_bar_row_height)
-                        .aligned()
-                        .right()
-                        .top()
-                        .flex(1., true),
-                )
-                .with_children(
-                    _search
-                        .read(cx)
-                        .filters_enabled
-                        .then(|| Flex::row().flex(1., true)),
-                )
-                .contained()
-                .flex(1., true);
+                .constrained()
+                .with_min_width(side_column_min_width)
+                .flex_float();
+
             Flex::row()
-                .with_child(nav_column)
                 .with_child(editor_column)
+                .with_child(nav_column)
                 .with_child(mode_column)
                 .contained()
                 .with_style(theme.search.container)
-                .flex_float()
                 .into_any_named("project search")
         } else {
             Empty::new().into_any()

crates/theme/src/theme.rs 🔗

@@ -380,7 +380,7 @@ pub struct Search {
     pub invalid_include_exclude_editor: ContainerStyle,
     pub include_exclude_inputs: ContainedText,
     pub option_button: Toggleable<Interactive<ContainedText>>,
-    pub action_button: Interactive<ContainedText>,
+    pub action_button: Toggleable<Interactive<ContainedText>>,
     pub match_background: Color,
     pub match_index: ContainedText,
     pub major_results_status: TextStyle,

styles/src/style_tree/search.ts 🔗

@@ -89,31 +89,52 @@ export default function search(): any {
                 },
             },
         }),
-        action_button: interactive({
-            base: {
-                ...text(theme.highest, "mono", "on"),
-                background: background(theme.highest, "on"),
-                corner_radius: 6,
-                border: border(theme.highest, "on"),
-                padding: {
-                    // bottom: 2,
-                    left: 10,
-                    right: 10,
-                    // top: 2,
-                },
-            },
-            state: {
-                hovered: {
-                    ...text(theme.highest, "mono", "on", "hovered"),
-                    background: background(theme.highest, "on", "hovered"),
-                    border: border(theme.highest, "on", "hovered"),
+        action_button: toggleable({
+            base: interactive({
+                base: {
+                    ...text(theme.highest, "mono", "disabled"),
+                    background: background(theme.highest, "disabled"),
+                    corner_radius: 6,
+                    border: border(theme.highest, "disabled"),
+                    padding: {
+                        // bottom: 2,
+                        left: 10,
+                        right: 10,
+                        // top: 2,
+                    },
                 },
-                clicked: {
-                    ...text(theme.highest, "mono", "on", "pressed"),
-                    background: background(theme.highest, "on", "pressed"),
-                    border: border(theme.highest, "on", "pressed"),
+                state: {
+                    hovered: {}
                 },
-            },
+            }),
+            state: {
+                active: interactive({
+                    base: {
+                        ...text(theme.highest, "mono", "on"),
+                        background: background(theme.highest, "on"),
+                        corner_radius: 6,
+                        border: border(theme.highest, "on"),
+                        padding: {
+                            // bottom: 2,
+                            left: 10,
+                            right: 10,
+                            // top: 2,
+                        },
+                    },
+                    state: {
+                        hovered: {
+                            ...text(theme.highest, "mono", "on", "hovered"),
+                            background: background(theme.highest, "on", "hovered"),
+                            border: border(theme.highest, "on", "hovered"),
+                        },
+                        clicked: {
+                            ...text(theme.highest, "mono", "on", "pressed"),
+                            background: background(theme.highest, "on", "pressed"),
+                            border: border(theme.highest, "on", "pressed"),
+                        },
+                    },
+                })
+            }
         }),
         editor,
         invalid_editor: {
@@ -128,7 +149,7 @@ export default function search(): any {
         match_index: {
             ...text(theme.highest, "mono", "variant"),
             padding: {
-                left: 6,
+                left: 9,
             },
         },
         option_button_group: {
@@ -208,10 +229,8 @@ export default function search(): any {
                     },
 
                     padding: {
-                        // bottom: 4,
                         left: 10,
                         right: 10,
-                        // top: 5,
                     },
                     corner_radius: 6,
                 },