diff --git a/crates/project/src/fs.rs b/crates/project/src/fs.rs index 2eec02d66d..a92516e3b9 100644 --- a/crates/project/src/fs.rs +++ b/crates/project/src/fs.rs @@ -15,7 +15,12 @@ use text::Rope; pub trait Fs: Send + Sync { async fn create_dir(&self, path: &Path) -> Result<()>; async fn create_file(&self, path: &Path, options: CreateOptions) -> Result<()>; - async fn copy(&self, source: &Path, target: &Path, options: CopyOptions) -> Result<()>; + async fn copy( + &self, + source: &Path, + target: &Path, + options: CopyOptions, + ) -> Result>; async fn rename(&self, source: &Path, target: &Path, options: RenameOptions) -> Result<()>; async fn remove_dir(&self, path: &Path, options: RemoveOptions) -> Result<()>; async fn remove_file(&self, path: &Path, options: RemoveOptions) -> Result<()>; @@ -91,15 +96,21 @@ impl Fs for RealFs { Ok(()) } - async fn copy(&self, source: &Path, target: &Path, options: CopyOptions) -> Result<()> { + async fn copy( + &self, + source: &Path, + target: &Path, + options: CopyOptions, + ) -> Result> { if !options.overwrite && smol::fs::metadata(target).await.is_ok() { if options.ignore_if_exists { - return Ok(()); + return Ok(Default::default()); } else { return Err(anyhow!("{target:?} already exists")); } } + let mut paths = vec![target.to_path_buf()]; let metadata = smol::fs::metadata(source).await?; let _ = smol::fs::remove_dir_all(target).await; if metadata.is_dir() { @@ -109,15 +120,17 @@ impl Fs for RealFs { if let Ok(child) = child { let child_source_path = child.path(); let child_target_path = target.join(child.file_name()); - self.copy(&child_source_path, &child_target_path, options) - .await?; + paths.extend( + self.copy(&child_source_path, &child_target_path, options) + .await?, + ); } } } else { smol::fs::copy(source, target).await?; } - Ok(()) + Ok(paths) } async fn rename(&self, source: &Path, target: &Path, options: RenameOptions) -> Result<()> { @@ -547,7 +560,12 @@ impl Fs for FakeFs { Ok(()) } - async fn copy(&self, source: &Path, target: &Path, options: CopyOptions) -> Result<()> { + async fn copy( + &self, + source: &Path, + target: &Path, + options: CopyOptions, + ) -> Result> { let source = normalize_path(source); let target = normalize_path(target); @@ -557,7 +575,7 @@ impl Fs for FakeFs { if !options.overwrite && state.entries.contains_key(&target) { if options.ignore_if_exists { - return Ok(()); + return Ok(Default::default()); } else { return Err(anyhow!("{target:?} already exists")); } @@ -570,15 +588,15 @@ impl Fs for FakeFs { } } - let mut events = Vec::new(); + let mut paths = Vec::new(); for (relative_path, entry) in new_entries { let new_path = normalize_path(&target.join(relative_path)); - events.push(new_path.clone()); + paths.push(new_path.clone()); state.entries.insert(new_path, entry); } - state.emit_event(&events).await; - Ok(()) + state.emit_event(&paths).await; + Ok(paths) } async fn remove_dir(&self, dir_path: &Path, options: RemoveOptions) -> Result<()> { diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 923c188ffc..5346a86965 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -784,7 +784,7 @@ impl Project { entry_id: ProjectEntryId, new_path: impl Into>, cx: &mut ModelContext, - ) -> Option>> { + ) -> Option)>>> { let worktree = self.worktree_for_entry(entry_id, cx)?; let new_path = new_path.into(); if self.is_local() { @@ -809,15 +809,24 @@ impl Project { let entry = response .entry .ok_or_else(|| anyhow!("missing entry in response"))?; - worktree - .update(&mut cx, |worktree, cx| { - worktree.as_remote().unwrap().insert_entry( + let (entry, child_entries) = worktree.update(&mut cx, |worktree, cx| { + let worktree = worktree.as_remote().unwrap(); + let root_entry = + worktree.insert_entry(entry, response.worktree_scan_id as usize, cx); + let mut child_entries = Vec::new(); + for entry in response.child_entries { + child_entries.push(worktree.insert_entry( entry, response.worktree_scan_id as usize, cx, - ) - }) - .await + )); + } + (root_entry, child_entries) + }); + Ok(( + entry.await?, + futures::future::try_join_all(child_entries).await?, + )) })) } } @@ -4039,6 +4048,7 @@ impl Project { .await?; Ok(proto::ProjectEntryResponse { entry: Some((&entry).into()), + child_entries: Default::default(), worktree_scan_id: worktree_scan_id as u64, }) } @@ -4067,6 +4077,7 @@ impl Project { .await?; Ok(proto::ProjectEntryResponse { entry: Some((&entry).into()), + child_entries: Default::default(), worktree_scan_id: worktree_scan_id as u64, }) } @@ -4083,7 +4094,7 @@ impl Project { .ok_or_else(|| anyhow!("worktree not found")) })?; let worktree_scan_id = worktree.read_with(&cx, |worktree, _| worktree.scan_id()); - let entry = worktree + let (entry, child_entries) = worktree .update(&mut cx, |worktree, cx| { let new_path = PathBuf::from(OsString::from_vec(envelope.payload.new_path)); worktree @@ -4095,6 +4106,7 @@ impl Project { .await?; Ok(proto::ProjectEntryResponse { entry: Some((&entry).into()), + child_entries: child_entries.iter().map(Into::into).collect(), worktree_scan_id: worktree_scan_id as u64, }) } @@ -4122,6 +4134,7 @@ impl Project { .await?; Ok(proto::ProjectEntryResponse { entry: None, + child_entries: Default::default(), worktree_scan_id: worktree_scan_id as u64, }) } diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index 8eef61f213..a4e36da749 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -779,7 +779,7 @@ impl LocalWorktree { entry_id: ProjectEntryId, new_path: impl Into>, cx: &mut ModelContext, - ) -> Option>> { + ) -> Option)>>> { let old_path = self.entry_for_id(entry_id)?.path.clone(); let new_path = new_path.into(); let abs_old_path = self.absolutize(&old_path); @@ -794,23 +794,38 @@ impl LocalWorktree { }); Some(cx.spawn(|this, mut cx| async move { - copy.await?; - let entry = this - .update(&mut cx, |this, cx| { - this.as_local_mut().unwrap().refresh_entry( - new_path.clone(), - abs_new_path, - None, - cx, - ) - }) - .await?; + let copied_paths = copy.await?; + let (entry, child_entries) = this.update(&mut cx, |this, cx| { + let this = this.as_local_mut().unwrap(); + let root_entry = + this.refresh_entry(new_path.clone(), abs_new_path.clone(), None, cx); + + let mut child_entries = Vec::new(); + for copied_path in copied_paths { + if copied_path != abs_new_path { + let relative_copied_path = copied_path.strip_prefix(this.abs_path())?; + child_entries.push(this.refresh_entry( + relative_copied_path.into(), + copied_path, + None, + cx, + )); + } + } + + anyhow::Ok((root_entry, child_entries)) + })?; + let (entry, child_entries) = ( + entry.await?, + futures::future::try_join_all(child_entries).await?, + ); + this.update(&mut cx, |this, cx| { this.poll_snapshot(cx); this.as_local().unwrap().broadcast_snapshot() }) .await; - Ok(entry) + Ok((entry, child_entries)) })) } @@ -1202,8 +1217,23 @@ impl Snapshot { } fn delete_entry(&mut self, entry_id: ProjectEntryId) -> bool { - if let Some(entry) = self.entries_by_id.remove(&entry_id, &()) { - self.entries_by_path.remove(&PathKey(entry.path), &()); + if let Some(removed_entry) = self.entries_by_id.remove(&entry_id, &()) { + self.entries_by_path = { + let mut cursor = self.entries_by_path.cursor(); + let mut new_entries_by_path = + cursor.slice(&TraversalTarget::Path(&removed_entry.path), Bias::Left, &()); + while let Some(entry) = cursor.item() { + if entry.path.starts_with(&removed_entry.path) { + self.entries_by_id.remove(&entry.id, &()); + cursor.next(&()); + } else { + break; + } + } + new_entries_by_path.push_tree(cursor.suffix(&()), &()); + new_entries_by_path + }; + true } else { false diff --git a/crates/rpc/proto/zed.proto b/crates/rpc/proto/zed.proto index 2f6ed1f318..e72cce6b6d 100644 --- a/crates/rpc/proto/zed.proto +++ b/crates/rpc/proto/zed.proto @@ -224,7 +224,8 @@ message DeleteProjectEntry { message ProjectEntryResponse { Entry entry = 1; - uint64 worktree_scan_id = 2; + repeated Entry child_entries = 2; + uint64 worktree_scan_id = 3; } message AddProjectCollaborator {