From f3c32472b2c4122ce58210d09e7b4a5346d09d25 Mon Sep 17 00:00:00 2001 From: zeld-a Date: Tue, 28 Oct 2025 01:09:48 -0400 Subject: [PATCH 01/18] fix collaps/expand all entries --- crates/outline_panel/src/outline_panel.rs | 96 ++++++++++++----------- 1 file changed, 49 insertions(+), 47 deletions(-) diff --git a/crates/outline_panel/src/outline_panel.rs b/crates/outline_panel/src/outline_panel.rs index 112aa3d21ebda9..2388385aa26027 100644 --- a/crates/outline_panel/src/outline_panel.rs +++ b/crates/outline_panel/src/outline_panel.rs @@ -1635,56 +1635,53 @@ impl OutlinePanel { let Some(active_editor) = self.active_editor() else { return; }; + let mut buffers_to_unfold = HashSet::default(); - let expanded_entries = - self.fs_entries - .iter() - .fold(HashSet::default(), |mut entries, fs_entry| { - match fs_entry { - FsEntry::ExternalFile(external_file) => { - buffers_to_unfold.insert(external_file.buffer_id); - entries.insert(CollapsedEntry::ExternalFile(external_file.buffer_id)); - entries.extend( - self.excerpts - .get(&external_file.buffer_id) - .into_iter() - .flat_map(|excerpts| { - excerpts.keys().map(|excerpt_id| { - CollapsedEntry::Excerpt( - external_file.buffer_id, - *excerpt_id, - ) - }) - }), - ); - } - FsEntry::Directory(directory) => { - entries.insert(CollapsedEntry::Dir( - directory.worktree_id, - directory.entry.id, - )); - } - FsEntry::File(file) => { - buffers_to_unfold.insert(file.buffer_id); - entries.insert(CollapsedEntry::File(file.worktree_id, file.buffer_id)); - entries.extend( - self.excerpts.get(&file.buffer_id).into_iter().flat_map( - |excerpts| { - excerpts.keys().map(|excerpt_id| { - CollapsedEntry::Excerpt(file.buffer_id, *excerpt_id) - }) - }, - ), - ); - } - }; - entries - }); - self.collapsed_entries - .retain(|entry| !expanded_entries.contains(entry)); + let entries_to_remove = self + .cached_entries + .iter() + .filter_map(|cached_entry| match &cached_entry.entry { + PanelEntry::Fs(FsEntry::Directory(FsEntryDirectory { + worktree_id, entry, .. + })) => Some(CollapsedEntry::Dir(*worktree_id, entry.id)), + PanelEntry::Fs(FsEntry::File(FsEntryFile { + worktree_id, + buffer_id, + .. + })) => { + buffers_to_unfold.insert(*buffer_id); + Some(CollapsedEntry::File(*worktree_id, *buffer_id)) + } + PanelEntry::Fs(FsEntry::ExternalFile(external_file)) => { + buffers_to_unfold.insert(external_file.buffer_id); + Some(CollapsedEntry::ExternalFile(external_file.buffer_id)) + } + PanelEntry::FoldedDirs(FoldedDirsEntry { + worktree_id, + entries, + .. + }) => entries.last().map(|last| CollapsedEntry::Dir(*worktree_id, last.id)), + PanelEntry::Outline(OutlineEntry::Excerpt(excerpt)) => { + Some(CollapsedEntry::Excerpt(excerpt.buffer_id, excerpt.id)) + } + PanelEntry::Outline(OutlineEntry::Outline(outline)) => Some(CollapsedEntry::Outline( + outline.buffer_id, + outline.excerpt_id, + outline.outline.range.clone(), + )), + PanelEntry::Search(_) => None, + }) + .collect::>(); + + if !entries_to_remove.is_empty() { + let to_remove: HashSet<_> = entries_to_remove.into_iter().collect(); + self.collapsed_entries.retain(|e| !to_remove.contains(e)); + } + active_editor.update(cx, |editor, cx| { buffers_to_unfold.retain(|buffer_id| editor.is_buffer_folded(*buffer_id, cx)); }); + if buffers_to_unfold.is_empty() { self.update_cached_entries(None, window, cx); } else { @@ -1730,7 +1727,12 @@ impl OutlinePanel { PanelEntry::Outline(OutlineEntry::Excerpt(excerpt)) => { Some(CollapsedEntry::Excerpt(excerpt.buffer_id, excerpt.id)) } - PanelEntry::Search(_) | PanelEntry::Outline(..) => None, + PanelEntry::Outline(OutlineEntry::Outline(outline)) => Some(CollapsedEntry::Outline( + outline.buffer_id, + outline.excerpt_id, + outline.outline.range.clone(), + )), + PanelEntry::Search(_) => None, }) .collect::>(); self.collapsed_entries.extend(new_entries); From e523c5244a2a16f07c936de3b751adc1be29722a Mon Sep 17 00:00:00 2001 From: zeld-a Date: Tue, 28 Oct 2025 01:40:00 -0400 Subject: [PATCH 02/18] fixed formatting issues --- crates/outline_panel/src/outline_panel.rs | 28 ++++++++++++++--------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/crates/outline_panel/src/outline_panel.rs b/crates/outline_panel/src/outline_panel.rs index 2388385aa26027..d8c95768d73c0f 100644 --- a/crates/outline_panel/src/outline_panel.rs +++ b/crates/outline_panel/src/outline_panel.rs @@ -1660,15 +1660,19 @@ impl OutlinePanel { worktree_id, entries, .. - }) => entries.last().map(|last| CollapsedEntry::Dir(*worktree_id, last.id)), + }) => entries + .last() + .map(|last| CollapsedEntry::Dir(*worktree_id, last.id)), PanelEntry::Outline(OutlineEntry::Excerpt(excerpt)) => { Some(CollapsedEntry::Excerpt(excerpt.buffer_id, excerpt.id)) } - PanelEntry::Outline(OutlineEntry::Outline(outline)) => Some(CollapsedEntry::Outline( - outline.buffer_id, - outline.excerpt_id, - outline.outline.range.clone(), - )), + PanelEntry::Outline(OutlineEntry::Outline(outline)) => { + Some(CollapsedEntry::Outline( + outline.buffer_id, + outline.excerpt_id, + outline.outline.range.clone(), + )) + } PanelEntry::Search(_) => None, }) .collect::>(); @@ -1727,11 +1731,13 @@ impl OutlinePanel { PanelEntry::Outline(OutlineEntry::Excerpt(excerpt)) => { Some(CollapsedEntry::Excerpt(excerpt.buffer_id, excerpt.id)) } - PanelEntry::Outline(OutlineEntry::Outline(outline)) => Some(CollapsedEntry::Outline( - outline.buffer_id, - outline.excerpt_id, - outline.outline.range.clone(), - )), + PanelEntry::Outline(OutlineEntry::Outline(outline)) => { + Some(CollapsedEntry::Outline( + outline.buffer_id, + outline.excerpt_id, + outline.outline.range.clone(), + )) + } PanelEntry::Search(_) => None, }) .collect::>(); From 13754ec7bb208607e397151dae20c8ab68f8274c Mon Sep 17 00:00:00 2001 From: zeld-a Date: Tue, 28 Oct 2025 01:42:24 -0400 Subject: [PATCH 03/18] fixed a brace placement --- crates/outline_panel/src/outline_panel.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/outline_panel/src/outline_panel.rs b/crates/outline_panel/src/outline_panel.rs index d8c95768d73c0f..1b904b1a64bd17 100644 --- a/crates/outline_panel/src/outline_panel.rs +++ b/crates/outline_panel/src/outline_panel.rs @@ -1737,7 +1737,7 @@ impl OutlinePanel { outline.excerpt_id, outline.outline.range.clone(), )) - } + } PanelEntry::Search(_) => None, }) .collect::>(); From 14df2445d1e9a383bc0fcc93f991f94063433a8a Mon Sep 17 00:00:00 2001 From: zeld-a Date: Tue, 28 Oct 2025 11:35:37 -0400 Subject: [PATCH 04/18] collect directly into a hashset --- crates/outline_panel/src/outline_panel.rs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/crates/outline_panel/src/outline_panel.rs b/crates/outline_panel/src/outline_panel.rs index 1b904b1a64bd17..aea6cc6e492bda 100644 --- a/crates/outline_panel/src/outline_panel.rs +++ b/crates/outline_panel/src/outline_panel.rs @@ -1637,7 +1637,7 @@ impl OutlinePanel { }; let mut buffers_to_unfold = HashSet::default(); - let entries_to_remove = self + let entries_to_remove: HashSet<_> = self .cached_entries .iter() .filter_map(|cached_entry| match &cached_entry.entry { @@ -1675,12 +1675,9 @@ impl OutlinePanel { } PanelEntry::Search(_) => None, }) - .collect::>(); + .collect(); - if !entries_to_remove.is_empty() { - let to_remove: HashSet<_> = entries_to_remove.into_iter().collect(); - self.collapsed_entries.retain(|e| !to_remove.contains(e)); - } + self.collapsed_entries.retain(|e| !entries_to_remove.contains(e)); active_editor.update(cx, |editor, cx| { buffers_to_unfold.retain(|buffer_id| editor.is_buffer_folded(*buffer_id, cx)); From 4bd3cdb1e5cb2133334b04c5758c72cf93f13984 Mon Sep 17 00:00:00 2001 From: zeld-a Date: Tue, 28 Oct 2025 11:40:54 -0400 Subject: [PATCH 05/18] formatting --- crates/outline_panel/src/outline_panel.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/outline_panel/src/outline_panel.rs b/crates/outline_panel/src/outline_panel.rs index aea6cc6e492bda..27dc78889b7c13 100644 --- a/crates/outline_panel/src/outline_panel.rs +++ b/crates/outline_panel/src/outline_panel.rs @@ -1676,8 +1676,8 @@ impl OutlinePanel { PanelEntry::Search(_) => None, }) .collect(); - - self.collapsed_entries.retain(|e| !entries_to_remove.contains(e)); + self.collapsed_entries + .retain(|e| !entries_to_remove.contains(e)); active_editor.update(cx, |editor, cx| { buffers_to_unfold.retain(|buffer_id| editor.is_buffer_folded(*buffer_id, cx)); From 92552cb9f7da0fde7904694d455afd78dd74d85f Mon Sep 17 00:00:00 2001 From: zeld-a Date: Tue, 28 Oct 2025 12:28:27 -0400 Subject: [PATCH 06/18] applied a direct extend in extend_all_entries to avoid extra memory alloc --- crates/outline_panel/src/outline_panel.rs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/crates/outline_panel/src/outline_panel.rs b/crates/outline_panel/src/outline_panel.rs index 27dc78889b7c13..4c998ad7413bed 100644 --- a/crates/outline_panel/src/outline_panel.rs +++ b/crates/outline_panel/src/outline_panel.rs @@ -1701,10 +1701,8 @@ impl OutlinePanel { return; }; let mut buffers_to_fold = HashSet::default(); - let new_entries = self - .cached_entries - .iter() - .flat_map(|cached_entry| match &cached_entry.entry { + self.collapsed_entries.extend( + self.cached_entries.iter().filter_map(|cached_entry| match &cached_entry.entry { PanelEntry::Fs(FsEntry::Directory(FsEntryDirectory { worktree_id, entry, .. })) => Some(CollapsedEntry::Dir(*worktree_id, entry.id)), @@ -1736,9 +1734,8 @@ impl OutlinePanel { )) } PanelEntry::Search(_) => None, - }) - .collect::>(); - self.collapsed_entries.extend(new_entries); + }), + ); active_editor.update(cx, |editor, cx| { buffers_to_fold.retain(|buffer_id| !editor.is_buffer_folded(*buffer_id, cx)); From ac5f6c09d8bd7088564a67393856c5afc7ec53a7 Mon Sep 17 00:00:00 2001 From: zeld-a Date: Tue, 28 Oct 2025 13:08:59 -0400 Subject: [PATCH 07/18] applied a direct retain in collapse_all_entries to avoid extra memory alloc --- crates/outline_panel/src/outline_panel.rs | 96 +++++++++++++---------- 1 file changed, 56 insertions(+), 40 deletions(-) diff --git a/crates/outline_panel/src/outline_panel.rs b/crates/outline_panel/src/outline_panel.rs index 4c998ad7413bed..bc8222de4162e0 100644 --- a/crates/outline_panel/src/outline_panel.rs +++ b/crates/outline_panel/src/outline_panel.rs @@ -1637,47 +1637,63 @@ impl OutlinePanel { }; let mut buffers_to_unfold = HashSet::default(); - let entries_to_remove: HashSet<_> = self - .cached_entries - .iter() - .filter_map(|cached_entry| match &cached_entry.entry { - PanelEntry::Fs(FsEntry::Directory(FsEntryDirectory { - worktree_id, entry, .. - })) => Some(CollapsedEntry::Dir(*worktree_id, entry.id)), - PanelEntry::Fs(FsEntry::File(FsEntryFile { - worktree_id, - buffer_id, - .. - })) => { - buffers_to_unfold.insert(*buffer_id); - Some(CollapsedEntry::File(*worktree_id, *buffer_id)) - } - PanelEntry::Fs(FsEntry::ExternalFile(external_file)) => { - buffers_to_unfold.insert(external_file.buffer_id); - Some(CollapsedEntry::ExternalFile(external_file.buffer_id)) - } - PanelEntry::FoldedDirs(FoldedDirsEntry { - worktree_id, - entries, - .. - }) => entries - .last() - .map(|last| CollapsedEntry::Dir(*worktree_id, last.id)), - PanelEntry::Outline(OutlineEntry::Excerpt(excerpt)) => { - Some(CollapsedEntry::Excerpt(excerpt.buffer_id, excerpt.id)) - } - PanelEntry::Outline(OutlineEntry::Outline(outline)) => { - Some(CollapsedEntry::Outline( - outline.buffer_id, - outline.excerpt_id, - outline.outline.range.clone(), - )) + self.collapsed_entries.retain(|collapsed_entry| { + let matched = self.cached_entries.iter().any(|cached_entry| { + match (&cached_entry.entry, collapsed_entry) { + ( + PanelEntry::Fs(FsEntry::Directory(FsEntryDirectory { worktree_id, entry: fs_entry, .. })), + CollapsedEntry::Dir(wid, id), + ) => wid == worktree_id && id == &fs_entry.id, + + ( + PanelEntry::Fs(FsEntry::File(FsEntryFile { worktree_id, buffer_id, .. })), + CollapsedEntry::File(wid, bid), + ) => { + if wid == worktree_id && bid == buffer_id { + buffers_to_unfold.insert(*buffer_id); + true + } else { + false + } + } + + ( + PanelEntry::Fs(FsEntry::ExternalFile(external)), + CollapsedEntry::ExternalFile(bid), + ) => { + if bid == &external.buffer_id { + buffers_to_unfold.insert(external.buffer_id); + true + } else { + false + } + } + + ( + PanelEntry::FoldedDirs(FoldedDirsEntry { worktree_id, entries, .. }), + CollapsedEntry::Dir(wid, id), + ) => entries + .last() + .map(|last| wid == worktree_id && id == &last.id) + .unwrap_or(false), + + ( + PanelEntry::Outline(OutlineEntry::Excerpt(excerpt)), + CollapsedEntry::Excerpt(bid, id), + ) => bid == &excerpt.buffer_id && id == &excerpt.id, + + ( + PanelEntry::Outline(OutlineEntry::Outline(outline)), + CollapsedEntry::Outline(bid, ex_id, range), + ) => bid == &outline.buffer_id + && ex_id == &outline.excerpt_id + && range == &outline.outline.range, + + _ => false, } - PanelEntry::Search(_) => None, - }) - .collect(); - self.collapsed_entries - .retain(|e| !entries_to_remove.contains(e)); + }); + !matched + }); active_editor.update(cx, |editor, cx| { buffers_to_unfold.retain(|buffer_id| editor.is_buffer_folded(*buffer_id, cx)); From b7ac75cce9a129a0a00112c38a5ec29b9d7c0b73 Mon Sep 17 00:00:00 2001 From: zeld-a Date: Tue, 28 Oct 2025 13:18:19 -0400 Subject: [PATCH 08/18] formmating changes --- crates/outline_panel/src/outline_panel.rs | 100 +++++++++++++--------- 1 file changed, 58 insertions(+), 42 deletions(-) diff --git a/crates/outline_panel/src/outline_panel.rs b/crates/outline_panel/src/outline_panel.rs index bc8222de4162e0..d211d831ec231b 100644 --- a/crates/outline_panel/src/outline_panel.rs +++ b/crates/outline_panel/src/outline_panel.rs @@ -1641,12 +1641,20 @@ impl OutlinePanel { let matched = self.cached_entries.iter().any(|cached_entry| { match (&cached_entry.entry, collapsed_entry) { ( - PanelEntry::Fs(FsEntry::Directory(FsEntryDirectory { worktree_id, entry: fs_entry, .. })), + PanelEntry::Fs(FsEntry::Directory(FsEntryDirectory { + worktree_id, + entry: fs_entry, + .. + })), CollapsedEntry::Dir(wid, id), ) => wid == worktree_id && id == &fs_entry.id, ( - PanelEntry::Fs(FsEntry::File(FsEntryFile { worktree_id, buffer_id, .. })), + PanelEntry::Fs(FsEntry::File(FsEntryFile { + worktree_id, + buffer_id, + .. + })), CollapsedEntry::File(wid, bid), ) => { if wid == worktree_id && bid == buffer_id { @@ -1670,7 +1678,11 @@ impl OutlinePanel { } ( - PanelEntry::FoldedDirs(FoldedDirsEntry { worktree_id, entries, .. }), + PanelEntry::FoldedDirs(FoldedDirsEntry { + worktree_id, + entries, + .. + }), CollapsedEntry::Dir(wid, id), ) => entries .last() @@ -1685,10 +1697,11 @@ impl OutlinePanel { ( PanelEntry::Outline(OutlineEntry::Outline(outline)), CollapsedEntry::Outline(bid, ex_id, range), - ) => bid == &outline.buffer_id - && ex_id == &outline.excerpt_id - && range == &outline.outline.range, - + ) => { + bid == &outline.buffer_id + && ex_id == &outline.excerpt_id + && range == &outline.outline.range + } _ => false, } }); @@ -1717,41 +1730,44 @@ impl OutlinePanel { return; }; let mut buffers_to_fold = HashSet::default(); - self.collapsed_entries.extend( - self.cached_entries.iter().filter_map(|cached_entry| match &cached_entry.entry { - PanelEntry::Fs(FsEntry::Directory(FsEntryDirectory { - worktree_id, entry, .. - })) => Some(CollapsedEntry::Dir(*worktree_id, entry.id)), - PanelEntry::Fs(FsEntry::File(FsEntryFile { - worktree_id, - buffer_id, - .. - })) => { - buffers_to_fold.insert(*buffer_id); - Some(CollapsedEntry::File(*worktree_id, *buffer_id)) - } - PanelEntry::Fs(FsEntry::ExternalFile(external_file)) => { - buffers_to_fold.insert(external_file.buffer_id); - Some(CollapsedEntry::ExternalFile(external_file.buffer_id)) - } - PanelEntry::FoldedDirs(FoldedDirsEntry { - worktree_id, - entries, - .. - }) => Some(CollapsedEntry::Dir(*worktree_id, entries.last()?.id)), - PanelEntry::Outline(OutlineEntry::Excerpt(excerpt)) => { - Some(CollapsedEntry::Excerpt(excerpt.buffer_id, excerpt.id)) - } - PanelEntry::Outline(OutlineEntry::Outline(outline)) => { - Some(CollapsedEntry::Outline( - outline.buffer_id, - outline.excerpt_id, - outline.outline.range.clone(), - )) - } - PanelEntry::Search(_) => None, - }), - ); + self.collapsed_entries + .extend(self.cached_entries.iter().filter_map( + |cached_entry| match &cached_entry.entry { + PanelEntry::Fs(FsEntry::Directory(FsEntryDirectory { + worktree_id, + entry, + .. + })) => Some(CollapsedEntry::Dir(*worktree_id, entry.id)), + PanelEntry::Fs(FsEntry::File(FsEntryFile { + worktree_id, + buffer_id, + .. + })) => { + buffers_to_fold.insert(*buffer_id); + Some(CollapsedEntry::File(*worktree_id, *buffer_id)) + } + PanelEntry::Fs(FsEntry::ExternalFile(external_file)) => { + buffers_to_fold.insert(external_file.buffer_id); + Some(CollapsedEntry::ExternalFile(external_file.buffer_id)) + } + PanelEntry::FoldedDirs(FoldedDirsEntry { + worktree_id, + entries, + .. + }) => Some(CollapsedEntry::Dir(*worktree_id, entries.last()?.id)), + PanelEntry::Outline(OutlineEntry::Excerpt(excerpt)) => { + Some(CollapsedEntry::Excerpt(excerpt.buffer_id, excerpt.id)) + } + PanelEntry::Outline(OutlineEntry::Outline(outline)) => { + Some(CollapsedEntry::Outline( + outline.buffer_id, + outline.excerpt_id, + outline.outline.range.clone(), + )) + } + PanelEntry::Search(_) => None, + }, + )); active_editor.update(cx, |editor, cx| { buffers_to_fold.retain(|buffer_id| !editor.is_buffer_folded(*buffer_id, cx)); From 3796ecfc3307c2445530f4265d9846aa0724d886 Mon Sep 17 00:00:00 2001 From: zeld-a Date: Tue, 28 Oct 2025 17:25:29 -0400 Subject: [PATCH 09/18] added expand_colllapse_all test - currently fails --- crates/outline_panel/src/outline_panel.rs | 227 ++++++++++++++++++++++ 1 file changed, 227 insertions(+) diff --git a/crates/outline_panel/src/outline_panel.rs b/crates/outline_panel/src/outline_panel.rs index d211d831ec231b..975eb034cbe9aa 100644 --- a/crates/outline_panel/src/outline_panel.rs +++ b/crates/outline_panel/src/outline_panel.rs @@ -7544,4 +7544,231 @@ outline: fn main()" ); }); } + + #[gpui::test] + async fn test_outline_expand_collapse_all(cx: &mut TestAppContext) { + init_test(cx); + + let fs = FakeFs::new(cx.background_executor.clone()); + fs.insert_tree( + "/test", + json!({ + "src": { + "lib.rs": indoc!(" + mod outer { + pub struct OuterStruct { + field: String, + } + impl OuterStruct { + pub fn new() -> Self { + Self { field: String::new() } + } + pub fn method(&self) { + println!(\"{}\", self.field); + } + } + mod inner { + pub fn inner_function() { + let x = 42; + println!(\"{}\", x); + } + pub struct InnerStruct { + value: i32, + } + } + } + fn main() { + let s = outer::OuterStruct::new(); + s.method(); + } + "), + } + }), + ) + .await; + + let project = Project::test(fs.clone(), ["/test".as_ref()], cx).await; + project.read_with(cx, |project, _| { + project.languages().add(Arc::new( + rust_lang() + .with_outline_query( + r#" + (struct_item + (visibility_modifier)? @context + "struct" @context + name: (_) @name) @item + (impl_item + "impl" @context + trait: (_)? @context + "for"? @context + type: (_) @context + body: (_)) @item + (function_item + (visibility_modifier)? @context + "fn" @context + name: (_) @name + parameters: (_) @context) @item + (mod_item + (visibility_modifier)? @context + "mod" @context + name: (_) @name) @item + (enum_item + (visibility_modifier)? @context + "enum" @context + name: (_) @name) @item + (field_declaration + (visibility_modifier)? @context + name: (_) @name + ":" @context + type: (_) @context) @item + "#, + ) + .unwrap(), + )) + }); + let workspace = add_outline_panel(&project, cx).await; + let cx = &mut VisualTestContext::from_window(*workspace, cx); + let outline_panel = outline_panel(&workspace, cx); + + outline_panel.update_in(cx, |outline_panel, window, cx| { + outline_panel.set_active(true, window, cx) + }); + + workspace + .update(cx, |workspace, window, cx| { + workspace.open_abs_path( + PathBuf::from("/test/src/lib.rs"), + OpenOptions { + visible: Some(OpenVisible::All), + ..Default::default() + }, + window, + cx, + ) + }) + .unwrap() + .await + .unwrap(); + + cx.executor() + .advance_clock(UPDATE_DEBOUNCE + Duration::from_millis(500)); + cx.run_until_parked(); + + // Force another update cycle to ensure outlines are fetched + outline_panel.update_in(cx, |panel, window, cx| { + panel.update_non_fs_items(window, cx); + panel.update_cached_entries(Some(UPDATE_DEBOUNCE), window, cx); + }); + cx.executor() + .advance_clock(UPDATE_DEBOUNCE + Duration::from_millis(500)); + cx.run_until_parked(); + + outline_panel.update(cx, |outline_panel, cx| { + assert_eq!( + display_entries( + &project, + &snapshot(outline_panel, cx), + &outline_panel.cached_entries, + outline_panel.selected_entry(), + cx, + ), + indoc!( + " +outline: mod outer <==== selected + outline: pub struct OuterStruct + outline: field: String + outline: impl OuterStruct + outline: pub fn new() + outline: pub fn method(&self) + outline: mod inner + outline: pub fn inner_function() + outline: pub struct InnerStruct + outline: value: i32 +outline: fn main()" + ) + ); + }); + + let _parent_outline = outline_panel + .read_with(cx, |panel, _cx| { + panel + .cached_entries + .iter() + .find_map(|entry| match &entry.entry { + PanelEntry::Outline(OutlineEntry::Outline(outline)) + if panel + .outline_children_cache + .get(&outline.buffer_id) + .and_then(|children_map| { + let key = + (outline.outline.range.clone(), outline.outline.depth); + children_map.get(&key) + }) + .copied() + .unwrap_or(false) => + { + Some(entry.entry.clone()) + } + _ => None, + }) + }) + .expect("Should find an outline with children"); + + // Collapse all entries + outline_panel.update_in(cx, |panel, window, cx| { + panel.collapse_all_entries(&CollapseAllEntries, window, cx); + }); + + let expected_collapsed_output = indoc!( + " + outline: mod outer <==== selected + outline: fn main()" + ); + + outline_panel.update(cx, |panel, cx| { + assert_eq!{ + display_entries( + &project, + &snapshot(panel, cx), + &panel.cached_entries, + panel.selected_entry(), + cx, + ), + expected_collapsed_output + }; + }); + + // Expand all entries + outline_panel.update_in(cx, |panel, window, cx| { + panel.expand_all_entries(&ExpandAllEntries, window, cx); + }); + + let expected_expanded_output = indoc!( + " + outline: mod outer <==== selected + outline: pub struct OuterStruct + outline: field: String + outline: impl OuterStruct + outline: pub fn new() + outline: pub fn method(&self) + outline: mod inner + outline: pub fn inner_function() + outline: pub struct InnerStruct + outline: value: i32 + outline: fn main()" + ); + + outline_panel.update(cx, |panel, cx| { + assert_eq! { + display_entries( + &project, + &snapshot(panel, cx), + &panel.cached_entries, + panel.selected_entry(), + cx, + ), + expected_expanded_output + }; + }); + } } From 9aef7d8fbce2d0113c57dd9f1d704366222ff79e Mon Sep 17 00:00:00 2001 From: zeld-a Date: Tue, 28 Oct 2025 18:24:53 -0400 Subject: [PATCH 10/18] fixed collapse test --- crates/outline_panel/src/outline_panel.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/crates/outline_panel/src/outline_panel.rs b/crates/outline_panel/src/outline_panel.rs index 975eb034cbe9aa..b1a72fa1f59616 100644 --- a/crates/outline_panel/src/outline_panel.rs +++ b/crates/outline_panel/src/outline_panel.rs @@ -7718,6 +7718,8 @@ outline: fn main()" outline_panel.update_in(cx, |panel, window, cx| { panel.collapse_all_entries(&CollapseAllEntries, window, cx); }); + cx.executor().advance_clock(UPDATE_DEBOUNCE + Duration::from_millis(100)); + cx.run_until_parked(); let expected_collapsed_output = indoc!( " @@ -7742,6 +7744,8 @@ outline: fn main()" outline_panel.update_in(cx, |panel, window, cx| { panel.expand_all_entries(&ExpandAllEntries, window, cx); }); + cx.executor().advance_clock(UPDATE_DEBOUNCE + Duration::from_millis(100)); + cx.run_until_parked(); let expected_expanded_output = indoc!( " From a3b002bdc90b17bccea95988315a7fa165c5854c Mon Sep 17 00:00:00 2001 From: zeld-a Date: Tue, 28 Oct 2025 18:32:57 -0400 Subject: [PATCH 11/18] test assumes only visible entries are to be expanded --- crates/outline_panel/src/outline_panel.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/crates/outline_panel/src/outline_panel.rs b/crates/outline_panel/src/outline_panel.rs index b1a72fa1f59616..cbfe100e8cbf85 100644 --- a/crates/outline_panel/src/outline_panel.rs +++ b/crates/outline_panel/src/outline_panel.rs @@ -7751,14 +7751,8 @@ outline: fn main()" " outline: mod outer <==== selected outline: pub struct OuterStruct - outline: field: String outline: impl OuterStruct - outline: pub fn new() - outline: pub fn method(&self) outline: mod inner - outline: pub fn inner_function() - outline: pub struct InnerStruct - outline: value: i32 outline: fn main()" ); From c0beddd46c258662d2ca2e71192db32bfe2057af Mon Sep 17 00:00:00 2001 From: zeld-a Date: Tue, 28 Oct 2025 18:40:05 -0400 Subject: [PATCH 12/18] formatting AGAIN --- crates/outline_panel/src/outline_panel.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/crates/outline_panel/src/outline_panel.rs b/crates/outline_panel/src/outline_panel.rs index cbfe100e8cbf85..4befef9359d6f8 100644 --- a/crates/outline_panel/src/outline_panel.rs +++ b/crates/outline_panel/src/outline_panel.rs @@ -7718,7 +7718,8 @@ outline: fn main()" outline_panel.update_in(cx, |panel, window, cx| { panel.collapse_all_entries(&CollapseAllEntries, window, cx); }); - cx.executor().advance_clock(UPDATE_DEBOUNCE + Duration::from_millis(100)); + cx.executor() + .advance_clock(UPDATE_DEBOUNCE + Duration::from_millis(100)); cx.run_until_parked(); let expected_collapsed_output = indoc!( @@ -7728,7 +7729,7 @@ outline: fn main()" ); outline_panel.update(cx, |panel, cx| { - assert_eq!{ + assert_eq! { display_entries( &project, &snapshot(panel, cx), @@ -7744,7 +7745,8 @@ outline: fn main()" outline_panel.update_in(cx, |panel, window, cx| { panel.expand_all_entries(&ExpandAllEntries, window, cx); }); - cx.executor().advance_clock(UPDATE_DEBOUNCE + Duration::from_millis(100)); + cx.executor() + .advance_clock(UPDATE_DEBOUNCE + Duration::from_millis(100)); cx.run_until_parked(); let expected_expanded_output = indoc!( From 92755cc94680a2a0271c94f076539a91d9f841dc Mon Sep 17 00:00:00 2001 From: zeld-a Date: Wed, 29 Oct 2025 17:09:06 -0400 Subject: [PATCH 13/18] expand now expands nested entries and test assumes the same --- crates/outline_panel/src/outline_panel.rs | 86 +++++------------------ 1 file changed, 16 insertions(+), 70 deletions(-) diff --git a/crates/outline_panel/src/outline_panel.rs b/crates/outline_panel/src/outline_panel.rs index 4befef9359d6f8..7937fd7802d9e0 100644 --- a/crates/outline_panel/src/outline_panel.rs +++ b/crates/outline_panel/src/outline_panel.rs @@ -1636,77 +1636,17 @@ impl OutlinePanel { return; }; - let mut buffers_to_unfold = HashSet::default(); - self.collapsed_entries.retain(|collapsed_entry| { - let matched = self.cached_entries.iter().any(|cached_entry| { - match (&cached_entry.entry, collapsed_entry) { - ( - PanelEntry::Fs(FsEntry::Directory(FsEntryDirectory { - worktree_id, - entry: fs_entry, - .. - })), - CollapsedEntry::Dir(wid, id), - ) => wid == worktree_id && id == &fs_entry.id, - - ( - PanelEntry::Fs(FsEntry::File(FsEntryFile { - worktree_id, - buffer_id, - .. - })), - CollapsedEntry::File(wid, bid), - ) => { - if wid == worktree_id && bid == buffer_id { - buffers_to_unfold.insert(*buffer_id); - true - } else { - false - } - } - - ( - PanelEntry::Fs(FsEntry::ExternalFile(external)), - CollapsedEntry::ExternalFile(bid), - ) => { - if bid == &external.buffer_id { - buffers_to_unfold.insert(external.buffer_id); - true - } else { - false - } - } + self.collapsed_entries.clear(); - ( - PanelEntry::FoldedDirs(FoldedDirsEntry { - worktree_id, - entries, - .. - }), - CollapsedEntry::Dir(wid, id), - ) => entries - .last() - .map(|last| wid == worktree_id && id == &last.id) - .unwrap_or(false), - - ( - PanelEntry::Outline(OutlineEntry::Excerpt(excerpt)), - CollapsedEntry::Excerpt(bid, id), - ) => bid == &excerpt.buffer_id && id == &excerpt.id, - - ( - PanelEntry::Outline(OutlineEntry::Outline(outline)), - CollapsedEntry::Outline(bid, ex_id, range), - ) => { - bid == &outline.buffer_id - && ex_id == &outline.excerpt_id - && range == &outline.outline.range - } - _ => false, - } - }); - !matched - }); + // You may still need to unfold buffers as before: + let mut buffers_to_unfold = HashSet::default(); + for cached_entry in &self.cached_entries { + if let PanelEntry::Fs(FsEntry::File(FsEntryFile { buffer_id, .. })) + | PanelEntry::Fs(FsEntry::ExternalFile(FsEntryExternalFile { buffer_id, .. })) = &cached_entry.entry + { + buffers_to_unfold.insert(*buffer_id); + } + } active_editor.update(cx, |editor, cx| { buffers_to_unfold.retain(|buffer_id| editor.is_buffer_folded(*buffer_id, cx)); @@ -7753,8 +7693,14 @@ outline: fn main()" " outline: mod outer <==== selected outline: pub struct OuterStruct + outline: field: String outline: impl OuterStruct + outline: pub fn new() + outline: pub fn method(&self) outline: mod inner + outline: pub fn inner_function() + outline: pub struct InnerStruct + outline: value: i32 outline: fn main()" ); From f38c5cf4512d5f941ebf954d5409dd3f7ce65164 Mon Sep 17 00:00:00 2001 From: zeld-a Date: Wed, 29 Oct 2025 17:12:39 -0400 Subject: [PATCH 14/18] formatting --- crates/outline_panel/src/outline_panel.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/outline_panel/src/outline_panel.rs b/crates/outline_panel/src/outline_panel.rs index 7937fd7802d9e0..fa51b8f60cf5d6 100644 --- a/crates/outline_panel/src/outline_panel.rs +++ b/crates/outline_panel/src/outline_panel.rs @@ -1642,7 +1642,9 @@ impl OutlinePanel { let mut buffers_to_unfold = HashSet::default(); for cached_entry in &self.cached_entries { if let PanelEntry::Fs(FsEntry::File(FsEntryFile { buffer_id, .. })) - | PanelEntry::Fs(FsEntry::ExternalFile(FsEntryExternalFile { buffer_id, .. })) = &cached_entry.entry + | PanelEntry::Fs(FsEntry::ExternalFile(FsEntryExternalFile { + buffer_id, .. + })) = &cached_entry.entry { buffers_to_unfold.insert(*buffer_id); } From 008f8b7c0de89732a513615ee3f3ca3e0be6293f Mon Sep 17 00:00:00 2001 From: zeld-a Date: Thu, 30 Oct 2025 06:49:16 -0400 Subject: [PATCH 15/18] expand now works in multibuffer --- crates/outline_panel/src/outline_panel.rs | 109 ++++++++++++++++------ 1 file changed, 81 insertions(+), 28 deletions(-) diff --git a/crates/outline_panel/src/outline_panel.rs b/crates/outline_panel/src/outline_panel.rs index fa51b8f60cf5d6..02f48649c4c63c 100644 --- a/crates/outline_panel/src/outline_panel.rs +++ b/crates/outline_panel/src/outline_panel.rs @@ -1627,40 +1627,93 @@ impl OutlinePanel { } pub fn expand_all_entries( - &mut self, - _: &ExpandAllEntries, - window: &mut Window, - cx: &mut Context, - ) { - let Some(active_editor) = self.active_editor() else { - return; - }; + &mut self, + _: &ExpandAllEntries, + window: &mut Window, + cx: &mut Context, + ) { + let Some(active_editor) = self.active_editor() else { + return; + }; - self.collapsed_entries.clear(); + let mut to_uncollapse: HashSet = HashSet::default(); + let mut buffers_to_unfold: HashSet = HashSet::default(); - // You may still need to unfold buffers as before: - let mut buffers_to_unfold = HashSet::default(); - for cached_entry in &self.cached_entries { - if let PanelEntry::Fs(FsEntry::File(FsEntryFile { buffer_id, .. })) - | PanelEntry::Fs(FsEntry::ExternalFile(FsEntryExternalFile { - buffer_id, .. - })) = &cached_entry.entry - { - buffers_to_unfold.insert(*buffer_id); + for fs_entry in &self.fs_entries { + match fs_entry { + FsEntry::File(FsEntryFile { + worktree_id, + buffer_id, + .. + }) => { + to_uncollapse.insert(CollapsedEntry::File(*worktree_id, *buffer_id)); + buffers_to_unfold.insert(*buffer_id); + } + FsEntry::ExternalFile(FsEntryExternalFile { buffer_id, .. }) => { + to_uncollapse.insert(CollapsedEntry::ExternalFile(*buffer_id)); + buffers_to_unfold.insert(*buffer_id); + } + FsEntry::Directory(FsEntryDirectory { + worktree_id, + entry, + .. + }) => { + to_uncollapse.insert(CollapsedEntry::Dir(*worktree_id, entry.id)); + } + } } - } - active_editor.update(cx, |editor, cx| { - buffers_to_unfold.retain(|buffer_id| editor.is_buffer_folded(*buffer_id, cx)); - }); + for (&buffer_id, excerpts) in &self.excerpts { + for (&excerpt_id, excerpt) in excerpts { + match &excerpt.outlines { + ExcerptOutlines::Outlines(outlines) => { + for outline in outlines { + to_uncollapse.insert(CollapsedEntry::Outline( + buffer_id, + excerpt_id, + outline.range.clone(), + )); + } + } + ExcerptOutlines::Invalidated(outlines) => { + for outline in outlines { + to_uncollapse.insert(CollapsedEntry::Outline( + buffer_id, + excerpt_id, + outline.range.clone(), + )); + } + } + ExcerptOutlines::NotFetched => { + } + } + to_uncollapse.insert(CollapsedEntry::Excerpt(buffer_id, excerpt_id)); + } + } - if buffers_to_unfold.is_empty() { - self.update_cached_entries(None, window, cx); - } else { - self.toggle_buffers_fold(buffers_to_unfold, false, window, cx) - .detach(); + for cached in &self.cached_entries { + if let PanelEntry::FoldedDirs(FoldedDirsEntry { worktree_id, entries, .. }) = + &cached.entry + { + if let Some(last) = entries.last() { + to_uncollapse.insert(CollapsedEntry::Dir(*worktree_id, last.id)); + } + } + } + + self.collapsed_entries.retain(|entry| !to_uncollapse.contains(entry)); + + active_editor.update(cx, |editor, cx| { + buffers_to_unfold.retain(|buffer_id| editor.is_buffer_folded(*buffer_id, cx)); + }); + + if buffers_to_unfold.is_empty() { + self.update_cached_entries(None, window, cx); + } else { + self.toggle_buffers_fold(buffers_to_unfold, false, window, cx) + .detach(); + } } - } pub fn collapse_all_entries( &mut self, From 67848064089b0b95d361b86e2c652d0613ed0488 Mon Sep 17 00:00:00 2001 From: zeld-a Date: Thu, 30 Oct 2025 07:04:09 -0400 Subject: [PATCH 16/18] corrected formatting --- crates/outline_panel/src/outline_panel.rs | 141 +++++++++++----------- 1 file changed, 71 insertions(+), 70 deletions(-) diff --git a/crates/outline_panel/src/outline_panel.rs b/crates/outline_panel/src/outline_panel.rs index 02f48649c4c63c..58892240c83433 100644 --- a/crates/outline_panel/src/outline_panel.rs +++ b/crates/outline_panel/src/outline_panel.rs @@ -1627,93 +1627,94 @@ impl OutlinePanel { } pub fn expand_all_entries( - &mut self, - _: &ExpandAllEntries, - window: &mut Window, - cx: &mut Context, - ) { - let Some(active_editor) = self.active_editor() else { - return; - }; + &mut self, + _: &ExpandAllEntries, + window: &mut Window, + cx: &mut Context, + ) { + let Some(active_editor) = self.active_editor() else { + return; + }; - let mut to_uncollapse: HashSet = HashSet::default(); - let mut buffers_to_unfold: HashSet = HashSet::default(); + let mut to_uncollapse: HashSet = HashSet::default(); + let mut buffers_to_unfold: HashSet = HashSet::default(); - for fs_entry in &self.fs_entries { - match fs_entry { - FsEntry::File(FsEntryFile { - worktree_id, - buffer_id, - .. - }) => { - to_uncollapse.insert(CollapsedEntry::File(*worktree_id, *buffer_id)); - buffers_to_unfold.insert(*buffer_id); - } - FsEntry::ExternalFile(FsEntryExternalFile { buffer_id, .. }) => { - to_uncollapse.insert(CollapsedEntry::ExternalFile(*buffer_id)); - buffers_to_unfold.insert(*buffer_id); - } - FsEntry::Directory(FsEntryDirectory { - worktree_id, - entry, - .. - }) => { - to_uncollapse.insert(CollapsedEntry::Dir(*worktree_id, entry.id)); - } + for fs_entry in &self.fs_entries { + match fs_entry { + FsEntry::File(FsEntryFile { + worktree_id, + buffer_id, + .. + }) => { + to_uncollapse.insert(CollapsedEntry::File(*worktree_id, *buffer_id)); + buffers_to_unfold.insert(*buffer_id); + } + FsEntry::ExternalFile(FsEntryExternalFile { buffer_id, .. }) => { + to_uncollapse.insert(CollapsedEntry::ExternalFile(*buffer_id)); + buffers_to_unfold.insert(*buffer_id); + } + FsEntry::Directory(FsEntryDirectory { + worktree_id, entry, .. + }) => { + to_uncollapse.insert(CollapsedEntry::Dir(*worktree_id, entry.id)); } } + } - for (&buffer_id, excerpts) in &self.excerpts { - for (&excerpt_id, excerpt) in excerpts { - match &excerpt.outlines { - ExcerptOutlines::Outlines(outlines) => { - for outline in outlines { - to_uncollapse.insert(CollapsedEntry::Outline( - buffer_id, - excerpt_id, - outline.range.clone(), - )); - } - } - ExcerptOutlines::Invalidated(outlines) => { - for outline in outlines { - to_uncollapse.insert(CollapsedEntry::Outline( - buffer_id, - excerpt_id, - outline.range.clone(), - )); - } + for (&buffer_id, excerpts) in &self.excerpts { + for (&excerpt_id, excerpt) in excerpts { + match &excerpt.outlines { + ExcerptOutlines::Outlines(outlines) => { + for outline in outlines { + to_uncollapse.insert(CollapsedEntry::Outline( + buffer_id, + excerpt_id, + outline.range.clone(), + )); } - ExcerptOutlines::NotFetched => { + } + ExcerptOutlines::Invalidated(outlines) => { + for outline in outlines { + to_uncollapse.insert(CollapsedEntry::Outline( + buffer_id, + excerpt_id, + outline.range.clone(), + )); } } - to_uncollapse.insert(CollapsedEntry::Excerpt(buffer_id, excerpt_id)); + ExcerptOutlines::NotFetched => {} } + to_uncollapse.insert(CollapsedEntry::Excerpt(buffer_id, excerpt_id)); } + } - for cached in &self.cached_entries { - if let PanelEntry::FoldedDirs(FoldedDirsEntry { worktree_id, entries, .. }) = - &cached.entry - { - if let Some(last) = entries.last() { - to_uncollapse.insert(CollapsedEntry::Dir(*worktree_id, last.id)); - } + for cached in &self.cached_entries { + if let PanelEntry::FoldedDirs(FoldedDirsEntry { + worktree_id, + entries, + .. + }) = &cached.entry + { + if let Some(last) = entries.last() { + to_uncollapse.insert(CollapsedEntry::Dir(*worktree_id, last.id)); } } + } - self.collapsed_entries.retain(|entry| !to_uncollapse.contains(entry)); + self.collapsed_entries + .retain(|entry| !to_uncollapse.contains(entry)); - active_editor.update(cx, |editor, cx| { - buffers_to_unfold.retain(|buffer_id| editor.is_buffer_folded(*buffer_id, cx)); - }); + active_editor.update(cx, |editor, cx| { + buffers_to_unfold.retain(|buffer_id| editor.is_buffer_folded(*buffer_id, cx)); + }); - if buffers_to_unfold.is_empty() { - self.update_cached_entries(None, window, cx); - } else { - self.toggle_buffers_fold(buffers_to_unfold, false, window, cx) - .detach(); - } + if buffers_to_unfold.is_empty() { + self.update_cached_entries(None, window, cx); + } else { + self.toggle_buffers_fold(buffers_to_unfold, false, window, cx) + .detach(); } + } pub fn collapse_all_entries( &mut self, From 449285390766b8715edaa64065048c91f9940376 Mon Sep 17 00:00:00 2001 From: zeld-a Date: Thu, 30 Oct 2025 09:07:40 -0400 Subject: [PATCH 17/18] added testing for multibuffer collapse/expand --- crates/outline_panel/src/outline_panel.rs | 56 +++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/crates/outline_panel/src/outline_panel.rs b/crates/outline_panel/src/outline_panel.rs index 58892240c83433..428276505408dc 100644 --- a/crates/outline_panel/src/outline_panel.rs +++ b/crates/outline_panel/src/outline_panel.rs @@ -6622,6 +6622,62 @@ outline: struct OutlineEntryExcerpt search: {{ "something": "static" }} src/ app/(site)/ + components/ + ErrorBoundary.tsx <==== selected + search: static"# + ) + ); + }); + + outline_panel.update_in(cx, |outline_panel, window, cx| { + outline_panel.collapse_all_entries(&CollapseAllEntries, window, cx); + }); + cx.executor() + .advance_clock(UPDATE_DEBOUNCE + Duration::from_millis(100)); + cx.run_until_parked(); + outline_panel.update(cx, |outline_panel, cx| { + assert_eq!( + display_entries( + &project, + &snapshot(outline_panel, cx), + &outline_panel.cached_entries, + outline_panel.selected_entry(), + cx, + ), + format!( + r#"frontend-project/"# + ) + ); + }); + + outline_panel.update_in(cx, |outline_panel, window, cx| { + outline_panel.expand_all_entries(&ExpandAllEntries, window, cx); + }); + cx.executor() + .advance_clock(UPDATE_DEBOUNCE + Duration::from_millis(100)); + cx.run_until_parked(); + outline_panel.update(cx, |outline_panel, cx| { + assert_eq!( + display_entries( + &project, + &snapshot(outline_panel, cx), + &outline_panel.cached_entries, + outline_panel.selected_entry(), + cx, + ), + format!( + r#"frontend-project/ + public/lottie/ + syntax-tree.json + search: {{ "something": "static" }} + src/ + app/(site)/ + (about)/jobs/[slug]/ + page.tsx + search: static + (blog)/post/[slug]/ + page.tsx + search: static components/ ErrorBoundary.tsx <==== selected search: static"# From 2abbddd35f6396a60df9047c2db9af2e3688c613 Mon Sep 17 00:00:00 2001 From: zeld-a Date: Thu, 30 Oct 2025 09:11:26 -0400 Subject: [PATCH 18/18] formatting --- crates/outline_panel/src/outline_panel.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/crates/outline_panel/src/outline_panel.rs b/crates/outline_panel/src/outline_panel.rs index 428276505408dc..f9b1afe34e5ebf 100644 --- a/crates/outline_panel/src/outline_panel.rs +++ b/crates/outline_panel/src/outline_panel.rs @@ -6644,9 +6644,7 @@ outline: struct OutlineEntryExcerpt outline_panel.selected_entry(), cx, ), - format!( - r#"frontend-project/"# - ) + format!(r#"frontend-project/"#) ); });