project panel: Improve performance in worktrees with lots of files. (#12980)

Piotr Osiewicz created

When working on a repro for a different issue that involved a worktree
with lots of files (100k to be precise), UI became pretty unresponsive.
I pinned it down to us repeatedly preparing a HashSet of all paths in
the currently-scrolled-to worktree, once per each entry in the range
passed to for_each_visible_range (which is e.g. called during
rendering).

This PR makes that hashing happen just once per worktree. Additionally,
we no longer iterate over (potentially) all entries in a given worktree
when calculating the depth of a given entry.

Note that we could probably be smarter about this still; instead of
recalculating the hashset per each call to for_each_visible_entry, we
could do it whenever we update entries in the project panel. However,
with this PR I wanted to get a quick bang for a small buck; I'm pretty
confident in the change as is, it is relatively straightforward and
messing with worktree updates is more involved.



Release Notes:

- Improvement performance of project panel in large worktrees

Change summary

crates/project_panel/src/project_panel.rs | 29 ++++++++----------------
1 file changed, 10 insertions(+), 19 deletions(-)

Detailed changes

crates/project_panel/src/project_panel.rs 🔗

@@ -1792,6 +1792,10 @@ impl ProjectPanel {
                     .unwrap_or(&[]);
 
                 let entry_range = range.start.saturating_sub(ix)..end_ix - ix;
+                let entries = visible_worktree_entries
+                    .iter()
+                    .map(|e| (e.path.clone()))
+                    .collect();
                 for entry in visible_worktree_entries[entry_range].iter() {
                     let status = git_status_setting.then(|| entry.git_status).flatten();
                     let is_expanded = expanded_entry_ids.binary_search(&entry.id).is_ok();
@@ -1812,10 +1816,8 @@ impl ProjectPanel {
                         }
                     };
 
-                    let (depth, difference) = ProjectPanel::calculate_depth_and_difference(
-                        entry,
-                        visible_worktree_entries,
-                    );
+                    let (depth, difference) =
+                        ProjectPanel::calculate_depth_and_difference(entry, &entries);
 
                     let filename = match difference {
                         diff if diff > 1 => entry
@@ -1888,32 +1890,21 @@ impl ProjectPanel {
 
     fn calculate_depth_and_difference(
         entry: &Entry,
-        visible_worktree_entries: &Vec<Entry>,
+        visible_worktree_entries: &HashSet<Arc<Path>>,
     ) -> (usize, usize) {
-        let visible_worktree_paths: HashSet<Arc<Path>> = visible_worktree_entries
-            .iter()
-            .map(|e| e.path.clone())
-            .collect();
-
         let (depth, difference) = entry
             .path
             .ancestors()
             .skip(1) // Skip the entry itself
             .find_map(|ancestor| {
-                if visible_worktree_paths.contains(ancestor) {
-                    let parent_entry = visible_worktree_entries
-                        .iter()
-                        .find(|&e| &*e.path == ancestor)
-                        .unwrap();
-
+                if let Some(parent_entry) = visible_worktree_entries.get(ancestor) {
                     let entry_path_components_count = entry.path.components().count();
-                    let parent_path_components_count = parent_entry.path.components().count();
+                    let parent_path_components_count = parent_entry.components().count();
                     let difference = entry_path_components_count - parent_path_components_count;
                     let depth = parent_entry
-                        .path
                         .ancestors()
                         .skip(1)
-                        .filter(|ancestor| visible_worktree_paths.contains(*ancestor))
+                        .filter(|ancestor| visible_worktree_entries.contains(*ancestor))
                         .count();
                     Some((depth + 1, difference))
                 } else {