search: Fix collapse/expand all button sync (#48773)
Derive collapsed state from `Editor.has_any_buffer_folded` instead of tracking it separately, removing redundant `ResultsCollapsedChanged` event and stale `is_collapsed`/`results_collapsed` fields. Closes #48734 Release Notes: - Fixed collapse/expand all button in buffer search and project search not syncing correctly when toggling individual file sections --------- Signed-off-by: Benjamin <5719034+bnjjj@users.noreply.github.com> Co-authored-by: dino <dinojoaocosta@gmail.com>
This commit is contained in:
1 parent
24dbd216d7
commit
b6c48b60ab
4 files changed
+242
-55
No files matched your search
@@ -217,7 +217,7 @@ use workspace::{
|
||||
TabBarSettings, Toast, ViewId, Workspace, WorkspaceId, WorkspaceSettings,
|
||||
item::{BreadcrumbText, ItemBufferKind, ItemHandle, PreviewTabsSettings, SaveOptions},
|
||||
notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
|
||||
searchable::{CollapseDirection, SearchEvent},
|
||||
searchable::SearchEvent,
|
||||
};
|
||||
use zed_actions::editor::{MoveDown, MoveUp};
|
||||
|
||||
@@ -20046,9 +20046,6 @@ impl Editor {
|
||||
.ok();
|
||||
});
|
||||
}
|
||||
cx.emit(SearchEvent::ResultsCollapsedChanged(
|
||||
CollapseDirection::Collapsed,
|
||||
));
|
||||
}
|
||||
|
||||
pub fn fold_function_bodies(
|
||||
@@ -20237,9 +20234,6 @@ impl Editor {
|
||||
.ok();
|
||||
});
|
||||
}
|
||||
cx.emit(SearchEvent::ResultsCollapsedChanged(
|
||||
CollapseDirection::Expanded,
|
||||
));
|
||||
}
|
||||
|
||||
pub fn fold_selected_ranges(
|
||||
@@ -20350,6 +20344,13 @@ impl Editor {
|
||||
self.display_map.read(cx).is_buffer_folded(buffer)
|
||||
}
|
||||
|
||||
pub fn has_any_buffer_folded(&self, cx: &App) -> bool {
|
||||
if self.buffer().read(cx).is_singleton() {
|
||||
return false;
|
||||
}
|
||||
!self.folded_buffers(cx).is_empty()
|
||||
}
|
||||
|
||||
pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
|
||||
self.display_map.read(cx).folded_buffers()
|
||||
}
|
||||
|
||||
@@ -42,8 +42,8 @@ use workspace::{
|
||||
ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace,
|
||||
item::{ItemBufferKind, ItemHandle},
|
||||
searchable::{
|
||||
CollapseDirection, Direction, FilteredSearchRange, SearchEvent, SearchToken,
|
||||
SearchableItemHandle, WeakSearchableItemHandle,
|
||||
Direction, FilteredSearchRange, SearchEvent, SearchToken, SearchableItemHandle,
|
||||
WeakSearchableItemHandle,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -94,7 +94,6 @@ pub struct BufferSearchBar {
|
||||
editor_scroll_handle: ScrollHandle,
|
||||
editor_needed_width: Pixels,
|
||||
regex_language: Option<Arc<Language>>,
|
||||
is_collapsed: bool,
|
||||
splittable_editor: Option<WeakEntity<SplittableEditor>>,
|
||||
_splittable_editor_subscription: Option<Subscription>,
|
||||
}
|
||||
@@ -212,7 +211,14 @@ impl Render for BufferSearchBar {
|
||||
let collapse_expand_button = if self.needs_expand_collapse_option(cx) {
|
||||
let query_editor_focus = self.query_editor.focus_handle(cx);
|
||||
|
||||
let (icon, tooltip_label) = if self.is_collapsed {
|
||||
let is_collapsed = self
|
||||
.active_searchable_item
|
||||
.as_ref()
|
||||
.and_then(|item| item.act_as_type(TypeId::of::<Editor>(), cx))
|
||||
.and_then(|item| item.downcast::<Editor>().ok())
|
||||
.map(|editor: Entity<Editor>| editor.read(cx).has_any_buffer_folded(cx))
|
||||
.unwrap_or_default();
|
||||
let (icon, tooltip_label) = if is_collapsed {
|
||||
(IconName::ChevronUpDown, "Expand All Files")
|
||||
} else {
|
||||
(IconName::ChevronDownUp, "Collapse All Files")
|
||||
@@ -887,7 +893,6 @@ impl BufferSearchBar {
|
||||
editor_scroll_handle: ScrollHandle::new(),
|
||||
editor_needed_width: px(0.),
|
||||
regex_language: None,
|
||||
is_collapsed: false,
|
||||
splittable_editor: None,
|
||||
_splittable_editor_subscription: None,
|
||||
}
|
||||
@@ -1053,11 +1058,11 @@ impl BufferSearchBar {
|
||||
}
|
||||
|
||||
fn toggle_fold_all_in_item(&self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
let is_collapsed = self.is_collapsed;
|
||||
if let Some(item) = &self.active_searchable_item {
|
||||
if let Some(item) = item.act_as_type(TypeId::of::<Editor>(), cx) {
|
||||
let editor = item.downcast::<Editor>().expect("Is an editor");
|
||||
editor.update(cx, |editor, cx| {
|
||||
let is_collapsed = editor.has_any_buffer_folded(cx);
|
||||
if is_collapsed {
|
||||
editor.unfold_all(&UnfoldAll, window, cx);
|
||||
} else {
|
||||
@@ -1434,15 +1439,6 @@ impl BufferSearchBar {
|
||||
drop(self.update_matches(false, false, window, cx));
|
||||
}
|
||||
SearchEvent::ActiveMatchChanged => self.update_match_index(window, cx),
|
||||
SearchEvent::ResultsCollapsedChanged(collapse_direction) => {
|
||||
if self.needs_expand_collapse_option(cx) {
|
||||
match collapse_direction {
|
||||
CollapseDirection::Collapsed => self.is_collapsed = true,
|
||||
CollapseDirection::Expanded => self.is_collapsed = false,
|
||||
}
|
||||
}
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3395,20 +3391,81 @@ mod tests {
|
||||
editor.update_in(cx, |editor, window, cx| {
|
||||
editor.fold_all(&FoldAll, window, cx);
|
||||
});
|
||||
cx.run_until_parked();
|
||||
|
||||
let is_collapsed = search_bar.read_with(cx, |search_bar, _| search_bar.is_collapsed);
|
||||
|
||||
let is_collapsed = editor.read_with(cx, |editor, cx| editor.has_any_buffer_folded(cx));
|
||||
assert!(is_collapsed);
|
||||
|
||||
editor.update_in(cx, |editor, window, cx| {
|
||||
editor.unfold_all(&UnfoldAll, window, cx);
|
||||
});
|
||||
cx.run_until_parked();
|
||||
|
||||
let is_collapsed = search_bar.read_with(cx, |search_bar, _| search_bar.is_collapsed);
|
||||
|
||||
let is_collapsed = editor.read_with(cx, |editor, cx| editor.has_any_buffer_folded(cx));
|
||||
assert!(!is_collapsed);
|
||||
}
|
||||
|
||||
#[perf]
|
||||
#[gpui::test]
|
||||
async fn test_collapse_state_syncs_after_manual_buffer_fold(cx: &mut TestAppContext) {
|
||||
let (editor, search_bar, cx) = init_multibuffer_test(cx);
|
||||
|
||||
search_bar.update_in(cx, |search_bar, window, cx| {
|
||||
search_bar.set_active_pane_item(Some(&editor), window, cx);
|
||||
});
|
||||
|
||||
// Fold all buffers via fold_all
|
||||
editor.update_in(cx, |editor, window, cx| {
|
||||
editor.fold_all(&FoldAll, window, cx);
|
||||
});
|
||||
cx.run_until_parked();
|
||||
|
||||
let has_any_folded = editor.read_with(cx, |editor, cx| editor.has_any_buffer_folded(cx));
|
||||
assert!(
|
||||
has_any_folded,
|
||||
"All buffers should be folded after fold_all"
|
||||
);
|
||||
|
||||
// Manually unfold one buffer (simulating a chevron click)
|
||||
let first_buffer_id = editor.read_with(cx, |editor, cx| {
|
||||
editor.buffer().read(cx).excerpt_buffer_ids()[0]
|
||||
});
|
||||
editor.update_in(cx, |editor, _window, cx| {
|
||||
editor.unfold_buffer(first_buffer_id, cx);
|
||||
});
|
||||
|
||||
let has_any_folded = editor.read_with(cx, |editor, cx| editor.has_any_buffer_folded(cx));
|
||||
assert!(
|
||||
has_any_folded,
|
||||
"Should still report folds when only one buffer is unfolded"
|
||||
);
|
||||
|
||||
// Manually unfold the second buffer too
|
||||
let second_buffer_id = editor.read_with(cx, |editor, cx| {
|
||||
editor.buffer().read(cx).excerpt_buffer_ids()[1]
|
||||
});
|
||||
editor.update_in(cx, |editor, _window, cx| {
|
||||
editor.unfold_buffer(second_buffer_id, cx);
|
||||
});
|
||||
|
||||
let has_any_folded = editor.read_with(cx, |editor, cx| editor.has_any_buffer_folded(cx));
|
||||
assert!(
|
||||
!has_any_folded,
|
||||
"No folds should remain after unfolding all buffers individually"
|
||||
);
|
||||
|
||||
// Manually fold one buffer back
|
||||
editor.update_in(cx, |editor, _window, cx| {
|
||||
editor.fold_buffer(first_buffer_id, cx);
|
||||
});
|
||||
|
||||
let has_any_folded = editor.read_with(cx, |editor, cx| editor.has_any_buffer_folded(cx));
|
||||
assert!(
|
||||
has_any_folded,
|
||||
"Should report folds after manually folding one buffer"
|
||||
);
|
||||
}
|
||||
|
||||
#[perf]
|
||||
#[gpui::test]
|
||||
async fn test_search_options_changes(cx: &mut TestAppContext) {
|
||||
|
||||
@@ -49,10 +49,7 @@ use workspace::{
|
||||
DeploySearch, ItemNavHistory, NewSearch, ToolbarItemEvent, ToolbarItemLocation,
|
||||
ToolbarItemView, Workspace, WorkspaceId,
|
||||
item::{Item, ItemEvent, ItemHandle, SaveOptions},
|
||||
searchable::{
|
||||
CollapseDirection, Direction, SearchEvent, SearchToken, SearchableItem,
|
||||
SearchableItemHandle,
|
||||
},
|
||||
searchable::{Direction, SearchEvent, SearchToken, SearchableItem, SearchableItemHandle},
|
||||
};
|
||||
|
||||
actions!(
|
||||
@@ -269,7 +266,6 @@ pub struct ProjectSearchView {
|
||||
replace_enabled: bool,
|
||||
included_opened_only: bool,
|
||||
regex_language: Option<Arc<Language>>,
|
||||
results_collapsed: bool,
|
||||
_subscriptions: Vec<Subscription>,
|
||||
}
|
||||
|
||||
@@ -814,8 +810,9 @@ impl ProjectSearchView {
|
||||
}
|
||||
|
||||
fn update_results_visibility(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
let has_any_folded = self.results_editor.read(cx).has_any_buffer_folded(cx);
|
||||
self.results_editor.update(cx, |editor, cx| {
|
||||
if self.results_collapsed {
|
||||
if has_any_folded {
|
||||
editor.unfold_all(&UnfoldAll, window, cx);
|
||||
} else {
|
||||
editor.fold_all(&FoldAll, window, cx);
|
||||
@@ -910,18 +907,7 @@ impl ProjectSearchView {
|
||||
);
|
||||
subscriptions.push(cx.subscribe(
|
||||
&results_editor,
|
||||
|this, _editor, event: &SearchEvent, cx| {
|
||||
match event {
|
||||
SearchEvent::ResultsCollapsedChanged(collapsed_direction) => {
|
||||
match collapsed_direction {
|
||||
CollapseDirection::Collapsed => this.results_collapsed = true,
|
||||
CollapseDirection::Expanded => this.results_collapsed = false,
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
};
|
||||
cx.notify();
|
||||
},
|
||||
|_this, _editor, _event: &SearchEvent, cx| cx.notify(),
|
||||
));
|
||||
|
||||
let included_files_editor = cx.new(|cx| {
|
||||
@@ -997,7 +983,6 @@ impl ProjectSearchView {
|
||||
replace_enabled: false,
|
||||
included_opened_only: false,
|
||||
regex_language: None,
|
||||
results_collapsed: false,
|
||||
_subscriptions: subscriptions,
|
||||
};
|
||||
|
||||
@@ -2224,7 +2209,7 @@ impl Render for ProjectSearchBar {
|
||||
))
|
||||
.child(matches_column);
|
||||
|
||||
let is_collapsed = search.results_collapsed;
|
||||
let is_collapsed = search.results_editor.read(cx).has_any_buffer_folded(cx);
|
||||
|
||||
let (icon, tooltip_label) = if is_collapsed {
|
||||
(IconName::ChevronUpDown, "Expand All Search Results")
|
||||
@@ -2804,9 +2789,15 @@ pub mod tests {
|
||||
})
|
||||
})
|
||||
.expect("Should fold fine");
|
||||
cx.run_until_parked();
|
||||
|
||||
let results_collapsed = search_view
|
||||
.read_with(cx, |search_view, _| search_view.results_collapsed)
|
||||
.read_with(cx, |search_view, cx| {
|
||||
search_view
|
||||
.results_editor
|
||||
.read(cx)
|
||||
.has_any_buffer_folded(cx)
|
||||
})
|
||||
.expect("got results_collapsed");
|
||||
|
||||
assert!(results_collapsed);
|
||||
@@ -2817,14 +2808,159 @@ pub mod tests {
|
||||
})
|
||||
})
|
||||
.expect("Should unfold fine");
|
||||
cx.run_until_parked();
|
||||
|
||||
let results_collapsed = search_view
|
||||
.read_with(cx, |search_view, _| search_view.results_collapsed)
|
||||
.read_with(cx, |search_view, cx| {
|
||||
search_view
|
||||
.results_editor
|
||||
.read(cx)
|
||||
.has_any_buffer_folded(cx)
|
||||
})
|
||||
.expect("got results_collapsed");
|
||||
|
||||
assert!(!results_collapsed);
|
||||
}
|
||||
|
||||
#[perf]
|
||||
#[gpui::test]
|
||||
async fn test_collapse_state_syncs_after_manual_buffer_fold(cx: &mut TestAppContext) {
|
||||
init_test(cx);
|
||||
|
||||
let fs = FakeFs::new(cx.background_executor.clone());
|
||||
fs.insert_tree(
|
||||
path!("/dir"),
|
||||
json!({
|
||||
"one.rs": "const ONE: usize = 1;",
|
||||
"two.rs": "const TWO: usize = one::ONE + one::ONE;",
|
||||
"three.rs": "const THREE: usize = one::ONE + two::TWO;",
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
|
||||
let window =
|
||||
cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
|
||||
let workspace = window
|
||||
.read_with(cx, |mw, _| mw.workspace().clone())
|
||||
.unwrap();
|
||||
let search = cx.new(|cx| ProjectSearch::new(project.clone(), cx));
|
||||
let search_view = cx.add_window(|window, cx| {
|
||||
ProjectSearchView::new(workspace.downgrade(), search.clone(), window, cx, None)
|
||||
});
|
||||
|
||||
// Search for "ONE" which appears in all 3 files
|
||||
perform_search(search_view, "ONE", cx);
|
||||
|
||||
// Verify initial state: no folds
|
||||
let has_any_folded = search_view
|
||||
.read_with(cx, |search_view, cx| {
|
||||
search_view
|
||||
.results_editor
|
||||
.read(cx)
|
||||
.has_any_buffer_folded(cx)
|
||||
})
|
||||
.expect("should read state");
|
||||
assert!(!has_any_folded, "No buffers should be folded initially");
|
||||
|
||||
// Fold all via fold_all
|
||||
search_view
|
||||
.update(cx, |search_view, window, cx| {
|
||||
search_view.results_editor.update(cx, |editor, cx| {
|
||||
editor.fold_all(&FoldAll, window, cx);
|
||||
})
|
||||
})
|
||||
.expect("Should fold fine");
|
||||
cx.run_until_parked();
|
||||
|
||||
let has_any_folded = search_view
|
||||
.read_with(cx, |search_view, cx| {
|
||||
search_view
|
||||
.results_editor
|
||||
.read(cx)
|
||||
.has_any_buffer_folded(cx)
|
||||
})
|
||||
.expect("should read state");
|
||||
assert!(
|
||||
has_any_folded,
|
||||
"All buffers should be folded after fold_all"
|
||||
);
|
||||
|
||||
// Manually unfold one buffer (simulating a chevron click)
|
||||
let first_buffer_id = search_view
|
||||
.read_with(cx, |search_view, cx| {
|
||||
search_view
|
||||
.results_editor
|
||||
.read(cx)
|
||||
.buffer()
|
||||
.read(cx)
|
||||
.excerpt_buffer_ids()[0]
|
||||
})
|
||||
.expect("should read buffer ids");
|
||||
|
||||
search_view
|
||||
.update(cx, |search_view, _window, cx| {
|
||||
search_view.results_editor.update(cx, |editor, cx| {
|
||||
editor.unfold_buffer(first_buffer_id, cx);
|
||||
})
|
||||
})
|
||||
.expect("Should unfold one buffer");
|
||||
|
||||
let has_any_folded = search_view
|
||||
.read_with(cx, |search_view, cx| {
|
||||
search_view
|
||||
.results_editor
|
||||
.read(cx)
|
||||
.has_any_buffer_folded(cx)
|
||||
})
|
||||
.expect("should read state");
|
||||
assert!(
|
||||
has_any_folded,
|
||||
"Should still report folds when only one buffer is unfolded"
|
||||
);
|
||||
|
||||
// Unfold all via unfold_all
|
||||
search_view
|
||||
.update(cx, |search_view, window, cx| {
|
||||
search_view.results_editor.update(cx, |editor, cx| {
|
||||
editor.unfold_all(&UnfoldAll, window, cx);
|
||||
})
|
||||
})
|
||||
.expect("Should unfold fine");
|
||||
cx.run_until_parked();
|
||||
|
||||
let has_any_folded = search_view
|
||||
.read_with(cx, |search_view, cx| {
|
||||
search_view
|
||||
.results_editor
|
||||
.read(cx)
|
||||
.has_any_buffer_folded(cx)
|
||||
})
|
||||
.expect("should read state");
|
||||
assert!(!has_any_folded, "No folds should remain after unfold_all");
|
||||
|
||||
// Manually fold one buffer back (simulating a chevron click)
|
||||
search_view
|
||||
.update(cx, |search_view, _window, cx| {
|
||||
search_view.results_editor.update(cx, |editor, cx| {
|
||||
editor.fold_buffer(first_buffer_id, cx);
|
||||
})
|
||||
})
|
||||
.expect("Should fold one buffer");
|
||||
|
||||
let has_any_folded = search_view
|
||||
.read_with(cx, |search_view, cx| {
|
||||
search_view
|
||||
.results_editor
|
||||
.read(cx)
|
||||
.has_any_buffer_folded(cx)
|
||||
})
|
||||
.expect("should read state");
|
||||
assert!(
|
||||
has_any_folded,
|
||||
"Should report folds after manually folding one buffer"
|
||||
);
|
||||
}
|
||||
|
||||
#[perf]
|
||||
#[gpui::test]
|
||||
async fn test_deploy_project_search_focus(cx: &mut TestAppContext) {
|
||||
|
||||
@@ -25,17 +25,10 @@ impl SearchToken {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum CollapseDirection {
|
||||
Collapsed,
|
||||
Expanded,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum SearchEvent {
|
||||
MatchesInvalidated,
|
||||
ActiveMatchChanged,
|
||||
ResultsCollapsedChanged(CollapseDirection),
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
|
||||
|
||||
Reference in new issue
Block a user