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 |
|--------|--------|
| <img width="1279" alt="Screenshot 2024-11-05 at 11 18 08"
src="https://github.com/user-attachments/assets/73548300-1ad2-4ed0-b99f-adb3212ac163">
| <img width="2992" alt="Screenshot 2024-11-05 at 11 24 06"
src="https://github.com/user-attachments/assets/3a1ccabd-2350-42f0-8e31-112f27da98a4">
|

Release Notes:

- N/A
This commit is contained in:
Danilo Leal 2024-11-05 13:04:42 -03:00 committed by GitHub
parent d6fcd9853a
commit d0ca49f0e1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -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");
}