Revert "Add support of auto folded directories" (#8476)

Antonio Scandurra created

Reverts zed-industries/zed#7674

@ABckh: reverting this as it introduced a significant performance
slowdown, most likely caused by iterating through all the snapshot
entries to determine whether a directory is foldable/unfoldable/omitted.
It would be great if you could open a new PR that reverts this revert
and addresses the performance issues. Thank you!

/cc: @maxbrunsfeld 

Release notes:

- N/A

Change summary

assets/settings/default.json                       |   5 
crates/project_panel/src/project_panel.rs          | 358 +--------------
crates/project_panel/src/project_panel_settings.rs |   6 
3 files changed, 41 insertions(+), 328 deletions(-)

Detailed changes

assets/settings/default.json 🔗

@@ -193,10 +193,7 @@
     // Whether to reveal it in the project panel automatically,
     // when a corresponding project entry becomes active.
     // Gitignored entries are never auto revealed.
-    "auto_reveal_entries": true,
-    /// Whether to fold directories automatically
-    /// when directory has only one directory inside.
-    "auto_fold_dirs": true
+    "auto_reveal_entries": true
   },
   "collaboration_panel": {
     // Whether to show the collaboration panel button in the status bar.

crates/project_panel/src/project_panel.rs 🔗

@@ -19,18 +19,11 @@ use gpui::{
 use menu::{Confirm, SelectNext, SelectPrev};
 use project::{
     repository::GitFileStatus, Entry, EntryKind, Fs, Project, ProjectEntryId, ProjectPath,
-    Snapshot, Worktree, WorktreeId,
+    Worktree, WorktreeId,
 };
 use project_panel_settings::{ProjectPanelDockPosition, ProjectPanelSettings};
 use serde::{Deserialize, Serialize};
-use std::{
-    cmp::Ordering,
-    collections::HashSet,
-    ffi::OsStr,
-    ops::Range,
-    path::{Path, PathBuf},
-    sync::Arc,
-};
+use std::{cmp::Ordering, ffi::OsStr, ops::Range, path::Path, sync::Arc};
 use theme::ThemeSettings;
 use ui::{prelude::*, v_flex, ContextMenu, Icon, KeyBinding, Label, ListItem};
 use unicase::UniCase;
@@ -52,7 +45,6 @@ pub struct ProjectPanel {
     visible_entries: Vec<(WorktreeId, Vec<Entry>)>,
     last_worktree_root_id: Option<ProjectEntryId>,
     expanded_dir_ids: HashMap<WorktreeId, Vec<ProjectEntryId>>,
-    unfolded_dir_ids: HashSet<ProjectEntryId>,
     selection: Option<Selection>,
     context_menu: Option<(View<ContextMenu>, Point<Pixels>, Subscription)>,
     edit_state: Option<EditState>,
@@ -128,8 +120,6 @@ actions!(
         Open,
         ToggleFocus,
         NewSearchInDirectory,
-        UnfoldDirectory,
-        FoldDirectory,
     ]
 );
 
@@ -241,7 +231,6 @@ impl ProjectPanel {
                 visible_entries: Default::default(),
                 last_worktree_root_id: Default::default(),
                 expanded_dir_ids: Default::default(),
-                unfolded_dir_ids: Default::default(),
                 selection: None,
                 edit_state: None,
                 context_menu: None,
@@ -400,11 +389,8 @@ impl ProjectPanel {
         });
 
         if let Some((worktree, entry)) = self.selected_entry(cx) {
-            let auto_fold_dirs = ProjectPanelSettings::get_global(cx).auto_fold_dirs;
             let is_root = Some(entry) == worktree.root_entry();
             let is_dir = entry.is_dir();
-            let is_foldable = self.is_foldable(entry, worktree) && auto_fold_dirs;
-            let is_unfoldable = self.is_unfoldable(entry, worktree) && auto_fold_dirs;
             let worktree_id = worktree.id();
             let is_local = project.is_local();
             let is_read_only = project.is_read_only();
@@ -455,12 +441,6 @@ impl ProjectPanel {
                             menu.action("Open in Terminal", Box::new(OpenInTerminal))
                                 .action("Search Inside", Box::new(NewSearchInDirectory))
                         })
-                        .when(is_unfoldable, |menu| {
-                            menu.action("Unfold Directory", Box::new(UnfoldDirectory))
-                        })
-                        .when(is_foldable, |menu| {
-                            menu.action("Fold Directory", Box::new(FoldDirectory))
-                        })
                         .separator()
                         .action("Rename", Box::new(Rename))
                         .when(!is_root, |menu| menu.action("Delete", Box::new(Delete)))
@@ -479,35 +459,6 @@ impl ProjectPanel {
         cx.notify();
     }
 
-    fn is_unfoldable(&self, entry: &Entry, worktree: &Worktree) -> bool {
-        if !entry.is_dir() || self.unfolded_dir_ids.contains(&entry.id) {
-            return false;
-        }
-
-        if let Some(parent_path) = entry.path.parent() {
-            let children_count = worktree
-                .entries(false)
-                .filter(|e| e.path.parent() == Some(parent_path))
-                .count();
-
-            return children_count <= 1;
-        };
-        false
-    }
-
-    fn is_foldable(&self, entry: &Entry, worktree: &Worktree) -> bool {
-        if !entry.is_dir() {
-            return false;
-        }
-
-        let children_count: Vec<&Entry> = worktree // children count for unfolded dirs
-            .entries(true)
-            .filter(|e| e.path.parent() == Some(&entry.path))
-            .collect();
-
-        children_count.len() <= 1 && (children_count.is_empty() || children_count[0].is_dir())
-    }
-
     fn expand_selected_entry(&mut self, _: &ExpandSelectedEntry, cx: &mut ViewContext<Self>) {
         if let Some((worktree, entry)) = self.selected_entry(cx) {
             if entry.is_dir() {
@@ -866,53 +817,6 @@ impl ProjectPanel {
         });
     }
 
-    fn unfold_directory(&mut self, _: &UnfoldDirectory, cx: &mut ViewContext<Self>) {
-        if let Some((worktree, entry)) = self.selected_entry(cx) {
-            self.unfolded_dir_ids.insert(entry.id);
-
-            let mut parent_path = entry.path.parent();
-            while let Some(path) = parent_path {
-                if let Some(parent_entry) = worktree.entry_for_path(path) {
-                    let children_count = worktree
-                        .entries(true)
-                        .filter(|e| e.path.parent() == Some(path))
-                        .count();
-
-                    if children_count > 1 {
-                        break;
-                    }
-
-                    self.unfolded_dir_ids.insert(parent_entry.id);
-                    parent_path = path.parent();
-                } else {
-                    break;
-                }
-            }
-
-            self.update_visible_entries(None, cx);
-            self.autoscroll(cx);
-            cx.notify();
-        }
-    }
-
-    fn fold_directory(&mut self, _: &FoldDirectory, cx: &mut ViewContext<Self>) {
-        if let Some((worktree, entry)) = self.selected_entry(cx) {
-            self.unfolded_dir_ids.remove(&entry.id);
-
-            let children = worktree
-                .entries(true)
-                .filter(|e| e.path.starts_with(&entry.path) && e.path != entry.path);
-
-            for child in children {
-                self.unfolded_dir_ids.remove(&child.id);
-            }
-
-            self.update_visible_entries(None, cx);
-            self.autoscroll(cx);
-            cx.notify();
-        }
-    }
-
     fn select_next(&mut self, _: &SelectNext, cx: &mut ViewContext<Self>) {
         if let Some(selection) = self.selection {
             let (mut worktree_ix, mut entry_ix, _) =
@@ -1196,7 +1100,6 @@ impl ProjectPanel {
         new_selected_entry: Option<(WorktreeId, ProjectEntryId)>,
         cx: &mut ViewContext<Self>,
     ) {
-        let auto_collapse_dirs = ProjectPanelSettings::get_global(cx).auto_fold_dirs;
         let project = self.project.read(cx);
         self.last_worktree_root_id = project
             .visible_worktrees(cx)
@@ -1238,19 +1141,8 @@ impl ProjectPanel {
 
             let mut visible_worktree_entries = Vec::new();
             let mut entry_iter = snapshot.entries(true);
-            while let Some(entry) = entry_iter.entry() {
-                if auto_collapse_dirs
-                    && entry.kind.is_dir()
-                    && !self.unfolded_dir_ids.contains(&entry.id)
-                {
-                    let is_omitted = ProjectPanel::should_omit_entry(snapshot.clone(), entry);
-
-                    if is_omitted {
-                        entry_iter.advance();
-                        continue;
-                    }
-                }
 
+            while let Some(entry) = entry_iter.entry() {
                 visible_worktree_entries.push(entry.clone());
                 if Some(entry.id) == new_entry_parent_id {
                     visible_worktree_entries.push(Entry {
@@ -1313,22 +1205,6 @@ impl ProjectPanel {
         }
     }
 
-    fn should_omit_entry(snapshot: Snapshot, entry: &Entry) -> bool {
-        if let Some(root_path) = snapshot.root_entry() {
-            if entry.path == root_path.path {
-                return false;
-            }
-        }
-
-        let children: Vec<&Entry> = snapshot
-            .entries(true)
-            .into_iter()
-            .filter(|e| e.path.parent() == Some(&entry.path))
-            .collect();
-
-        children.len() == 1 && children[0].kind.is_dir()
-    }
-
     fn expand_entry(
         &mut self,
         worktree_id: WorktreeId,
@@ -1418,32 +1294,16 @@ impl ProjectPanel {
                         }
                     };
 
-                    let (depth, difference) = ProjectPanel::calculate_depth_and_difference(
-                        entry,
-                        visible_worktree_entries,
-                    );
-
-                    let filename = match difference {
-                        diff if diff > 1 => entry
-                            .path
-                            .iter()
-                            .skip(entry.path.components().count() - diff)
-                            .collect::<PathBuf>()
-                            .to_str()
-                            .unwrap_or_default()
-                            .to_string(),
-                        _ => entry
+                    let mut details = EntryDetails {
+                        filename: entry
                             .path
                             .file_name()
-                            .map(|name| name.to_string_lossy().into_owned())
-                            .unwrap_or_else(|| root_name.to_string_lossy().to_string()),
-                    };
-
-                    let mut details = EntryDetails {
-                        filename,
+                            .unwrap_or(root_name)
+                            .to_string_lossy()
+                            .to_string(),
                         icon,
                         path: entry.path.clone(),
-                        depth,
+                        depth: entry.path.components().count(),
                         kind: entry.kind,
                         is_ignored: entry.is_ignored,
                         is_expanded,
@@ -1487,40 +1347,6 @@ impl ProjectPanel {
         }
     }
 
-    fn calculate_depth_and_difference(
-        entry: &Entry,
-        visible_worktree_entries: &Vec<Entry>,
-    ) -> (usize, usize) {
-        let entry_path_components_count = entry.path.components().count();
-        let (depth, difference) = entry
-            .path
-            .ancestors()
-            .skip(1) // Skip the entry itself
-            .find_map(|ancestor| {
-                visible_worktree_entries
-                    .iter()
-                    .find(|&e| *e.path == *ancestor)
-                    .map(|parent_entry| {
-                        let parent_path_components_count = parent_entry.path.components().count();
-                        let difference = entry_path_components_count - parent_path_components_count;
-                        let depth = parent_entry
-                            .path
-                            .ancestors()
-                            .skip(1)
-                            .filter(|ancestor| {
-                                visible_worktree_entries
-                                    .iter()
-                                    .any(|e| *e.path == **ancestor)
-                            })
-                            .count();
-                        (depth + 1, difference)
-                    })
-            })
-            .unwrap_or((0, 0));
-
-        (depth, difference)
-    }
-
     fn render_entry(
         &self,
         entry_id: ProjectEntryId,
@@ -1677,8 +1503,6 @@ impl Render for ProjectPanel {
                 .on_action(cx.listener(Self::copy_path))
                 .on_action(cx.listener(Self::copy_relative_path))
                 .on_action(cx.listener(Self::new_search_in_directory))
-                .on_action(cx.listener(Self::unfold_directory))
-                .on_action(cx.listener(Self::fold_directory))
                 .when(!project.is_read_only(), |el| {
                     el.on_action(cx.listener(Self::new_file))
                         .on_action(cx.listener(Self::new_directory))
@@ -2029,7 +1853,7 @@ mod tests {
             &[
                 "v root1",
                 "    > a",
-                "    > b/3",
+                "    > b",
                 "    > C",
                 "      .dockerignore",
                 "v root2",
@@ -2038,14 +1862,14 @@ mod tests {
             ]
         );
 
-        toggle_expand_dir(&panel, "root1/b/3", cx);
+        toggle_expand_dir(&panel, "root1/b", cx);
         assert_eq!(
             visible_entries_as_strings(&panel, 0..50, cx),
             &[
                 "v root1",
                 "    > a",
-                "    v b/3  <== selected",
-                "          Q",
+                "    v b  <== selected",
+                "        > 3",
                 "    > C",
                 "      .dockerignore",
                 "v root2",
@@ -2060,8 +1884,8 @@ mod tests {
             &[
                 "v root1",
                 "    > a",
-                "    v b/3",
-                "          Q",
+                "    v b",
+                "        > 3",
                 "    > C",
                 "      .dockerignore",
                 "v root2",
@@ -2076,8 +1900,8 @@ mod tests {
             &[
                 "v root1",
                 "    > a",
-                "    v b/3",
-                "          Q",
+                "    v b",
+                "        > 3",
                 "    > C",
                 "      .dockerignore",
                 "v root2",
@@ -2087,115 +1911,6 @@ mod tests {
         );
     }
 
-    #[gpui::test]
-    async fn test_auto_collapse_dir_paths(cx: &mut gpui::TestAppContext) {
-        init_test(cx);
-
-        let fs = FakeFs::new(cx.executor().clone());
-        fs.insert_tree(
-            "/root1",
-            json!({
-                "dir_1": {
-                    "nested_dir_1": {
-                        "nested_dir_2": {
-                            "nested_dir_3": {
-                                "file_a.java": "// File contents",
-                                "file_b.java": "// File contents",
-                                "file_c.java": "// File contents",
-                                "nested_dir_4": {
-                                    "nested_dir_5": {
-                                        "file_d.java": "// File contents",
-                                    }
-                                }
-                            }
-                        }
-                    }
-                }
-            }),
-        )
-        .await;
-        fs.insert_tree(
-            "/root2",
-            json!({
-                "dir_2": {
-                    "file_1.java": "// File contents",
-                }
-            }),
-        )
-        .await;
-
-        let project = Project::test(fs.clone(), ["/root1".as_ref(), "/root2".as_ref()], cx).await;
-        let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
-        let cx = &mut VisualTestContext::from_window(*workspace, cx);
-        let panel = workspace
-            .update(cx, |workspace, cx| ProjectPanel::new(workspace, cx))
-            .unwrap();
-        assert_eq!(
-            visible_entries_as_strings(&panel, 0..10, cx),
-            &[
-                "v root1",
-                "    > dir_1/nested_dir_1/nested_dir_2/nested_dir_3",
-                "v root2",
-                "    > dir_2",
-            ]
-        );
-
-        toggle_expand_dir(
-            &panel,
-            "root1/dir_1/nested_dir_1/nested_dir_2/nested_dir_3",
-            cx,
-        );
-        assert_eq!(
-            visible_entries_as_strings(&panel, 0..10, cx),
-            &[
-                "v root1",
-                "    v dir_1/nested_dir_1/nested_dir_2/nested_dir_3  <== selected",
-                "        > nested_dir_4/nested_dir_5",
-                "          file_a.java",
-                "          file_b.java",
-                "          file_c.java",
-                "v root2",
-                "    > dir_2",
-            ]
-        );
-
-        toggle_expand_dir(
-            &panel,
-            "root1/dir_1/nested_dir_1/nested_dir_2/nested_dir_3/nested_dir_4/nested_dir_5",
-            cx,
-        );
-        assert_eq!(
-            visible_entries_as_strings(&panel, 0..10, cx),
-            &[
-                "v root1",
-                "    v dir_1/nested_dir_1/nested_dir_2/nested_dir_3",
-                "        v nested_dir_4/nested_dir_5  <== selected",
-                "              file_d.java",
-                "          file_a.java",
-                "          file_b.java",
-                "          file_c.java",
-                "v root2",
-                "    > dir_2",
-            ]
-        );
-        toggle_expand_dir(&panel, "root2/dir_2", cx);
-        assert_eq!(
-            visible_entries_as_strings(&panel, 0..10, cx),
-            &[
-                "v root1",
-                "    v dir_1/nested_dir_1/nested_dir_2/nested_dir_3",
-                "        v nested_dir_4/nested_dir_5",
-                "              file_d.java",
-                "          file_a.java",
-                "          file_b.java",
-                "          file_c.java",
-                "v root2",
-                "    v dir_2  <== selected",
-                "          file_1.java",
-            ]
-        );
-    }
-
     #[gpui::test(iterations = 30)]
     async fn test_editing_files(cx: &mut gpui::TestAppContext) {
         init_test(cx);
@@ -2674,8 +2389,9 @@ mod tests {
                 "    > .git",
                 "    > a",
                 "    > b",
-                "    v bdir1/dir2",
-                "          the-new-filename  <== selected",
+                "    v bdir1",
+                "        v dir2",
+                "              the-new-filename  <== selected",
                 "    > C",
                 "      .dockerignore",
                 "v root2",
@@ -2809,6 +2525,7 @@ mod tests {
             "Directories inside pasted directory should have an entry"
         );
 
+        toggle_expand_dir(&panel, "root/b", cx);
         toggle_expand_dir(&panel, "root/b/a", cx);
         toggle_expand_dir(&panel, "root/b/a/inner_dir", cx);
 
@@ -2818,12 +2535,13 @@ mod tests {
                 //
                 "v root",
                 "    > a",
-                "    v b/a",
-                "        v inner_dir  <== selected",
-                "              four.txt",
-                "              three.txt",
-                "          one.txt",
-                "          two.txt",
+                "    v b",
+                "        v a",
+                "            v inner_dir  <== selected",
+                "                  four.txt",
+                "                  three.txt",
+                "              one.txt",
+                "              two.txt",
             ]
         );
 
@@ -2840,12 +2558,13 @@ mod tests {
                 "    > a",
                 "    > a copy",
                 "    > a copy 1",
-                "    v b/a",
-                "        v inner_dir",
-                "              four.txt",
-                "              three.txt",
-                "          one.txt",
-                "          two.txt"
+                "    v b",
+                "        v a",
+                "            v inner_dir",
+                "                  four.txt",
+                "                  three.txt",
+                "              one.txt",
+                "              two.txt"
             ]
         );
     }
@@ -3137,15 +2856,18 @@ mod tests {
 
         panel.update(cx, |panel, cx| panel.open(&Open, cx));
         cx.executor().run_until_parked();
+        select_path(&panel, "project_root/dir_1", cx);
+        panel.update(cx, |panel, cx| panel.open(&Open, cx));
         select_path(&panel, "project_root/dir_1/nested_dir", cx);
         panel.update(cx, |panel, cx| panel.open(&Open, cx));
+        panel.update(cx, |panel, cx| panel.open(&Open, cx));
         cx.executor().run_until_parked();
         assert_eq!(
             visible_entries_as_strings(&panel, 0..10, cx),
             &[
                 "v project_root",
-                "    v dir_1/nested_dir  <== selected",
-                "          file_a.py",
+                "    v dir_1",
+                "        > nested_dir  <== selected",
                 "      file_1.py",
             ]
         );

crates/project_panel/src/project_panel_settings.rs 🔗

@@ -20,7 +20,6 @@ pub struct ProjectPanelSettings {
     pub git_status: bool,
     pub indent_size: f32,
     pub auto_reveal_entries: bool,
-    pub auto_fold_dirs: bool,
 }
 
 #[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)]
@@ -55,11 +54,6 @@ pub struct ProjectPanelSettingsContent {
     ///
     /// Default: true
     pub auto_reveal_entries: Option<bool>,
-    /// Whether to fold directories automatically
-    /// when directory has only one directory inside.
-    ///
-    /// Default: true
-    pub auto_fold_dirs: Option<bool>,
 }
 
 impl Settings for ProjectPanelSettings {