diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index 4e72f2d689..811098540f 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -2759,6 +2759,10 @@ impl EntryKind { ) } + pub fn is_unloaded(&self) -> bool { + matches!(self, EntryKind::UnloadedDir) + } + pub fn is_file(&self) -> bool { matches!(self, EntryKind::File(_)) } @@ -3773,6 +3777,7 @@ impl BackgroundScanner { let mut changes = Vec::new(); let mut old_paths = old_snapshot.entries_by_path.cursor::(); let mut new_paths = new_snapshot.entries_by_path.cursor::(); + let mut last_newly_loaded_dir_path = None; old_paths.next(&()); new_paths.next(&()); for path in event_paths { @@ -3820,20 +3825,33 @@ impl BackgroundScanner { changes.push((old_entry.path.clone(), old_entry.id, Removed)); changes.push((new_entry.path.clone(), new_entry.id, Added)); } else if old_entry != new_entry { - changes.push((new_entry.path.clone(), new_entry.id, Updated)); + if old_entry.kind.is_unloaded() { + last_newly_loaded_dir_path = Some(&new_entry.path); + changes.push(( + new_entry.path.clone(), + new_entry.id, + Loaded, + )); + } else { + changes.push(( + new_entry.path.clone(), + new_entry.id, + Updated, + )); + } } old_paths.next(&()); new_paths.next(&()); } Ordering::Greater => { + let is_newly_loaded = self.phase == InitialScan + || last_newly_loaded_dir_path + .as_ref() + .map_or(false, |dir| new_entry.path.starts_with(&dir)); changes.push(( new_entry.path.clone(), new_entry.id, - if self.phase == InitialScan { - Loaded - } else { - Added - }, + if is_newly_loaded { Loaded } else { Added }, )); new_paths.next(&()); } diff --git a/crates/project/src/worktree_tests.rs b/crates/project/src/worktree_tests.rs index 0a228c6830..a539e138bf 100644 --- a/crates/project/src/worktree_tests.rs +++ b/crates/project/src/worktree_tests.rs @@ -15,6 +15,7 @@ use serde_json::json; use std::{ env, fmt::Write, + mem, path::{Path, PathBuf}, sync::Arc, }; @@ -313,6 +314,21 @@ async fn test_symlinks_pointing_outside(cx: &mut TestAppContext) { cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete()) .await; + let tree_updates = Arc::new(Mutex::new(Vec::new())); + tree.update(cx, |_, cx| { + let tree_updates = tree_updates.clone(); + cx.subscribe(&tree, move |_, _, event, _| { + if let Event::UpdatedEntries(update) = event { + tree_updates.lock().extend( + update + .iter() + .map(|(path, _, change)| (path.clone(), *change)), + ); + } + }) + .detach(); + }); + // The symlinked directories are not scanned by default. tree.read_with(cx, |tree, _| { assert_eq!( @@ -365,6 +381,14 @@ async fn test_symlinks_pointing_outside(cx: &mut TestAppContext) { ] ); }); + assert_eq!( + mem::take(&mut *tree_updates.lock()), + &[ + (Path::new("deps/dep-dir3").into(), PathChange::Loaded), + (Path::new("deps/dep-dir3/deps").into(), PathChange::Loaded), + (Path::new("deps/dep-dir3/src").into(), PathChange::Loaded) + ] + ); // Expand a subdirectory of one of the symlinked directories. tree.read_with(cx, |tree, _| { @@ -396,6 +420,21 @@ async fn test_symlinks_pointing_outside(cx: &mut TestAppContext) { ] ); }); + + assert_eq!( + mem::take(&mut *tree_updates.lock()), + &[ + (Path::new("deps/dep-dir3/src").into(), PathChange::Loaded), + ( + Path::new("deps/dep-dir3/src/e.rs").into(), + PathChange::Loaded + ), + ( + Path::new("deps/dep-dir3/src/f.rs").into(), + PathChange::Loaded + ) + ] + ); } #[gpui::test] @@ -1114,7 +1153,6 @@ fn check_worktree_change_events(tree: &mut Worktree, cx: &mut ModelContext ix, }; match change_type { - PathChange::Loaded => entries.insert(ix, entry.unwrap()), PathChange::Added => entries.insert(ix, entry.unwrap()), PathChange::Removed => drop(entries.remove(ix)), PathChange::Updated => { @@ -1123,7 +1161,7 @@ fn check_worktree_change_events(tree: &mut Worktree, cx: &mut ModelContext { + PathChange::AddedOrUpdated | PathChange::Loaded => { let entry = entry.unwrap(); if entries.get(ix).map(|e| &e.path) == Some(&entry.path) { *entries.get_mut(ix).unwrap() = entry;