Extra rows approach draft

Kirill Bulatov and Max created

co-authored-by: Max <max@zed.dev>

Change summary

crates/search/src/project_search.rs | 196 +++++++++++++++++-------------
crates/theme/src/theme.rs           |   1 
crates/workspace/src/toolbar.rs     |  16 ++
styles/src/styleTree/search.ts      |  13 +
4 files changed, 136 insertions(+), 90 deletions(-)

Detailed changes

crates/search/src/project_search.rs 🔗

@@ -451,11 +451,12 @@ impl ProjectSearchView {
         .detach();
 
         let included_files_editor = cx.add_view(|cx| {
-            let editor = Editor::single_line(
-                Some(Arc::new(|theme| theme.search.editor.input.clone())),
+            Editor::single_line(
+                Some(Arc::new(|theme| {
+                    theme.search.include_exclude_editor.input.clone()
+                })),
                 cx,
-            );
-            editor
+            )
         });
         // Subcribe to include_files_editor in order to reraise editor events for workspace item activation purposes
         cx.subscribe(&included_files_editor, |_, _, event, cx| {
@@ -464,11 +465,12 @@ impl ProjectSearchView {
         .detach();
 
         let excluded_files_editor = cx.add_view(|cx| {
-            let editor = Editor::single_line(
-                Some(Arc::new(|theme| theme.search.editor.input.clone())),
+            Editor::single_line(
+                Some(Arc::new(|theme| {
+                    theme.search.include_exclude_editor.input.clone()
+                })),
                 cx,
-            );
-            editor
+            )
         });
         // Subcribe to excluded_files_editor in order to reraise editor events for workspace item activation purposes
         cx.subscribe(&excluded_files_editor, |_, _, event, cx| {
@@ -935,101 +937,119 @@ impl View for ProjectSearchBar {
             let included_files_view = ChildView::new(&search.included_files_editor, cx)
                 .aligned()
                 .left()
-                .flex(0.5, true);
+                .flex(1.0, true);
             let excluded_files_view = ChildView::new(&search.excluded_files_editor, cx)
                 .aligned()
                 .right()
-                .flex(0.5, true);
+                .flex(1.0, true);
+
+            let row_spacing = theme.workspace.toolbar.container.padding.bottom;
 
-            Flex::row()
+            Flex::column()
                 .with_child(
                     Flex::row()
                         .with_child(
-                            ChildView::new(&search.query_editor, cx)
+                            Flex::row()
+                                .with_child(
+                                    ChildView::new(&search.query_editor, cx)
+                                        .aligned()
+                                        .left()
+                                        .flex(1., true),
+                                )
+                                .with_children(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()
+                                }))
+                                .contained()
+                                .with_style(editor_container)
                                 .aligned()
-                                .left()
-                                .flex(1., true),
+                                .constrained()
+                                .with_min_width(theme.search.editor.min_width)
+                                .with_max_width(theme.search.editor.max_width)
+                                .flex(1., false),
+                        )
+                        .with_child(
+                            Flex::row()
+                                .with_child(self.render_nav_button("<", Direction::Prev, cx))
+                                .with_child(self.render_nav_button(">", Direction::Next, cx))
+                                .aligned(),
                         )
-                        .with_children(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()
-                        }))
-                        .contained()
-                        .with_style(editor_container)
-                        .aligned()
-                        .constrained()
-                        .with_min_width(theme.search.editor.min_width)
-                        .with_max_width(theme.search.editor.max_width)
-                        .flex(1., false),
-                )
-                .with_child(
-                    Flex::row()
-                        .with_child(self.render_nav_button("<", Direction::Prev, cx))
-                        .with_child(self.render_nav_button(">", Direction::Next, cx))
-                        .aligned(),
-                )
-                .with_child(
-                    Flex::row()
-                        .with_child(self.render_option_button(
-                            "Case",
-                            SearchOption::CaseSensitive,
-                            cx,
-                        ))
-                        .with_child(self.render_option_button("Word", SearchOption::WholeWord, cx))
-                        .with_child(self.render_option_button("Regex", SearchOption::Regex, cx))
-                        .contained()
-                        .with_style(theme.search.option_button_group)
-                        .aligned(),
-                )
-                // TODO kb better layout
-                .with_child(
-                    Flex::row()
                         .with_child(
-                            Label::new(
-                                "Include:",
-                                theme.search.include_exclude_inputs.text.clone(),
-                            )
-                            .contained()
-                            .with_style(theme.search.include_exclude_inputs.container)
-                            .aligned(),
+                            Flex::row()
+                                .with_child(self.render_option_button(
+                                    "Case",
+                                    SearchOption::CaseSensitive,
+                                    cx,
+                                ))
+                                .with_child(self.render_option_button(
+                                    "Word",
+                                    SearchOption::WholeWord,
+                                    cx,
+                                ))
+                                .with_child(self.render_option_button(
+                                    "Regex",
+                                    SearchOption::Regex,
+                                    cx,
+                                ))
+                                .contained()
+                                .with_style(theme.search.option_button_group)
+                                .aligned(),
                         )
-                        .with_child(included_files_view)
                         .contained()
-                        .with_style(theme.search.editor.input.container)
-                        .aligned()
-                        .constrained()
-                        .with_min_width(theme.search.editor.min_width)
-                        .with_max_width(theme.search.editor.max_width)
-                        .flex(1., false),
+                        .with_margin_bottom(row_spacing),
                 )
                 .with_child(
                     Flex::row()
+                        // TODO kb better layout
                         .with_child(
-                            Label::new(
-                                "Exclude:",
-                                theme.search.include_exclude_inputs.text.clone(),
-                            )
-                            .contained()
-                            .with_style(theme.search.include_exclude_inputs.container)
-                            .aligned(),
+                            Flex::row()
+                                .with_child(
+                                    Label::new(
+                                        "Include:",
+                                        theme.search.include_exclude_inputs.text.clone(),
+                                    )
+                                    .contained()
+                                    .with_style(theme.search.include_exclude_inputs.container)
+                                    .aligned(),
+                                )
+                                .with_child(included_files_view)
+                                .contained()
+                                .with_style(theme.search.include_exclude_editor.input.container)
+                                .aligned()
+                                .constrained()
+                                .with_min_width(theme.search.include_exclude_editor.min_width)
+                                .with_max_width(theme.search.include_exclude_editor.max_width)
+                                .flex(1., false),
                         )
-                        .with_child(excluded_files_view)
-                        .contained()
-                        .with_style(theme.search.editor.input.container)
-                        .aligned()
-                        .constrained()
-                        .with_min_width(theme.search.editor.min_width)
-                        .with_max_width(theme.search.editor.max_width)
-                        .flex(1., false),
+                        .with_child(
+                            Flex::row()
+                                .with_child(
+                                    Label::new(
+                                        "Exclude:",
+                                        theme.search.include_exclude_inputs.text.clone(),
+                                    )
+                                    .contained()
+                                    .with_style(theme.search.include_exclude_inputs.container)
+                                    .aligned(),
+                                )
+                                .with_child(excluded_files_view)
+                                .contained()
+                                .with_style(theme.search.include_exclude_editor.input.container)
+                                .aligned()
+                                .constrained()
+                                .with_min_width(theme.search.include_exclude_editor.min_width)
+                                .with_max_width(theme.search.include_exclude_editor.max_width)
+                                .flex(1., false),
+                        ),
                 )
                 .contained()
                 .with_style(theme.search.container)
@@ -1061,6 +1081,10 @@ impl ToolbarItemView for ProjectSearchBar {
             ToolbarItemLocation::Hidden
         }
     }
+
+    fn row_count(&self) -> usize {
+        2
+    }
 }
 
 #[cfg(test)]

crates/theme/src/theme.rs 🔗

@@ -319,6 +319,7 @@ pub struct Search {
     pub editor: FindEditor,
     pub invalid_editor: ContainerStyle,
     pub option_button_group: ContainerStyle,
+    pub include_exclude_editor: FindEditor,
     pub include_exclude_inputs: ContainedText,
     pub option_button: Interactive<ContainedText>,
     pub match_background: Color,

crates/workspace/src/toolbar.rs 🔗

@@ -22,6 +22,10 @@ pub trait ToolbarItemView: View {
     }
 
     fn pane_focus_update(&mut self, _pane_focused: bool, _cx: &mut ViewContext<Self>) {}
+
+    fn row_count(&self) -> usize {
+        1
+    }
 }
 
 trait ToolbarItemViewHandle {
@@ -33,6 +37,7 @@ trait ToolbarItemViewHandle {
         cx: &mut WindowContext,
     ) -> ToolbarItemLocation;
     fn pane_focus_update(&mut self, pane_focused: bool, cx: &mut WindowContext);
+    fn row_count(&self, cx: &WindowContext) -> usize;
 }
 
 #[derive(Copy, Clone, Debug, PartialEq)]
@@ -66,12 +71,14 @@ impl View for Toolbar {
         let mut primary_right_items = Vec::new();
         let mut secondary_item = None;
         let spacing = theme.item_spacing;
+        let mut primary_items_row_count = 1;
 
         for (item, position) in &self.items {
             match *position {
                 ToolbarItemLocation::Hidden => {}
 
                 ToolbarItemLocation::PrimaryLeft { flex } => {
+                    primary_items_row_count = primary_items_row_count.max(item.row_count(cx));
                     let left_item = ChildView::new(item.as_any(), cx)
                         .aligned()
                         .contained()
@@ -84,6 +91,7 @@ impl View for Toolbar {
                 }
 
                 ToolbarItemLocation::PrimaryRight { flex } => {
+                    primary_items_row_count = primary_items_row_count.max(item.row_count(cx));
                     let right_item = ChildView::new(item.as_any(), cx)
                         .aligned()
                         .contained()
@@ -100,7 +108,7 @@ impl View for Toolbar {
                     secondary_item = Some(
                         ChildView::new(item.as_any(), cx)
                             .constrained()
-                            .with_height(theme.height)
+                            .with_height(theme.height * item.row_count(cx) as f32)
                             .into_any(),
                     );
                 }
@@ -117,7 +125,7 @@ impl View for Toolbar {
         }
 
         let container_style = theme.container;
-        let height = theme.height;
+        let height = theme.height * primary_items_row_count as f32;
         let button_style = theme.nav_button;
         let tooltip_style = cx.global::<Settings>().theme.tooltip.clone();
 
@@ -338,6 +346,10 @@ impl<T: ToolbarItemView> ToolbarItemViewHandle for ViewHandle<T> {
             cx.notify();
         });
     }
+
+    fn row_count(&self, cx: &WindowContext) -> usize {
+        self.read(cx).row_count()
+    }
 }
 
 impl From<&dyn ToolbarItemViewHandle> for AnyViewHandle {

styles/src/styleTree/search.ts 🔗

@@ -64,9 +64,16 @@ export default function search(colorScheme: ColorScheme) {
             ...editor,
             border: border(layer, "negative"),
         },
+        includeExcludeEditor: {
+            ...editor,
+            minWidth: 100,
+            maxWidth: 250,
+        },
         matchIndex: {
             ...text(layer, "mono", "variant"),
-            padding: 6,
+            padding: {
+                left: 6,
+            },
         },
         optionButtonGroup: {
             padding: {
@@ -76,7 +83,9 @@ export default function search(colorScheme: ColorScheme) {
         },
         includeExcludeInputs: {
             ...text(layer, "mono", "variant"),
-            padding: 6,
+            padding: {
+                right: 6,
+            },
         },
         resultsStatus: {
             ...text(layer, "mono", "on"),