mirror of
https://github.com/zed-industries/zed.git
synced 2026-06-01 03:14:56 +07:00
project_search: Add button to collapse/expand all excerpts (#41654)
<img width="500" height="834" alt="Screenshot 2025-11-03 at 12 59@2x" src="https://github.com/user-attachments/assets/15c5e1fc-2291-41b4-9eec-a8cfa5a446c7" /> Releases Note: - Added a button that allows to expand/collapse all project search excerpts at once. --------- Co-authored-by: Danilo Leal <daniloleal09@gmail.com>
This commit is contained in:
parent
45b78482f5
commit
7cfce60570
9 changed files with 125 additions and 8 deletions
4
assets/icons/chevron_down_up.svg
Normal file
4
assets/icons/chevron_down_up.svg
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M11.3335 13.3333L8.00017 10L4.66685 13.3333" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M11.3335 2.66669L8.00017 6.00002L4.66685 2.66669" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 382 B |
|
|
@ -407,6 +407,7 @@
|
|||
"bindings": {
|
||||
"escape": "project_search::ToggleFocus",
|
||||
"shift-find": "search::FocusSearch",
|
||||
"shift-enter": "project_search::ToggleAllSearchResults",
|
||||
"ctrl-shift-f": "search::FocusSearch",
|
||||
"ctrl-shift-h": "search::ToggleReplace",
|
||||
"alt-ctrl-g": "search::ToggleRegex",
|
||||
|
|
@ -479,6 +480,7 @@
|
|||
"alt-w": "search::ToggleWholeWord",
|
||||
"alt-find": "project_search::ToggleFilters",
|
||||
"alt-ctrl-f": "project_search::ToggleFilters",
|
||||
"shift-enter": "project_search::ToggleAllSearchResults",
|
||||
"ctrl-alt-shift-r": "search::ToggleRegex",
|
||||
"ctrl-alt-shift-x": "search::ToggleRegex",
|
||||
"alt-r": "search::ToggleRegex",
|
||||
|
|
|
|||
|
|
@ -468,6 +468,7 @@
|
|||
"bindings": {
|
||||
"escape": "project_search::ToggleFocus",
|
||||
"cmd-shift-j": "project_search::ToggleFilters",
|
||||
"shift-enter": "project_search::ToggleAllSearchResults",
|
||||
"cmd-shift-f": "search::FocusSearch",
|
||||
"cmd-shift-h": "search::ToggleReplace",
|
||||
"alt-cmd-g": "search::ToggleRegex",
|
||||
|
|
@ -496,6 +497,7 @@
|
|||
"bindings": {
|
||||
"escape": "project_search::ToggleFocus",
|
||||
"cmd-shift-j": "project_search::ToggleFilters",
|
||||
"shift-enter": "project_search::ToggleAllSearchResults",
|
||||
"cmd-shift-h": "search::ToggleReplace",
|
||||
"alt-cmd-g": "search::ToggleRegex",
|
||||
"alt-cmd-x": "search::ToggleRegex"
|
||||
|
|
|
|||
|
|
@ -488,6 +488,7 @@
|
|||
"alt-c": "search::ToggleCaseSensitive",
|
||||
"alt-w": "search::ToggleWholeWord",
|
||||
"alt-f": "project_search::ToggleFilters",
|
||||
"shift-enter": "project_search::ToggleAllSearchResults",
|
||||
"alt-r": "search::ToggleRegex",
|
||||
// "ctrl-shift-alt-x": "search::ToggleRegex",
|
||||
"ctrl-k shift-enter": "pane::TogglePinTab"
|
||||
|
|
|
|||
|
|
@ -100,13 +100,21 @@ impl Render for Breadcrumbs {
|
|||
|
||||
let breadcrumbs_stack = h_flex().gap_1().children(breadcrumbs);
|
||||
|
||||
let prefix_element = active_item.breadcrumb_prefix(window, cx);
|
||||
|
||||
let breadcrumbs = if let Some(prefix) = prefix_element {
|
||||
h_flex().gap_1p5().child(prefix).child(breadcrumbs_stack)
|
||||
} else {
|
||||
breadcrumbs_stack
|
||||
};
|
||||
|
||||
match active_item
|
||||
.downcast::<Editor>()
|
||||
.map(|editor| editor.downgrade())
|
||||
{
|
||||
Some(editor) => element.child(
|
||||
ButtonLike::new("toggle outline view")
|
||||
.child(breadcrumbs_stack)
|
||||
.child(breadcrumbs)
|
||||
.style(ButtonStyle::Transparent)
|
||||
.on_click({
|
||||
let editor = editor.clone();
|
||||
|
|
@ -141,7 +149,7 @@ impl Render for Breadcrumbs {
|
|||
// Match the height and padding of the `ButtonLike` in the other arm.
|
||||
.h(rems_from_px(22.))
|
||||
.pl_1()
|
||||
.child(breadcrumbs_stack),
|
||||
.child(breadcrumbs),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -53,6 +53,7 @@ pub enum IconName {
|
|||
Check,
|
||||
CheckDouble,
|
||||
ChevronDown,
|
||||
ChevronDownUp,
|
||||
ChevronLeft,
|
||||
ChevronRight,
|
||||
ChevronUp,
|
||||
|
|
|
|||
|
|
@ -57,7 +57,9 @@ actions!(
|
|||
/// Moves to the next input field.
|
||||
NextField,
|
||||
/// Toggles the search filters panel.
|
||||
ToggleFilters
|
||||
ToggleFilters,
|
||||
/// Toggles collapse/expand state of all search result excerpts.
|
||||
ToggleAllSearchResults
|
||||
]
|
||||
);
|
||||
|
||||
|
|
@ -120,6 +122,20 @@ pub fn init(cx: &mut App) {
|
|||
ProjectSearchView::search_in_new(workspace, action, window, cx)
|
||||
});
|
||||
|
||||
register_workspace_action_for_present_search(
|
||||
workspace,
|
||||
|workspace, action: &ToggleAllSearchResults, window, cx| {
|
||||
if let Some(search_view) = workspace
|
||||
.active_item(cx)
|
||||
.and_then(|item| item.downcast::<ProjectSearchView>())
|
||||
{
|
||||
search_view.update(cx, |search_view, cx| {
|
||||
search_view.toggle_all_search_results(action, window, cx);
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
register_workspace_action_for_present_search(
|
||||
workspace,
|
||||
|workspace, _: &menu::Cancel, window, cx| {
|
||||
|
|
@ -219,6 +235,7 @@ pub struct ProjectSearchView {
|
|||
replace_enabled: bool,
|
||||
included_opened_only: bool,
|
||||
regex_language: Option<Arc<Language>>,
|
||||
results_collapsed: bool,
|
||||
_subscriptions: Vec<Subscription>,
|
||||
}
|
||||
|
||||
|
|
@ -651,6 +668,44 @@ impl Item for ProjectSearchView {
|
|||
fn breadcrumbs(&self, theme: &theme::Theme, cx: &App) -> Option<Vec<BreadcrumbText>> {
|
||||
self.results_editor.breadcrumbs(theme, cx)
|
||||
}
|
||||
|
||||
fn breadcrumb_prefix(
|
||||
&self,
|
||||
_window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Option<gpui::AnyElement> {
|
||||
if !self.has_matches() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let is_collapsed = self.results_collapsed;
|
||||
|
||||
let (icon, tooltip_label) = if is_collapsed {
|
||||
(IconName::ChevronUpDown, "Expand All Search Results")
|
||||
} else {
|
||||
(IconName::ChevronDownUp, "Collapse All Search Results")
|
||||
};
|
||||
|
||||
let focus_handle = self.query_editor.focus_handle(cx);
|
||||
|
||||
Some(
|
||||
IconButton::new("project-search-collapse-expand", icon)
|
||||
.shape(IconButtonShape::Square)
|
||||
.icon_size(IconSize::Small)
|
||||
.tooltip(move |_, cx| {
|
||||
Tooltip::for_action_in(
|
||||
tooltip_label,
|
||||
&ToggleAllSearchResults,
|
||||
&focus_handle,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.on_click(cx.listener(|this, _, window, cx| {
|
||||
this.toggle_all_search_results(&ToggleAllSearchResults, window, cx);
|
||||
}))
|
||||
.into_any_element(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl ProjectSearchView {
|
||||
|
|
@ -753,6 +808,34 @@ impl ProjectSearchView {
|
|||
});
|
||||
}
|
||||
|
||||
fn toggle_all_search_results(
|
||||
&mut self,
|
||||
_: &ToggleAllSearchResults,
|
||||
_window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.results_collapsed = !self.results_collapsed;
|
||||
self.update_results_visibility(cx);
|
||||
}
|
||||
|
||||
fn update_results_visibility(&mut self, cx: &mut Context<Self>) {
|
||||
self.results_editor.update(cx, |editor, cx| {
|
||||
let multibuffer = editor.buffer().read(cx);
|
||||
let buffer_ids = multibuffer.excerpt_buffer_ids();
|
||||
|
||||
if self.results_collapsed {
|
||||
for buffer_id in buffer_ids {
|
||||
editor.fold_buffer(buffer_id, cx);
|
||||
}
|
||||
} else {
|
||||
for buffer_id in buffer_ids {
|
||||
editor.unfold_buffer(buffer_id, cx);
|
||||
}
|
||||
}
|
||||
});
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn new(
|
||||
workspace: WeakEntity<Workspace>,
|
||||
entity: Entity<ProjectSearch>,
|
||||
|
|
@ -911,8 +994,10 @@ impl ProjectSearchView {
|
|||
replace_enabled: false,
|
||||
included_opened_only: false,
|
||||
regex_language: None,
|
||||
results_collapsed: false,
|
||||
_subscriptions: subscriptions,
|
||||
};
|
||||
|
||||
this.entity_changed(window, cx);
|
||||
this
|
||||
}
|
||||
|
|
@ -1411,6 +1496,7 @@ impl ProjectSearchView {
|
|||
|
||||
fn entity_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
let match_ranges = self.entity.read(cx).match_ranges.clone();
|
||||
|
||||
if match_ranges.is_empty() {
|
||||
self.active_match_index = None;
|
||||
self.results_editor.update(cx, |editor, cx| {
|
||||
|
|
@ -1968,6 +2054,8 @@ impl Render for ProjectSearchBar {
|
|||
})
|
||||
.unwrap_or_else(|| "0/0".to_string());
|
||||
|
||||
let query_focus = search.query_editor.focus_handle(cx);
|
||||
|
||||
let query_column = input_base_styles(InputPanel::Query)
|
||||
.on_action(cx.listener(|this, action, window, cx| this.confirm(action, window, cx)))
|
||||
.on_action(cx.listener(|this, action, window, cx| {
|
||||
|
|
@ -1997,11 +2085,9 @@ impl Render for ProjectSearchBar {
|
|||
)),
|
||||
);
|
||||
|
||||
let query_focus = search.query_editor.focus_handle(cx);
|
||||
|
||||
let matches_column = h_flex()
|
||||
.pl_2()
|
||||
.ml_2()
|
||||
.ml_1()
|
||||
.pl_1p5()
|
||||
.border_l_1()
|
||||
.border_color(theme_colors.border_variant)
|
||||
.child(render_action_button(
|
||||
|
|
|
|||
|
|
@ -46,7 +46,6 @@ pub(crate) fn input_base_styles(border_color: Hsla, map: impl FnOnce(Div) -> Div
|
|||
.h_8()
|
||||
.pl_2()
|
||||
.pr_1()
|
||||
.py_1()
|
||||
.border_1()
|
||||
.border_color(border_color)
|
||||
.rounded_md()
|
||||
|
|
|
|||
|
|
@ -296,6 +296,15 @@ pub trait Item: Focusable + EventEmitter<Self::Event> + Render + Sized {
|
|||
None
|
||||
}
|
||||
|
||||
/// Returns optional elements to render to the left of the breadcrumb.
|
||||
fn breadcrumb_prefix(
|
||||
&self,
|
||||
_window: &mut Window,
|
||||
_cx: &mut Context<Self>,
|
||||
) -> Option<gpui::AnyElement> {
|
||||
None
|
||||
}
|
||||
|
||||
fn added_to_workspace(
|
||||
&mut self,
|
||||
_workspace: &mut Workspace,
|
||||
|
|
@ -479,6 +488,7 @@ pub trait ItemHandle: 'static + Send {
|
|||
fn to_searchable_item_handle(&self, cx: &App) -> Option<Box<dyn SearchableItemHandle>>;
|
||||
fn breadcrumb_location(&self, cx: &App) -> ToolbarItemLocation;
|
||||
fn breadcrumbs(&self, theme: &Theme, cx: &App) -> Option<Vec<BreadcrumbText>>;
|
||||
fn breadcrumb_prefix(&self, window: &mut Window, cx: &mut App) -> Option<gpui::AnyElement>;
|
||||
fn show_toolbar(&self, cx: &App) -> bool;
|
||||
fn pixel_position_of_cursor(&self, cx: &App) -> Option<Point<Pixels>>;
|
||||
fn downgrade_item(&self) -> Box<dyn WeakItemHandle>;
|
||||
|
|
@ -979,6 +989,10 @@ impl<T: Item> ItemHandle for Entity<T> {
|
|||
self.read(cx).breadcrumbs(theme, cx)
|
||||
}
|
||||
|
||||
fn breadcrumb_prefix(&self, window: &mut Window, cx: &mut App) -> Option<gpui::AnyElement> {
|
||||
self.update(cx, |item, cx| item.breadcrumb_prefix(window, cx))
|
||||
}
|
||||
|
||||
fn show_toolbar(&self, cx: &App) -> bool {
|
||||
self.read(cx).show_toolbar()
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue