WIP2

Piotr Osiewicz and Smit Barmase created

Co-authored-by: Smit Barmase <heysmitbarmase@gmail.com>

Change summary

crates/project_panel/src/project_panel.rs | 549 +++++++++++++-----------
1 file changed, 294 insertions(+), 255 deletions(-)

Detailed changes

crates/project_panel/src/project_panel.rs 🔗

@@ -88,6 +88,23 @@ struct State {
     // Currently selected leaf entry (see auto-folding for a definition of that) in a file tree
     selection: Option<SelectedEntry>,
     edit_state: Option<EditState>,
+    unfolded_dir_ids: HashSet<ProjectEntryId>,
+    expanded_dir_ids: HashMap<WorktreeId, Vec<ProjectEntryId>>,
+}
+
+impl State {
+    fn derive(old: &Self) -> Self {
+        Self {
+            last_worktree_root_id: None,
+            ancestors: Default::default(),
+            visible_entries: Default::default(),
+            max_width_item_index: None,
+            edit_state: old.edit_state.clone(),
+            unfolded_dir_ids: old.unfolded_dir_ids.clone(),
+            selection: old.selection.clone(),
+            expanded_dir_ids: old.expanded_dir_ids.clone(),
+        }
+    }
 }
 
 pub struct ProjectPanel {
@@ -100,8 +117,6 @@ pub struct ProjectPanel {
     hover_scroll_task: Option<Task<()>>,
     folded_directory_drag_target: Option<FoldedDirectoryDragTarget>,
     drag_target_entry: Option<DragTarget>,
-    expanded_dir_ids: HashMap<WorktreeId, Vec<ProjectEntryId>>,
-    unfolded_dir_ids: HashSet<ProjectEntryId>,
     marked_entries: Vec<SelectedEntry>,
     context_menu: Option<(Entity<ContextMenu>, Point<Pixels>, Subscription)>,
     filename_editor: Entity<Editor>,
@@ -298,7 +313,7 @@ actions!(
     ]
 );
 
-#[derive(Debug, Default)]
+#[derive(Clone, Debug, Default)]
 struct FoldedAncestors {
     current_ancestor_depth: usize,
     ancestors: Vec<ProjectEntryId>,
@@ -522,7 +537,7 @@ impl ProjectPanel {
                     }
                 }
                 project::Event::WorktreeRemoved(id) => {
-                    this.expanded_dir_ids.remove(id);
+                    this.state.expanded_dir_ids.remove(id);
                     this.update_visible_entries(None, cx);
                     cx.notify();
                 }
@@ -536,7 +551,7 @@ impl ProjectPanel {
                     if let Some((worktree, expanded_dir_ids)) = project
                         .read(cx)
                         .worktree_for_id(*worktree_id, cx)
-                        .zip(this.expanded_dir_ids.get_mut(worktree_id))
+                        .zip(this.state.expanded_dir_ids.get_mut(worktree_id))
                     {
                         let worktree = worktree.read(cx);
 
@@ -560,7 +575,7 @@ impl ProjectPanel {
                                 if let Err(ix) = expanded_dir_ids.binary_search(&child.id) {
                                     expanded_dir_ids.insert(ix, child.id);
                                 }
-                                this.unfolded_dir_ids.insert(child.id);
+                                this.state.unfolded_dir_ids.insert(child.id);
                             }
                         }
                         this.update_visible_entries(None, cx);
@@ -642,8 +657,7 @@ impl ProjectPanel {
                 focus_handle,
                 folded_directory_drag_target: None,
                 drag_target_entry: None,
-                expanded_dir_ids: Default::default(),
-                unfolded_dir_ids: Default::default(),
+
                 marked_entries: Default::default(),
                 context_menu: None,
                 filename_editor,
@@ -667,6 +681,8 @@ impl ProjectPanel {
                     last_worktree_root_id: Default::default(),
                     visible_entries: Default::default(),
                     ancestors: Default::default(),
+                    expanded_dir_ids: Default::default(),
+                    unfolded_dir_ids: Default::default(),
                 },
                 update_visible_entries_task: Task::ready(()),
             };
@@ -1022,7 +1038,7 @@ impl ProjectPanel {
     }
 
     fn is_unfoldable(&self, entry: &Entry, worktree: &Worktree) -> bool {
-        if !entry.is_dir() || self.unfolded_dir_ids.contains(&entry.id) {
+        if !entry.is_dir() || self.state.unfolded_dir_ids.contains(&entry.id) {
             return false;
         }
 
@@ -1069,12 +1085,13 @@ impl ProjectPanel {
             if entry.is_dir() {
                 let worktree_id = worktree.id();
                 let entry_id = entry.id;
-                let expanded_dir_ids =
-                    if let Some(expanded_dir_ids) = self.expanded_dir_ids.get_mut(&worktree_id) {
-                        expanded_dir_ids
-                    } else {
-                        return;
-                    };
+                let expanded_dir_ids = if let Some(expanded_dir_ids) =
+                    self.state.expanded_dir_ids.get_mut(&worktree_id)
+                {
+                    expanded_dir_ids
+                } else {
+                    return;
+                };
 
                 match expanded_dir_ids.binary_search(&entry_id) {
                     Ok(_) => self.select_next(&SelectNext, window, cx),
@@ -1115,7 +1132,7 @@ impl ProjectPanel {
         }
         let worktree_id = worktree.id();
         let expanded_dir_ids =
-            if let Some(expanded_dir_ids) = self.expanded_dir_ids.get_mut(&worktree_id) {
+            if let Some(expanded_dir_ids) = self.state.expanded_dir_ids.get_mut(&worktree_id) {
                 expanded_dir_ids
             } else {
                 return;
@@ -1155,7 +1172,8 @@ impl ProjectPanel {
         let multiple_worktrees = self.project.read(cx).worktrees(cx).count() > 1;
         let project = self.project.read(cx);
 
-        self.expanded_dir_ids
+        self.state
+            .expanded_dir_ids
             .iter_mut()
             .for_each(|(worktree_id, expanded_entries)| {
                 if multiple_worktrees {
@@ -1189,7 +1207,7 @@ impl ProjectPanel {
         cx: &mut Context<Self>,
     ) {
         if let Some(worktree_id) = self.project.read(cx).worktree_id_for_entry(entry_id, cx)
-            && let Some(expanded_dir_ids) = self.expanded_dir_ids.get_mut(&worktree_id)
+            && let Some(expanded_dir_ids) = self.state.expanded_dir_ids.get_mut(&worktree_id)
         {
             self.project.update(cx, |project, cx| {
                 match expanded_dir_ids.binary_search(&entry_id) {
@@ -1215,7 +1233,7 @@ impl ProjectPanel {
         cx: &mut Context<Self>,
     ) {
         if let Some(worktree_id) = self.project.read(cx).worktree_id_for_entry(entry_id, cx)
-            && let Some(expanded_dir_ids) = self.expanded_dir_ids.get_mut(&worktree_id)
+            && let Some(expanded_dir_ids) = self.state.expanded_dir_ids.get_mut(&worktree_id)
         {
             match expanded_dir_ids.binary_search(&entry_id) {
                 Ok(_ix) => {
@@ -1240,7 +1258,7 @@ impl ProjectPanel {
         self.project.update(cx, |project, cx| {
             if let Some((worktree, expanded_dir_ids)) = project
                 .worktree_for_id(worktree_id, cx)
-                .zip(self.expanded_dir_ids.get_mut(&worktree_id))
+                .zip(self.state.expanded_dir_ids.get_mut(&worktree_id))
             {
                 if let Some(task) = project.expand_all_for_entry(worktree_id, entry_id, cx) {
                     task.detach();
@@ -1276,7 +1294,7 @@ impl ProjectPanel {
         self.project.update(cx, |project, cx| {
             if let Some((worktree, expanded_dir_ids)) = project
                 .worktree_for_id(worktree_id, cx)
-                .zip(self.expanded_dir_ids.get_mut(&worktree_id))
+                .zip(self.state.expanded_dir_ids.get_mut(&worktree_id))
             {
                 let worktree = worktree.read(cx);
                 let mut dirs_to_collapse = vec![entry_id];
@@ -1289,7 +1307,7 @@ impl ProjectPanel {
                         expanded_dir_ids.remove(ix);
                     }
                     if auto_fold_enabled {
-                        self.unfolded_dir_ids.remove(&current_id);
+                        self.state.unfolded_dir_ids.remove(&current_id);
                     }
                     for child in worktree.child_entries(&current_entry.path) {
                         if child.is_dir() {
@@ -1706,7 +1724,7 @@ impl ProjectPanel {
             .project
             .read(cx)
             .worktree_for_id(worktree_id, cx)
-            .zip(self.expanded_dir_ids.get_mut(&worktree_id))
+            .zip(self.state.expanded_dir_ids.get_mut(&worktree_id))
         {
             let worktree = worktree.read(cx);
             if let Some(mut entry) = worktree.entry_for_id(new_entry_id) {
@@ -2039,7 +2057,7 @@ impl ProjectPanel {
 
     fn unfold_directory(&mut self, _: &UnfoldDirectory, _: &mut Window, cx: &mut Context<Self>) {
         if let Some((worktree, entry)) = self.selected_entry(cx) {
-            self.unfolded_dir_ids.insert(entry.id);
+            self.state.unfolded_dir_ids.insert(entry.id);
 
             let snapshot = worktree.snapshot();
             let mut parent_path = entry.path.parent();
@@ -2051,7 +2069,7 @@ impl ProjectPanel {
                         break;
                     }
 
-                    self.unfolded_dir_ids.insert(parent_entry.id);
+                    self.state.unfolded_dir_ids.insert(parent_entry.id);
                     parent_path = path.parent();
                 } else {
                     break;
@@ -2066,7 +2084,7 @@ impl ProjectPanel {
 
     fn fold_directory(&mut self, _: &FoldDirectory, _: &mut Window, cx: &mut Context<Self>) {
         if let Some((worktree, entry)) = self.selected_entry(cx) {
-            self.unfolded_dir_ids.remove(&entry.id);
+            self.state.unfolded_dir_ids.remove(&entry.id);
 
             let snapshot = worktree.snapshot();
             let mut path = &*entry.path;
@@ -2074,7 +2092,7 @@ impl ProjectPanel {
                 let mut child_entries_iter = snapshot.child_entries(path);
                 if let Some(child) = child_entries_iter.next() {
                     if child_entries_iter.next().is_none() && child.is_dir() {
-                        self.unfolded_dir_ids.remove(&child.id);
+                        self.state.unfolded_dir_ids.remove(&child.id);
                         path = &*child.path;
                     } else {
                         break;
@@ -2990,7 +3008,11 @@ impl ProjectPanel {
 
     fn expand_to_selection(&mut self, cx: &mut Context<Self>) -> Option<()> {
         let (worktree, entry) = self.selected_entry(cx)?;
-        let expanded_dir_ids = self.expanded_dir_ids.entry(worktree.id()).or_default();
+        let expanded_dir_ids = self
+            .state
+            .expanded_dir_ids
+            .entry(worktree.id())
+            .or_default();
 
         for path in entry.path.ancestors() {
             let Some(entry) = worktree.entry_for_path(path) else {
@@ -3042,249 +3064,263 @@ impl ProjectPanel {
         let hide_gitignore = settings.hide_gitignore;
         let project = self.project.read(cx);
         let repo_snapshots = project.git_store().read(cx).repo_snapshots(cx);
-        self.state.last_worktree_root_id = project
+
+        let old_ancestors = self.state.ancestors.clone();
+        let mut new_state = State::derive(&self.state);
+        new_state.last_worktree_root_id = project
             .visible_worktrees(cx)
             .next_back()
             .and_then(|worktree| worktree.read(cx).root_entry())
             .map(|entry| entry.id);
-
-        let old_ancestors = std::mem::take(&mut self.state.ancestors);
-        self.state.visible_entries.clear();
         let mut max_width_item = None;
 
-        let visible_worktrees: Vec<_> = project.visible_worktrees(cx).collect();
+        let visible_worktrees: Vec<_> = project
+            .visible_worktrees(cx)
+            .map(|worktree| worktree.read(cx).snapshot())
+            .collect();
         let hide_root = settings.hide_root && visible_worktrees.len() == 1;
-        for worktree in visible_worktrees {
-            let worktree_snapshot = worktree.read(cx).snapshot();
-            let worktree_id = worktree_snapshot.id();
-
-            let expanded_dir_ids = match self.expanded_dir_ids.entry(worktree_id) {
-                hash_map::Entry::Occupied(e) => e.into_mut(),
-                hash_map::Entry::Vacant(e) => {
-                    // The first time a worktree's root entry becomes available,
-                    // mark that root entry as expanded.
-                    if let Some(entry) = worktree_snapshot.root_entry() {
-                        e.insert(vec![entry.id]).as_slice()
-                    } else {
-                        &[]
-                    }
-                }
-            };
-
-            let mut new_entry_parent_id = None;
-            let mut new_entry_kind = EntryKind::Dir;
-            if let Some(edit_state) = &self.state.edit_state
-                && edit_state.worktree_id == worktree_id
-                && edit_state.is_new_entry()
-            {
-                new_entry_parent_id = Some(edit_state.entry_id);
-                new_entry_kind = if edit_state.is_dir {
-                    EntryKind::Dir
-                } else {
-                    EntryKind::File
-                };
-            }
+        self.update_visible_entries_task = cx.spawn(async move |this, cx| {
+            let new_state = cx
+                .background_spawn(async move {
+                    for worktree_snapshot in visible_worktrees {
+                        let worktree_id = worktree_snapshot.id();
+
+                        let expanded_dir_ids = match new_state.expanded_dir_ids.entry(worktree_id) {
+                            hash_map::Entry::Occupied(e) => e.into_mut(),
+                            hash_map::Entry::Vacant(e) => {
+                                // The first time a worktree's root entry becomes available,
+                                // mark that root entry as expanded.
+                                if let Some(entry) = worktree_snapshot.root_entry() {
+                                    e.insert(vec![entry.id]).as_slice()
+                                } else {
+                                    &[]
+                                }
+                            }
+                        };
 
-            let mut visible_worktree_entries = Vec::new();
-            let mut entry_iter =
-                GitTraversal::new(&repo_snapshots, worktree_snapshot.entries(true, 0));
-            let mut auto_folded_ancestors = vec![];
-            while let Some(entry) = entry_iter.entry() {
-                if hide_root && Some(entry.entry) == worktree.read(cx).root_entry() {
-                    if new_entry_parent_id == Some(entry.id) {
-                        visible_worktree_entries.push(Self::create_new_git_entry(
-                            entry.entry,
-                            entry.git_summary,
-                            new_entry_kind,
-                        ));
-                        new_entry_parent_id = None;
-                    }
-                    entry_iter.advance();
-                    continue;
-                }
-                if auto_collapse_dirs && entry.kind.is_dir() {
-                    auto_folded_ancestors.push(entry.id);
-                    if !self.unfolded_dir_ids.contains(&entry.id)
-                        && let Some(root_path) = worktree_snapshot.root_entry()
-                    {
-                        let mut child_entries = worktree_snapshot.child_entries(&entry.path);
-                        if let Some(child) = child_entries.next()
-                            && entry.path != root_path.path
-                            && child_entries.next().is_none()
-                            && child.kind.is_dir()
+                        let mut new_entry_parent_id = None;
+                        let mut new_entry_kind = EntryKind::Dir;
+                        if let Some(edit_state) = &new_state.edit_state
+                            && edit_state.worktree_id == worktree_id
+                            && edit_state.is_new_entry()
                         {
-                            entry_iter.advance();
-
-                            continue;
+                            new_entry_parent_id = Some(edit_state.entry_id);
+                            new_entry_kind = if edit_state.is_dir {
+                                EntryKind::Dir
+                            } else {
+                                EntryKind::File
+                            };
                         }
-                    }
-                    let depth = old_ancestors
-                        .get(&entry.id)
-                        .map(|ancestor| ancestor.current_ancestor_depth)
-                        .unwrap_or_default()
-                        .min(auto_folded_ancestors.len());
-                    if let Some(edit_state) = &mut self.state.edit_state
-                        && edit_state.entry_id == entry.id
-                    {
-                        edit_state.depth = depth;
-                    }
-                    let mut ancestors = std::mem::take(&mut auto_folded_ancestors);
-                    if ancestors.len() > 1 {
-                        ancestors.reverse();
-                        self.state.ancestors.insert(
-                            entry.id,
-                            FoldedAncestors {
-                                current_ancestor_depth: depth,
-                                ancestors,
-                            },
-                        );
-                    }
-                }
-                auto_folded_ancestors.clear();
-                if !hide_gitignore || !entry.is_ignored {
-                    visible_worktree_entries.push(entry.to_owned());
-                }
-                let precedes_new_entry = if let Some(new_entry_id) = new_entry_parent_id {
-                    entry.id == new_entry_id || {
-                        self.state
-                            .ancestors
-                            .get(&entry.id)
-                            .is_some_and(|entries| entries.ancestors.contains(&new_entry_id))
-                    }
-                } else {
-                    false
-                };
-                if precedes_new_entry && (!hide_gitignore || !entry.is_ignored) {
-                    visible_worktree_entries.push(Self::create_new_git_entry(
-                        entry.entry,
-                        entry.git_summary,
-                        new_entry_kind,
-                    ));
-                }
-                let worktree_abs_path = worktree.read(cx).abs_path();
-                let (depth, chars) =
-                    if Some(entry.entry) == worktree.read(cx).root_entry() {
-                        let Some(path_name) = worktree_abs_path.file_name() else {
-                            continue;
-                        };
-                        let depth = 0;
-                        (depth, path_name.to_string_lossy().chars().count())
-                    } else if entry.is_file() {
-                        let Some(path_name) = entry
-                            .path
-                            .file_name()
-                            .with_context(|| format!("Non-root entry has no file name: {entry:?}"))
-                            .log_err()
-                        else {
-                            continue;
-                        };
-                        let depth = entry.path.ancestors().count() - 1;
-                        (depth, path_name.chars().count())
-                    } else {
-                        let path =
-                            self.state
-                                .ancestors
-                                .get(&entry.id)
-                                .and_then(|ancestors| {
-                                    let outermost_ancestor = ancestors.ancestors.last()?;
-                                    let root_folded_entry = worktree
-                                        .read(cx)
-                                        .entry_for_id(*outermost_ancestor)?
-                                        .path
-                                        .as_ref();
-                                    entry.path.strip_prefix(root_folded_entry).ok().and_then(
-                                        |suffix| {
-                                            Some(
-                                                RelPath::new(root_folded_entry.file_name()?)
-                                                    .unwrap()
-                                                    .join(suffix),
-                                            )
+
+                        let mut visible_worktree_entries = Vec::new();
+                        let mut entry_iter =
+                            GitTraversal::new(&repo_snapshots, worktree_snapshot.entries(true, 0));
+                        let mut auto_folded_ancestors = vec![];
+                        let worktree_abs_path = worktree_snapshot.abs_path();
+                        while let Some(entry) = entry_iter.entry() {
+                            if hide_root && Some(entry.entry) == worktree_snapshot.root_entry() {
+                                if new_entry_parent_id == Some(entry.id) {
+                                    visible_worktree_entries.push(Self::create_new_git_entry(
+                                        entry.entry,
+                                        entry.git_summary,
+                                        new_entry_kind,
+                                    ));
+                                    new_entry_parent_id = None;
+                                }
+                                entry_iter.advance();
+                                continue;
+                            }
+                            if auto_collapse_dirs && entry.kind.is_dir() {
+                                auto_folded_ancestors.push(entry.id);
+                                if !new_state.unfolded_dir_ids.contains(&entry.id)
+                                    && let Some(root_path) = worktree_snapshot.root_entry()
+                                {
+                                    let mut child_entries =
+                                        worktree_snapshot.child_entries(&entry.path);
+                                    if let Some(child) = child_entries.next()
+                                        && entry.path != root_path.path
+                                        && child_entries.next().is_none()
+                                        && child.kind.is_dir()
+                                    {
+                                        entry_iter.advance();
+
+                                        continue;
+                                    }
+                                }
+                                let depth = old_ancestors
+                                    .get(&entry.id)
+                                    .map(|ancestor| ancestor.current_ancestor_depth)
+                                    .unwrap_or_default()
+                                    .min(auto_folded_ancestors.len());
+                                if let Some(edit_state) = &mut new_state.edit_state
+                                    && edit_state.entry_id == entry.id
+                                {
+                                    edit_state.depth = depth;
+                                }
+                                let mut ancestors = std::mem::take(&mut auto_folded_ancestors);
+                                if ancestors.len() > 1 {
+                                    ancestors.reverse();
+                                    new_state.ancestors.insert(
+                                        entry.id,
+                                        FoldedAncestors {
+                                            current_ancestor_depth: depth,
+                                            ancestors,
                                         },
-                                    )
-                                })
-                                .or_else(|| {
-                                    entry
-                                        .path
-                                        .file_name()
-                                        .map(|file_name| RelPath::new(file_name).unwrap().into())
-                                })
-                                .unwrap_or_else(|| entry.path.clone());
-                        let depth = path.components().count();
-                        (depth, path.as_str().chars().count())
-                    };
-                let width_estimate =
-                    item_width_estimate(depth, chars, entry.canonical_path.is_some());
-
-                match max_width_item.as_mut() {
-                    Some((id, worktree_id, width)) => {
-                        if *width < width_estimate {
-                            *id = entry.id;
-                            *worktree_id = worktree.read(cx).id();
-                            *width = width_estimate;
-                        }
-                    }
-                    None => {
-                        max_width_item = Some((entry.id, worktree.read(cx).id(), width_estimate))
-                    }
-                }
+                                    );
+                                }
+                            }
+                            auto_folded_ancestors.clear();
+                            if !hide_gitignore || !entry.is_ignored {
+                                visible_worktree_entries.push(entry.to_owned());
+                            }
+                            let precedes_new_entry = if let Some(new_entry_id) = new_entry_parent_id
+                            {
+                                entry.id == new_entry_id || {
+                                    new_state.ancestors.get(&entry.id).is_some_and(|entries| {
+                                        entries.ancestors.contains(&new_entry_id)
+                                    })
+                                }
+                            } else {
+                                false
+                            };
+                            if precedes_new_entry && (!hide_gitignore || !entry.is_ignored) {
+                                visible_worktree_entries.push(Self::create_new_git_entry(
+                                    entry.entry,
+                                    entry.git_summary,
+                                    new_entry_kind,
+                                ));
+                            }
 
-                if expanded_dir_ids.binary_search(&entry.id).is_err()
-                    && entry_iter.advance_to_sibling()
-                {
-                    continue;
-                }
-                entry_iter.advance();
-            }
+                            let (depth, chars) = if Some(entry.entry)
+                                == worktree_snapshot.root_entry()
+                            {
+                                let Some(path_name) = worktree_abs_path.file_name() else {
+                                    continue;
+                                };
+                                let depth = 0;
+                                (depth, path_name.to_string_lossy().chars().count())
+                            } else if entry.is_file() {
+                                let Some(path_name) = entry
+                                    .path
+                                    .file_name()
+                                    .with_context(|| {
+                                        format!("Non-root entry has no file name: {entry:?}")
+                                    })
+                                    .log_err()
+                                else {
+                                    continue;
+                                };
+                                let depth = entry.path.ancestors().count() - 1;
+                                (depth, path_name.chars().count())
+                            } else {
+                                let path = new_state
+                                    .ancestors
+                                    .get(&entry.id)
+                                    .and_then(|ancestors| {
+                                        let outermost_ancestor = ancestors.ancestors.last()?;
+                                        let root_folded_entry = worktree_snapshot
+                                            .entry_for_id(*outermost_ancestor)?
+                                            .path
+                                            .as_ref();
+                                        entry.path.strip_prefix(root_folded_entry).ok().and_then(
+                                            |suffix| {
+                                                Some(
+                                                    RelPath::new(root_folded_entry.file_name()?)
+                                                        .unwrap()
+                                                        .join(suffix),
+                                                )
+                                            },
+                                        )
+                                    })
+                                    .or_else(|| {
+                                        entry.path.file_name().map(|file_name| {
+                                            RelPath::new(file_name).unwrap().into()
+                                        })
+                                    })
+                                    .unwrap_or_else(|| entry.path.clone());
+                                let depth = path.components().count();
+                                (depth, path.as_str().chars().count())
+                            };
+                            let width_estimate =
+                                item_width_estimate(depth, chars, entry.canonical_path.is_some());
+
+                            match max_width_item.as_mut() {
+                                Some((id, worktree_id, width)) => {
+                                    if *width < width_estimate {
+                                        *id = entry.id;
+                                        *worktree_id = worktree_snapshot.id();
+                                        *width = width_estimate;
+                                    }
+                                }
+                                None => {
+                                    max_width_item =
+                                        Some((entry.id, worktree_snapshot.id(), width_estimate))
+                                }
+                            }
 
-            project::sort_worktree_entries(&mut visible_worktree_entries);
+                            if expanded_dir_ids.binary_search(&entry.id).is_err()
+                                && entry_iter.advance_to_sibling()
+                            {
+                                continue;
+                            }
+                            entry_iter.advance();
+                        }
 
-            self.state.visible_entries.push(VisibleEntriesForWorktree {
-                worktree_id,
-                entries: visible_worktree_entries,
-                index: OnceCell::new(),
-            })
-        }
+                        project::sort_worktree_entries(&mut visible_worktree_entries);
 
-        if let Some((project_entry_id, worktree_id, _)) = max_width_item {
-            let mut visited_worktrees_length = 0;
-            let index = self
-                .state
-                .visible_entries
-                .iter()
-                .find_map(|visible_entries| {
-                    if worktree_id == visible_entries.worktree_id {
-                        visible_entries
-                            .entries
+                        new_state.visible_entries.push(VisibleEntriesForWorktree {
+                            worktree_id,
+                            entries: visible_worktree_entries,
+                            index: OnceCell::new(),
+                        })
+                    }
+                    if let Some((project_entry_id, worktree_id, _)) = max_width_item {
+                        let mut visited_worktrees_length = 0;
+                        let index = new_state
+                            .visible_entries
                             .iter()
-                            .position(|entry| entry.id == project_entry_id)
-                    } else {
-                        visited_worktrees_length += visible_entries.entries.len();
-                        None
+                            .find_map(|visible_entries| {
+                                if worktree_id == visible_entries.worktree_id {
+                                    visible_entries
+                                        .entries
+                                        .iter()
+                                        .position(|entry| entry.id == project_entry_id)
+                                } else {
+                                    visited_worktrees_length += visible_entries.entries.len();
+                                    None
+                                }
+                            });
+                        if let Some(index) = index {
+                            new_state.max_width_item_index = Some(visited_worktrees_length + index);
+                        }
                     }
-                });
-            if let Some(index) = index {
-                self.state.max_width_item_index = Some(visited_worktrees_length + index);
-            }
-        }
-        if let Some((worktree_id, entry_id)) = new_selected_entry {
-            self.state.selection = Some(SelectedEntry {
-                worktree_id,
-                entry_id,
-            });
-        }
-        let elapsed = now.elapsed();
-        if self.last_reported_update.elapsed() > Duration::from_secs(3600) {
-            telemetry::event!(
-                "Project Panel Updated",
-                elapsed_ms = elapsed.as_millis() as u64,
-                worktree_entries = self
-                    .state
-                    .visible_entries
-                    .iter()
-                    .map(|worktree| worktree.entries.len())
-                    .sum::<usize>(),
-            )
-        }
+                    if let Some((worktree_id, entry_id)) = new_selected_entry {
+                        new_state.selection = Some(SelectedEntry {
+                            worktree_id,
+                            entry_id,
+                        });
+                    }
+                    new_state
+                })
+                .await;
+            this.update(cx, |this, _| {
+                this.state = new_state;
+                let elapsed = now.elapsed();
+                if this.last_reported_update.elapsed() > Duration::from_secs(3600) {
+                    telemetry::event!(
+                        "Project Panel Updated",
+                        elapsed_ms = elapsed.as_millis() as u64,
+                        worktree_entries = this
+                            .state
+                            .visible_entries
+                            .iter()
+                            .map(|worktree| worktree.entries.len())
+                            .sum::<usize>(),
+                    )
+                }
+            })
+            .ok();
+        });
     }
 
     fn expand_entry(
@@ -3296,7 +3332,7 @@ impl ProjectPanel {
         self.project.update(cx, |project, cx| {
             if let Some((worktree, expanded_dir_ids)) = project
                 .worktree_for_id(worktree_id, cx)
-                .zip(self.expanded_dir_ids.get_mut(&worktree_id))
+                .zip(self.state.expanded_dir_ids.get_mut(&worktree_id))
             {
                 project.expand_entry(worktree_id, entry_id, cx);
                 let worktree = worktree.read(cx);
@@ -4308,6 +4344,7 @@ impl ProjectPanel {
 
                         if !kind.is_dir()
                             || this
+                                .state
                                 .expanded_dir_ids
                                 .get(&details.worktree_id)
                                 .is_some_and(|ids| ids.binary_search(&entry_id).is_ok())
@@ -4765,6 +4802,7 @@ impl ProjectPanel {
         };
 
         let expanded_entry_ids = self
+            .state
             .expanded_dir_ids
             .get(&worktree_id)
             .map(Vec::as_slice)
@@ -4921,6 +4959,7 @@ impl ProjectPanel {
         loop {
             let is_expanded_dir = entry.is_dir()
                 && self
+                    .state
                     .expanded_dir_ids
                     .get(&worktree.id())
                     .map(|ids| ids.binary_search(&entry.id).is_ok())