collab: Fix project sharing between Windows and Unix (#23680)

Closes #14258

Windows user(host) sharing a project to a guest(using macOS), and host
follows guest:


https://github.com/user-attachments/assets/ba306b6b-23f7-48b1-8ba8-fdc5992d8f00

macOS user(host) sharing a project to a guest(using Windows), and host
follows guest:



https://github.com/user-attachments/assets/c5ee5e78-870d-49e5-907d-8565977a01ae

macOS user edits files in a windows project through collab:



https://github.com/user-attachments/assets/581057cf-e7df-4e56-a0ce-ced74339906a





Release Notes:

- N/A
This commit is contained in:
张小白 2025-02-11 08:12:01 +08:00 committed by GitHub
parent 929c5e76b4
commit c1f162abc6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 226 additions and 117 deletions

View file

@ -1,3 +1,4 @@
use ::proto::{FromProto, ToProto};
use anyhow::{anyhow, Context as _, Result};
use extension::ExtensionHostProxy;
use extension_host::headless_host::HeadlessExtensionStore;
@ -325,10 +326,8 @@ impl HeadlessProject {
mut cx: AsyncApp,
) -> Result<proto::AddWorktreeResponse> {
use client::ErrorCodeExt;
let path = shellexpand::tilde(&message.payload.path).to_string();
let fs = this.read_with(&mut cx, |this, _| this.fs.clone())?;
let path = PathBuf::from(path);
let path = PathBuf::from_proto(shellexpand::tilde(&message.payload.path).to_string());
let canonicalized = match fs.canonicalize(&path).await {
Ok(path) => path,
@ -363,7 +362,7 @@ impl HeadlessProject {
let response = this.update(&mut cx, |_, cx| {
worktree.update(cx, |worktree, _| proto::AddWorktreeResponse {
worktree_id: worktree.id().to_proto(),
canonicalized_path: canonicalized.to_string_lossy().to_string(),
canonicalized_path: canonicalized.to_proto(),
})
})?;
@ -418,7 +417,7 @@ impl HeadlessProject {
buffer_store.open_buffer(
ProjectPath {
worktree_id,
path: PathBuf::from(message.payload.path).into(),
path: Arc::<Path>::from_proto(message.payload.path),
},
cx,
)
@ -559,11 +558,11 @@ impl HeadlessProject {
envelope: TypedEnvelope<proto::ListRemoteDirectory>,
cx: AsyncApp,
) -> Result<proto::ListRemoteDirectoryResponse> {
let expanded = shellexpand::tilde(&envelope.payload.path).to_string();
let fs = cx.read_entity(&this, |this, _| this.fs.clone())?;
let expanded = PathBuf::from_proto(shellexpand::tilde(&envelope.payload.path).to_string());
let mut entries = Vec::new();
let mut response = fs.read_dir(Path::new(&expanded)).await?;
let mut response = fs.read_dir(&expanded).await?;
while let Some(path) = response.next().await {
if let Some(file_name) = path?.file_name() {
entries.push(file_name.to_string_lossy().to_string());
@ -578,15 +577,15 @@ impl HeadlessProject {
cx: AsyncApp,
) -> Result<proto::GetPathMetadataResponse> {
let fs = cx.read_entity(&this, |this, _| this.fs.clone())?;
let expanded = shellexpand::tilde(&envelope.payload.path).to_string();
let expanded = PathBuf::from_proto(shellexpand::tilde(&envelope.payload.path).to_string());
let metadata = fs.metadata(&PathBuf::from(expanded.clone())).await?;
let metadata = fs.metadata(&expanded).await?;
let is_dir = metadata.map(|metadata| metadata.is_dir).unwrap_or(false);
Ok(proto::GetPathMetadataResponse {
exists: metadata.is_some(),
is_dir,
path: expanded,
path: expanded.to_proto(),
})
}

View file

@ -859,7 +859,7 @@ async fn test_remote_resolve_path_in_buffer(
async fn test_remote_resolve_abs_path(cx: &mut TestAppContext, server_cx: &mut TestAppContext) {
let fs = FakeFs::new(server_cx.executor());
fs.insert_tree(
"/code",
path!("/code"),
json!({
"project1": {
".git": {},
@ -876,7 +876,7 @@ async fn test_remote_resolve_abs_path(cx: &mut TestAppContext, server_cx: &mut T
let path = project
.update(cx, |project, cx| {
project.resolve_abs_path("/code/project1/README.md", cx)
project.resolve_abs_path(path!("/code/project1/README.md"), cx)
})
.await
.unwrap();
@ -884,12 +884,12 @@ async fn test_remote_resolve_abs_path(cx: &mut TestAppContext, server_cx: &mut T
assert!(path.is_file());
assert_eq!(
path.abs_path().unwrap().to_string_lossy(),
"/code/project1/README.md"
path!("/code/project1/README.md")
);
let path = project
.update(cx, |project, cx| {
project.resolve_abs_path("/code/project1/src", cx)
project.resolve_abs_path(path!("/code/project1/src"), cx)
})
.await
.unwrap();
@ -897,12 +897,12 @@ async fn test_remote_resolve_abs_path(cx: &mut TestAppContext, server_cx: &mut T
assert!(path.is_dir());
assert_eq!(
path.abs_path().unwrap().to_string_lossy(),
"/code/project1/src"
path!("/code/project1/src")
);
let path = project
.update(cx, |project, cx| {
project.resolve_abs_path("/code/project1/DOESNOTEXIST", cx)
project.resolve_abs_path(path!("/code/project1/DOESNOTEXIST"), cx)
})
.await;
assert!(path.is_none());
@ -958,7 +958,7 @@ async fn test_adding_then_removing_then_adding_worktrees(
) {
let fs = FakeFs::new(server_cx.executor());
fs.insert_tree(
"/code",
path!("/code"),
json!({
"project1": {
".git": {},
@ -977,14 +977,14 @@ async fn test_adding_then_removing_then_adding_worktrees(
let (project, _headless) = init_test(&fs, cx, server_cx).await;
let (_worktree, _) = project
.update(cx, |project, cx| {
project.find_or_create_worktree("/code/project1", true, cx)
project.find_or_create_worktree(path!("/code/project1"), true, cx)
})
.await
.unwrap();
let (worktree_2, _) = project
.update(cx, |project, cx| {
project.find_or_create_worktree("/code/project2", true, cx)
project.find_or_create_worktree(path!("/code/project2"), true, cx)
})
.await
.unwrap();
@ -994,7 +994,7 @@ async fn test_adding_then_removing_then_adding_worktrees(
let (worktree_2, _) = project
.update(cx, |project, cx| {
project.find_or_create_worktree("/code/project2", true, cx)
project.find_or_create_worktree(path!("/code/project2"), true, cx)
})
.await
.unwrap();