WIP: Add status bubbling to project panel

Mikayla Maki created

Change summary

crates/fs/src/repository.rs               | 12 ++--
crates/project/src/worktree.rs            | 56 ++++++++++++++++--------
crates/project_panel/src/project_panel.rs | 12 ++++
3 files changed, 54 insertions(+), 26 deletions(-)

Detailed changes

crates/fs/src/repository.rs 🔗

@@ -25,7 +25,7 @@ pub trait GitRepository: Send {
 
     fn statuses(&self) -> Option<TreeMap<RepoPath, GitFileStatus>>;
 
-    fn status(&self, path: &RepoPath) -> Option<GitFileStatus>;
+    fn status(&self, path: &RepoPath) -> Result<Option<GitFileStatus>>;
 }
 
 impl std::fmt::Debug for dyn GitRepository {
@@ -92,9 +92,9 @@ impl GitRepository for LibGitRepository {
         Some(map)
     }
 
-    fn status(&self, path: &RepoPath) -> Option<GitFileStatus> {
-        let status = self.status_file(path).log_err()?;
-        read_status(status)
+    fn status(&self, path: &RepoPath) -> Result<Option<GitFileStatus>> {
+        let status = self.status_file(path)?;
+        Ok(read_status(status))
     }
 }
 
@@ -156,9 +156,9 @@ impl GitRepository for FakeGitRepository {
         Some(map)
     }
 
-    fn status(&self, path: &RepoPath) -> Option<GitFileStatus> {
+    fn status(&self, path: &RepoPath) -> Result<Option<GitFileStatus>> {
         let state = self.state.lock();
-        state.worktree_statuses.get(path).cloned()
+        Ok(state.worktree_statuses.get(path).cloned())
     }
 }
 

crates/project/src/worktree.rs 🔗

@@ -1670,11 +1670,14 @@ impl Snapshot {
         })
     }
 
-    pub fn statuses_for_paths(&self, paths: &[&Path]) -> Vec<Option<GitFileStatus>> {
+    pub fn statuses_for_paths<'a>(
+        &self,
+        paths: impl IntoIterator<Item = &'a Path>,
+    ) -> Vec<Option<GitFileStatus>> {
         let mut cursor = self
             .entries_by_path
             .cursor::<(TraversalProgress, GitStatuses)>();
-        let mut paths = paths.iter().peekable();
+        let mut paths = paths.into_iter().peekable();
         let mut path_stack = Vec::<(&Path, usize, GitStatuses)>::new();
         let mut result = Vec::new();
 
@@ -2040,11 +2043,15 @@ impl LocalSnapshot {
             let Ok(repo_path) = entry.path.strip_prefix(&work_directory.0) else {
                 continue;
             };
-            let git_file_status = repo_ptr.status(&RepoPath(repo_path.into()));
-            let status = git_file_status;
-            entry.git_status = status;
-            changes.push(entry.path.clone());
-            edits.push(Edit::Insert(entry));
+            let git_file_status = repo_ptr
+                .status(&RepoPath(repo_path.into()))
+                .log_err()
+                .flatten();
+            if entry.git_status != git_file_status {
+                entry.git_status = git_file_status;
+                changes.push(entry.path.clone());
+                edits.push(Edit::Insert(entry));
+            }
         }
 
         self.entries_by_path.edit(edits, &());
@@ -3068,11 +3075,16 @@ impl BackgroundScanner {
                 }
             } else {
                 child_entry.is_ignored = ignore_stack.is_abs_path_ignored(&child_abs_path, false);
-
-                if let Some((repo_path, repo)) = &repository {
-                    if let Ok(path) = child_path.strip_prefix(&repo_path.0) {
-                        child_entry.git_status =
-                            repo.repo_ptr.lock().status(&RepoPath(path.into()));
+                if !child_entry.is_ignored {
+                    if let Some((repo_path, repo)) = &repository {
+                        if let Ok(path) = child_path.strip_prefix(&repo_path.0) {
+                            child_entry.git_status = repo
+                                .repo_ptr
+                                .lock()
+                                .status(&RepoPath(path.into()))
+                                .log_err()
+                                .flatten();
+                        }
                     }
                 }
             }
@@ -3170,11 +3182,19 @@ impl BackgroundScanner {
                     );
                     fs_entry.is_ignored = ignore_stack.is_all();
 
-                    if !fs_entry.is_dir() {
-                        if let Some((work_dir, repo)) = state.snapshot.local_repo_for_path(&path) {
-                            if let Ok(path) = path.strip_prefix(work_dir.0) {
-                                fs_entry.git_status =
-                                    repo.repo_ptr.lock().status(&RepoPath(path.into()))
+                    if !fs_entry.is_ignored {
+                        if !fs_entry.is_dir() {
+                            if let Some((work_dir, repo)) =
+                                state.snapshot.local_repo_for_path(&path)
+                            {
+                                if let Ok(path) = path.strip_prefix(work_dir.0) {
+                                    fs_entry.git_status = repo
+                                        .repo_ptr
+                                        .lock()
+                                        .status(&RepoPath(path.into()))
+                                        .log_err()
+                                        .flatten()
+                                }
                             }
                         }
                     }
@@ -5345,7 +5365,7 @@ mod tests {
             let snapshot = tree.read_with(cx, |tree, _| tree.snapshot());
 
             assert_eq!(
-                snapshot.statuses_for_paths(&[
+                snapshot.statuses_for_paths([
                     Path::new(""),
                     Path::new("a"),
                     Path::new("a/b"),

crates/project_panel/src/project_panel.rs 🔗

@@ -1109,8 +1109,16 @@ impl ProjectPanel {
                     .unwrap_or(&[]);
 
                 let entry_range = range.start.saturating_sub(ix)..end_ix - ix;
-                for entry in visible_worktree_entries[entry_range].iter() {
-                    let status = git_status_setting.then(|| entry.git_status).flatten();
+                let statuses = worktree.read(cx).statuses_for_paths(
+                    visible_worktree_entries[entry_range.clone()]
+                        .iter()
+                        .map(|entry| entry.path.as_ref()),
+                );
+                for (entry, status) in visible_worktree_entries[entry_range]
+                    .iter()
+                    .zip(statuses.into_iter())
+                {
+                    let status = git_status_setting.then(|| status).flatten();
 
                     let mut details = EntryDetails {
                         filename: entry