Implement dragging external files to remote projects (#28987)
Release Notes: - Added the ability to copy external files into remote projects by dragging them onto the project panel. --------- Co-authored-by: Peter Tripp <petertripp@gmail.com>
This commit is contained in:
parent
fade49a11a
commit
7e928dd615
8 changed files with 275 additions and 82 deletions
|
@ -7,7 +7,7 @@ use ::ignore::gitignore::{Gitignore, GitignoreBuilder};
|
|||
use anyhow::{Context as _, Result, anyhow};
|
||||
use clock::ReplicaId;
|
||||
use collections::{HashMap, HashSet, VecDeque};
|
||||
use fs::{Fs, MTime, PathEvent, RemoveOptions, Watcher, copy_recursive};
|
||||
use fs::{Fs, MTime, PathEvent, RemoveOptions, Watcher, copy_recursive, read_dir_items};
|
||||
use futures::{
|
||||
FutureExt as _, Stream, StreamExt,
|
||||
channel::{
|
||||
|
@ -847,18 +847,20 @@ impl Worktree {
|
|||
&mut self,
|
||||
path: impl Into<Arc<Path>>,
|
||||
is_directory: bool,
|
||||
content: Option<Vec<u8>>,
|
||||
cx: &Context<Worktree>,
|
||||
) -> Task<Result<CreatedEntry>> {
|
||||
let path: Arc<Path> = path.into();
|
||||
let worktree_id = self.id();
|
||||
match self {
|
||||
Worktree::Local(this) => this.create_entry(path, is_directory, cx),
|
||||
Worktree::Local(this) => this.create_entry(path, is_directory, content, cx),
|
||||
Worktree::Remote(this) => {
|
||||
let project_id = this.project_id;
|
||||
let request = this.client.request(proto::CreateProjectEntry {
|
||||
worktree_id: worktree_id.to_proto(),
|
||||
project_id,
|
||||
path: path.as_ref().to_proto(),
|
||||
content,
|
||||
is_directory,
|
||||
});
|
||||
cx.spawn(async move |this, cx| {
|
||||
|
@ -979,18 +981,14 @@ impl Worktree {
|
|||
|
||||
pub fn copy_external_entries(
|
||||
&mut self,
|
||||
target_directory: PathBuf,
|
||||
target_directory: Arc<Path>,
|
||||
paths: Vec<Arc<Path>>,
|
||||
overwrite_existing_files: bool,
|
||||
fs: Arc<dyn Fs>,
|
||||
cx: &Context<Worktree>,
|
||||
) -> Task<Result<Vec<ProjectEntryId>>> {
|
||||
match self {
|
||||
Worktree::Local(this) => {
|
||||
this.copy_external_entries(target_directory, paths, overwrite_existing_files, cx)
|
||||
}
|
||||
_ => Task::ready(Err(anyhow!(
|
||||
"Copying external entries is not supported for remote worktrees"
|
||||
))),
|
||||
Worktree::Local(this) => this.copy_external_entries(target_directory, paths, cx),
|
||||
Worktree::Remote(this) => this.copy_external_entries(target_directory, paths, fs, cx),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1057,6 +1055,7 @@ impl Worktree {
|
|||
this.create_entry(
|
||||
Arc::<Path>::from_proto(request.path),
|
||||
request.is_directory,
|
||||
request.content,
|
||||
cx,
|
||||
),
|
||||
)
|
||||
|
@ -1585,6 +1584,7 @@ impl LocalWorktree {
|
|||
&self,
|
||||
path: impl Into<Arc<Path>>,
|
||||
is_dir: bool,
|
||||
content: Option<Vec<u8>>,
|
||||
cx: &Context<Worktree>,
|
||||
) -> Task<Result<CreatedEntry>> {
|
||||
let path = path.into();
|
||||
|
@ -1601,7 +1601,7 @@ impl LocalWorktree {
|
|||
.await
|
||||
.with_context(|| format!("creating directory {task_abs_path:?}"))
|
||||
} else {
|
||||
fs.save(&task_abs_path, &Rope::default(), LineEnding::default())
|
||||
fs.write(&task_abs_path, content.as_deref().unwrap_or(&[]))
|
||||
.await
|
||||
.with_context(|| format!("creating file {task_abs_path:?}"))
|
||||
}
|
||||
|
@ -1877,11 +1877,13 @@ impl LocalWorktree {
|
|||
|
||||
pub fn copy_external_entries(
|
||||
&self,
|
||||
target_directory: PathBuf,
|
||||
target_directory: Arc<Path>,
|
||||
paths: Vec<Arc<Path>>,
|
||||
overwrite_existing_files: bool,
|
||||
cx: &Context<Worktree>,
|
||||
) -> Task<Result<Vec<ProjectEntryId>>> {
|
||||
let Ok(target_directory) = self.absolutize(&target_directory) else {
|
||||
return Task::ready(Err(anyhow!("invalid target path")));
|
||||
};
|
||||
let worktree_path = self.abs_path().clone();
|
||||
let fs = self.fs.clone();
|
||||
let paths = paths
|
||||
|
@ -1913,7 +1915,7 @@ impl LocalWorktree {
|
|||
&source,
|
||||
&target,
|
||||
fs::CopyOptions {
|
||||
overwrite: overwrite_existing_files,
|
||||
overwrite: true,
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
|
@ -2283,6 +2285,62 @@ impl RemoteWorktree {
|
|||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn copy_external_entries(
|
||||
&self,
|
||||
target_directory: Arc<Path>,
|
||||
paths_to_copy: Vec<Arc<Path>>,
|
||||
local_fs: Arc<dyn Fs>,
|
||||
cx: &Context<Worktree>,
|
||||
) -> Task<Result<Vec<ProjectEntryId>, anyhow::Error>> {
|
||||
let client = self.client.clone();
|
||||
let worktree_id = self.id().to_proto();
|
||||
let project_id = self.project_id;
|
||||
|
||||
cx.background_spawn(async move {
|
||||
let mut requests = Vec::new();
|
||||
for root_path_to_copy in paths_to_copy {
|
||||
let Some(filename) = root_path_to_copy.file_name() else {
|
||||
continue;
|
||||
};
|
||||
for (abs_path, is_directory) in
|
||||
read_dir_items(local_fs.as_ref(), &root_path_to_copy).await?
|
||||
{
|
||||
let Ok(relative_path) = abs_path.strip_prefix(&root_path_to_copy) else {
|
||||
continue;
|
||||
};
|
||||
let content = if is_directory {
|
||||
None
|
||||
} else {
|
||||
Some(local_fs.load_bytes(&abs_path).await?)
|
||||
};
|
||||
|
||||
let mut target_path = target_directory.join(filename);
|
||||
if relative_path.file_name().is_some() {
|
||||
target_path.push(relative_path)
|
||||
}
|
||||
|
||||
requests.push(proto::CreateProjectEntry {
|
||||
project_id,
|
||||
worktree_id,
|
||||
path: target_path.to_string_lossy().to_string(),
|
||||
is_directory,
|
||||
content,
|
||||
});
|
||||
}
|
||||
}
|
||||
requests.sort_unstable_by(|a, b| a.path.cmp(&b.path));
|
||||
requests.dedup();
|
||||
|
||||
let mut copied_entry_ids = Vec::new();
|
||||
for request in requests {
|
||||
let response = client.request(request).await?;
|
||||
copied_entry_ids.extend(response.entry.map(|e| ProjectEntryId::from_proto(e.id)));
|
||||
}
|
||||
|
||||
Ok(copied_entry_ids)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Snapshot {
|
||||
|
|
|
@ -1270,7 +1270,7 @@ async fn test_create_directory_during_initial_scan(cx: &mut TestAppContext) {
|
|||
.update(cx, |tree, cx| {
|
||||
tree.as_local_mut()
|
||||
.unwrap()
|
||||
.create_entry("a/e".as_ref(), true, cx)
|
||||
.create_entry("a/e".as_ref(), true, None, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
|
@ -1319,7 +1319,7 @@ async fn test_create_dir_all_on_create_entry(cx: &mut TestAppContext) {
|
|||
.update(cx, |tree, cx| {
|
||||
tree.as_local_mut()
|
||||
.unwrap()
|
||||
.create_entry("a/b/c/d.txt".as_ref(), false, cx)
|
||||
.create_entry("a/b/c/d.txt".as_ref(), false, None, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
|
@ -1353,7 +1353,7 @@ async fn test_create_dir_all_on_create_entry(cx: &mut TestAppContext) {
|
|||
.update(cx, |tree, cx| {
|
||||
tree.as_local_mut()
|
||||
.unwrap()
|
||||
.create_entry("a/b/c/d.txt".as_ref(), false, cx)
|
||||
.create_entry("a/b/c/d.txt".as_ref(), false, None, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
|
@ -1373,7 +1373,7 @@ async fn test_create_dir_all_on_create_entry(cx: &mut TestAppContext) {
|
|||
.update(cx, |tree, cx| {
|
||||
tree.as_local_mut()
|
||||
.unwrap()
|
||||
.create_entry("a/b/c/e.txt".as_ref(), false, cx)
|
||||
.create_entry("a/b/c/e.txt".as_ref(), false, None, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
|
@ -1391,7 +1391,7 @@ async fn test_create_dir_all_on_create_entry(cx: &mut TestAppContext) {
|
|||
.update(cx, |tree, cx| {
|
||||
tree.as_local_mut()
|
||||
.unwrap()
|
||||
.create_entry("d/e/f/g.txt".as_ref(), false, cx)
|
||||
.create_entry("d/e/f/g.txt".as_ref(), false, None, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
|
@ -1739,7 +1739,7 @@ fn randomly_mutate_worktree(
|
|||
if is_dir { "dir" } else { "file" },
|
||||
child_path,
|
||||
);
|
||||
let task = worktree.create_entry(child_path, is_dir, cx);
|
||||
let task = worktree.create_entry(child_path, is_dir, None, cx);
|
||||
cx.background_spawn(async move {
|
||||
task.await?;
|
||||
Ok(())
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue