From d0ca49f0e1003bf67680d6c5831346966847e4ed Mon Sep 17 00:00:00 2001
From: Danilo Leal <67129314+danilo-leal@users.noreply.github.com>
Date: Tue, 5 Nov 2024 13:04:42 -0300
Subject: [PATCH] Make project search inputs full width (#20242)
Closes https://github.com/zed-industries/zed/issues/13099
This PR main thing is making the inputs in the project search full
width, but it also has some slight design and UI code improvements here
and there, such as extracting the common input styles to its own
variable.
I figure that the reason why the inputs weren't full width before is
just because it'd be hard to reach for the buttons when in a large
monitor with the app maximised _and_ with a single tab open. However, I
do feel like it's common not to have these conditions in place, too,
which make the small inputs too small, like the issue states. At the
very least, we also have the keybindings.
Here's the final result:
| Small window size | Big window size |
|--------|--------|
|
|
|
Release Notes:
- N/A
---
crates/search/src/project_search.rs | 255 ++++++++++++++--------------
1 file changed, 125 insertions(+), 130 deletions(-)
diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs
index b018f2969342709fd7b8c9eecb2ca28cf9218824..0edc38a09516fad5519f3ed301508afb1930f08e 100644
--- a/crates/search/src/project_search.rs
+++ b/crates/search/src/project_search.rs
@@ -34,8 +34,8 @@ use std::{
};
use theme::ThemeSettings;
use ui::{
- h_flex, prelude::*, v_flex, Icon, IconButton, IconName, KeyBinding, Label, LabelCommon,
- LabelSize, Selectable, Tooltip,
+ h_flex, prelude::*, v_flex, Icon, IconButton, IconButtonShape, IconName, KeyBinding, Label,
+ LabelCommon, LabelSize, Selectable, Tooltip,
};
use util::paths::PathMatcher;
use workspace::{
@@ -45,9 +45,6 @@ use workspace::{
ToolbarItemView, Workspace, WorkspaceId,
};
-const MIN_INPUT_WIDTH_REMS: f32 = 15.;
-const MAX_INPUT_WIDTH_REMS: f32 = 30.;
-
actions!(
project_search,
[SearchInNew, ToggleFocus, NextField, ToggleFilters]
@@ -669,7 +666,7 @@ impl ProjectSearchView {
let query_editor = cx.new_view(|cx| {
let mut editor = Editor::single_line(cx);
- editor.set_placeholder_text("Search all files..", cx);
+ editor.set_placeholder_text("Search all files...", cx);
editor.set_text(query_text, cx);
editor
});
@@ -692,7 +689,7 @@ impl ProjectSearchView {
);
let replacement_editor = cx.new_view(|cx| {
let mut editor = Editor::single_line(cx);
- editor.set_placeholder_text("Replace in project..", cx);
+ editor.set_placeholder_text("Replace in project...", cx);
if let Some(text) = replacement_text {
editor.set_text(text, cx);
}
@@ -1167,7 +1164,11 @@ impl ProjectSearchView {
let focus_handle = self.focus_handle.clone();
v_flex()
.gap_1()
- .child(Label::new("Hit enter to search. For more options:"))
+ .child(
+ Label::new("Hit enter to search. For more options:")
+ .color(Color::Muted)
+ .mb_2(),
+ )
.child(
Button::new("filter-paths", "Include/exclude specific paths")
.icon(IconName::Filter)
@@ -1555,23 +1556,25 @@ impl Render for ProjectSearchBar {
let search = search.read(cx);
let focus_handle = search.focus_handle(cx);
- let query_column = h_flex()
- .flex_1()
- .h_8()
- .mr_2()
- .px_2()
- .py_1()
- .border_1()
- .border_color(search.border_color_for(InputPanel::Query, cx))
- .rounded_lg()
- .min_w(rems(MIN_INPUT_WIDTH_REMS))
- .max_w(rems(MAX_INPUT_WIDTH_REMS))
+ let input_base_styles = || {
+ h_flex()
+ .w_full()
+ .h_8()
+ .px_2()
+ .py_1()
+ .border_1()
+ .border_color(search.border_color_for(InputPanel::Query, cx))
+ .rounded_lg()
+ };
+
+ let query_column = input_base_styles()
.on_action(cx.listener(|this, action, cx| this.confirm(action, cx)))
.on_action(cx.listener(|this, action, cx| this.previous_history_query(action, cx)))
.on_action(cx.listener(|this, action, cx| this.next_history_query(action, cx)))
.child(self.render_text_input(&search.query_editor, cx))
.child(
h_flex()
+ .gap_0p5()
.child(SearchOptions::CASE_SENSITIVE.as_button(
self.is_option_enabled(SearchOptions::CASE_SENSITIVE, cx),
focus_handle.clone(),
@@ -1595,58 +1598,60 @@ impl Render for ProjectSearchBar {
)),
);
- let mode_column = v_flex().items_start().justify_start().child(
- h_flex()
- .child(
- IconButton::new("project-search-filter-button", IconName::Filter)
- .tooltip(|cx| Tooltip::for_action("Toggle filters", &ToggleFilters, cx))
- .on_click(cx.listener(|this, _, cx| {
- this.toggle_filters(cx);
- }))
- .selected(
- self.active_project_search
- .as_ref()
- .map(|search| search.read(cx).filters_enabled)
- .unwrap_or_default(),
- )
- .tooltip({
- let focus_handle = focus_handle.clone();
- move |cx| {
- Tooltip::for_action_in(
- "Toggle filters",
- &ToggleFilters,
- &focus_handle,
- cx,
- )
- }
- }),
- )
- .child(
- IconButton::new("project-search-toggle-replace", IconName::Replace)
- .on_click(cx.listener(|this, _, cx| {
- this.toggle_replace(&ToggleReplace, cx);
- }))
- .selected(
- self.active_project_search
- .as_ref()
- .map(|search| search.read(cx).replace_enabled)
- .unwrap_or_default(),
- )
- .tooltip({
- let focus_handle = focus_handle.clone();
- move |cx| {
- Tooltip::for_action_in(
- "Toggle Replace",
- &ToggleReplace,
- &focus_handle,
- cx,
- )
- }
- }),
- ),
- );
+ let mode_column = h_flex()
+ .gap_1()
+ .child(
+ IconButton::new("project-search-filter-button", IconName::Filter)
+ .shape(IconButtonShape::Square)
+ .tooltip(|cx| Tooltip::for_action("Toggle Filters", &ToggleFilters, cx))
+ .on_click(cx.listener(|this, _, cx| {
+ this.toggle_filters(cx);
+ }))
+ .selected(
+ self.active_project_search
+ .as_ref()
+ .map(|search| search.read(cx).filters_enabled)
+ .unwrap_or_default(),
+ )
+ .tooltip({
+ let focus_handle = focus_handle.clone();
+ move |cx| {
+ Tooltip::for_action_in(
+ "Toggle Filters",
+ &ToggleFilters,
+ &focus_handle,
+ cx,
+ )
+ }
+ }),
+ )
+ .child(
+ IconButton::new("project-search-toggle-replace", IconName::Replace)
+ .shape(IconButtonShape::Square)
+ .on_click(cx.listener(|this, _, cx| {
+ this.toggle_replace(&ToggleReplace, cx);
+ }))
+ .selected(
+ self.active_project_search
+ .as_ref()
+ .map(|search| search.read(cx).replace_enabled)
+ .unwrap_or_default(),
+ )
+ .tooltip({
+ let focus_handle = focus_handle.clone();
+ move |cx| {
+ Tooltip::for_action_in(
+ "Toggle Replace",
+ &ToggleReplace,
+ &focus_handle,
+ cx,
+ )
+ }
+ }),
+ );
let limit_reached = search.model.read(cx).limit_reached;
+
let match_text = search
.active_match_index
.and_then(|index| {
@@ -1668,6 +1673,7 @@ impl Render for ProjectSearchBar {
let matches_column = h_flex()
.child(
IconButton::new("project-search-prev-match", IconName::ChevronLeft)
+ .shape(IconButtonShape::Square)
.disabled(search.active_match_index.is_none())
.on_click(cx.listener(|this, _, cx| {
if let Some(search) = this.active_project_search.as_ref() {
@@ -1680,7 +1686,7 @@ impl Render for ProjectSearchBar {
let focus_handle = focus_handle.clone();
move |cx| {
Tooltip::for_action_in(
- "Go to previous match",
+ "Go To Previous Match",
&SelectPrevMatch,
&focus_handle,
cx,
@@ -1690,6 +1696,7 @@ impl Render for ProjectSearchBar {
)
.child(
IconButton::new("project-search-next-match", IconName::ChevronRight)
+ .shape(IconButtonShape::Square)
.disabled(search.active_match_index.is_none())
.on_click(cx.listener(|this, _, cx| {
if let Some(search) = this.active_project_search.as_ref() {
@@ -1702,7 +1709,7 @@ impl Render for ProjectSearchBar {
let focus_handle = focus_handle.clone();
move |cx| {
Tooltip::for_action_in(
- "Go to next match",
+ "Go To Next Match",
&SelectNextMatch,
&focus_handle,
cx,
@@ -1711,9 +1718,9 @@ impl Render for ProjectSearchBar {
}),
)
.child(
- h_flex()
+ div()
.id("matches")
- .min_w(rems_from_px(40.))
+ .ml_0p5()
.child(
Label::new(match_text).color(if search.active_match_index.is_some() {
Color::Default
@@ -1729,27 +1736,23 @@ impl Render for ProjectSearchBar {
);
let search_line = h_flex()
- .flex_1()
+ .w_full()
+ .gap_1p5()
+ .pr_6()
.child(query_column)
.child(mode_column)
.child(matches_column);
let replace_line = search.replace_enabled.then(|| {
- let replace_column = h_flex()
- .flex_1()
- .min_w(rems(MIN_INPUT_WIDTH_REMS))
- .max_w(rems(MAX_INPUT_WIDTH_REMS))
- .h_8()
- .px_2()
- .py_1()
- .border_1()
- .border_color(cx.theme().colors().border)
- .rounded_lg()
- .child(self.render_text_input(&search.replacement_editor, cx));
+ let replace_column =
+ input_base_styles().child(self.render_text_input(&search.replacement_editor, cx));
+
let focus_handle = search.replacement_editor.read(cx).focus_handle(cx);
- let replace_actions = h_flex().when(search.replace_enabled, |this| {
+
+ let replace_actions = h_flex().gap_1().when(search.replace_enabled, |this| {
this.child(
IconButton::new("project-search-replace-next", IconName::ReplaceNext)
+ .shape(IconButtonShape::Square)
.on_click(cx.listener(|this, _, cx| {
if let Some(search) = this.active_project_search.as_ref() {
search.update(cx, |this, cx| {
@@ -1761,7 +1764,7 @@ impl Render for ProjectSearchBar {
let focus_handle = focus_handle.clone();
move |cx| {
Tooltip::for_action_in(
- "Replace next match",
+ "Replace Next Match",
&ReplaceNext,
&focus_handle,
cx,
@@ -1771,6 +1774,7 @@ impl Render for ProjectSearchBar {
)
.child(
IconButton::new("project-search-replace-all", IconName::ReplaceAll)
+ .shape(IconButtonShape::Square)
.on_click(cx.listener(|this, _, cx| {
if let Some(search) = this.active_project_search.as_ref() {
search.update(cx, |this, cx| {
@@ -1782,7 +1786,7 @@ impl Render for ProjectSearchBar {
let focus_handle = focus_handle.clone();
move |cx| {
Tooltip::for_action_in(
- "Replace all matches",
+ "Replace All Matches",
&ReplaceAll,
&focus_handle,
cx,
@@ -1791,9 +1795,11 @@ impl Render for ProjectSearchBar {
}),
)
});
+
h_flex()
- .pr(rems(5.5))
- .gap_2()
+ .w_full()
+ .gap_1p5()
+ .pr_24()
.child(replace_column)
.child(replace_actions)
});
@@ -1801,20 +1807,10 @@ impl Render for ProjectSearchBar {
let filter_line = search.filters_enabled.then(|| {
h_flex()
.w_full()
- .gap_2()
+ .gap_1p5()
+ .pr_24()
.child(
- h_flex()
- .flex_1()
- // chosen so the total width of the search bar line
- // is about the same as the include/exclude line
- .min_w(rems(10.25))
- .max_w(rems(20.))
- .h_8()
- .px_2()
- .py_1()
- .border_1()
- .border_color(search.border_color_for(InputPanel::Include, cx))
- .rounded_lg()
+ input_base_styles()
.on_action(
cx.listener(|this, action, cx| this.previous_history_query(action, cx)),
)
@@ -1824,16 +1820,7 @@ impl Render for ProjectSearchBar {
.child(self.render_text_input(&search.included_files_editor, cx)),
)
.child(
- h_flex()
- .flex_1()
- .min_w(rems(10.25))
- .max_w(rems(20.))
- .h_8()
- .px_2()
- .py_1()
- .border_1()
- .border_color(search.border_color_for(InputPanel::Exclude, cx))
- .rounded_lg()
+ input_base_styles()
.on_action(
cx.listener(|this, action, cx| this.previous_history_query(action, cx)),
)
@@ -1843,27 +1830,35 @@ impl Render for ProjectSearchBar {
.child(self.render_text_input(&search.excluded_files_editor, cx)),
)
.child(
- IconButton::new("project-search-opened-only", IconName::FileDoc)
- .selected(self.is_opened_only_enabled(cx))
- .tooltip(|cx| Tooltip::text("Only search open files", cx))
- .on_click(cx.listener(|this, _, cx| {
- this.toggle_opened_only(cx);
- })),
- )
- .child(
- SearchOptions::INCLUDE_IGNORED.as_button(
- search
- .search_options
- .contains(SearchOptions::INCLUDE_IGNORED),
- focus_handle.clone(),
- cx.listener(|this, _, cx| {
- this.toggle_search_option(SearchOptions::INCLUDE_IGNORED, cx);
- }),
- ),
+ h_flex()
+ .gap_1()
+ .child(
+ IconButton::new("project-search-opened-only", IconName::FileDoc)
+ .shape(IconButtonShape::Square)
+ .selected(self.is_opened_only_enabled(cx))
+ .tooltip(|cx| Tooltip::text("Only Search Open Files", cx))
+ .on_click(cx.listener(|this, _, cx| {
+ this.toggle_opened_only(cx);
+ })),
+ )
+ .child(
+ SearchOptions::INCLUDE_IGNORED.as_button(
+ search
+ .search_options
+ .contains(SearchOptions::INCLUDE_IGNORED),
+ focus_handle.clone(),
+ cx.listener(|this, _, cx| {
+ this.toggle_search_option(SearchOptions::INCLUDE_IGNORED, cx);
+ }),
+ ),
+ ),
)
});
+
let mut key_context = KeyContext::default();
+
key_context.add("ProjectSearchBar");
+
if search.replacement_editor.focus_handle(cx).is_focused(cx) {
key_context.add("in_replace");
}