mirror of
https://github.com/zed-industries/zed.git
synced 2024-11-08 07:35:01 +03:00
Handle buffer diff base updates and file renames properly for SSH projects (#14989)
Release Notes: - N/A --------- Co-authored-by: Conrad <conrad@zed.dev>
This commit is contained in:
parent
ec093c390f
commit
38e3182bef
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -8771,6 +8771,7 @@ dependencies = [
|
|||||||
"settings",
|
"settings",
|
||||||
"smol",
|
"smol",
|
||||||
"toml 0.8.10",
|
"toml 0.8.10",
|
||||||
|
"util",
|
||||||
"worktree",
|
"worktree",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -2867,7 +2867,7 @@ fn make_lsp_adapter_delegate(
|
|||||||
project.update(cx, |project, cx| {
|
project.update(cx, |project, cx| {
|
||||||
// TODO: Find the right worktree.
|
// TODO: Find the right worktree.
|
||||||
let worktree = project
|
let worktree = project
|
||||||
.worktrees()
|
.worktrees(cx)
|
||||||
.next()
|
.next()
|
||||||
.ok_or_else(|| anyhow!("no worktrees when constructing ProjectLspAdapterDelegate"))?;
|
.ok_or_else(|| anyhow!("no worktrees when constructing ProjectLspAdapterDelegate"))?;
|
||||||
Ok(ProjectLspAdapterDelegate::new(project, &worktree, cx) as Arc<dyn LspAdapterDelegate>)
|
Ok(ProjectLspAdapterDelegate::new(project, &worktree, cx) as Arc<dyn LspAdapterDelegate>)
|
||||||
|
@ -284,7 +284,7 @@ fn collect_diagnostics(
|
|||||||
PathBuf::try_from(path)
|
PathBuf::try_from(path)
|
||||||
.ok()
|
.ok()
|
||||||
.and_then(|path| {
|
.and_then(|path| {
|
||||||
project.read(cx).worktrees().find_map(|worktree| {
|
project.read(cx).worktrees(cx).find_map(|worktree| {
|
||||||
let worktree = worktree.read(cx);
|
let worktree = worktree.read(cx);
|
||||||
let worktree_root_path = Path::new(worktree.root_name());
|
let worktree_root_path = Path::new(worktree.root_name());
|
||||||
let relative_path = path.strip_prefix(worktree_root_path).ok()?;
|
let relative_path = path.strip_prefix(worktree_root_path).ok()?;
|
||||||
|
@ -24,7 +24,7 @@ impl DocsSlashCommand {
|
|||||||
pub const NAME: &'static str = "docs";
|
pub const NAME: &'static str = "docs";
|
||||||
|
|
||||||
fn path_to_cargo_toml(project: Model<Project>, cx: &mut AppContext) -> Option<Arc<Path>> {
|
fn path_to_cargo_toml(project: Model<Project>, cx: &mut AppContext) -> Option<Arc<Path>> {
|
||||||
let worktree = project.read(cx).worktrees().next()?;
|
let worktree = project.read(cx).worktrees(cx).next()?;
|
||||||
let worktree = worktree.read(cx);
|
let worktree = worktree.read(cx);
|
||||||
let entry = worktree.entry_for_path("Cargo.toml")?;
|
let entry = worktree.entry_for_path("Cargo.toml")?;
|
||||||
let path = ProjectPath {
|
let path = ProjectPath {
|
||||||
|
@ -188,7 +188,7 @@ fn collect_files(
|
|||||||
let project_handle = project.downgrade();
|
let project_handle = project.downgrade();
|
||||||
let snapshots = project
|
let snapshots = project
|
||||||
.read(cx)
|
.read(cx)
|
||||||
.worktrees()
|
.worktrees(cx)
|
||||||
.map(|worktree| worktree.read(cx).snapshot())
|
.map(|worktree| worktree.read(cx).snapshot())
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
cx.spawn(|mut cx| async move {
|
cx.spawn(|mut cx| async move {
|
||||||
|
@ -75,7 +75,7 @@ impl ProjectSlashCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn path_to_cargo_toml(project: Model<Project>, cx: &mut AppContext) -> Option<Arc<Path>> {
|
fn path_to_cargo_toml(project: Model<Project>, cx: &mut AppContext) -> Option<Arc<Path>> {
|
||||||
let worktree = project.read(cx).worktrees().next()?;
|
let worktree = project.read(cx).worktrees(cx).next()?;
|
||||||
let worktree = worktree.read(cx);
|
let worktree = worktree.read(cx);
|
||||||
let entry = worktree.entry_for_path("Cargo.toml")?;
|
let entry = worktree.entry_for_path("Cargo.toml")?;
|
||||||
let path = ProjectPath {
|
let path = ProjectPath {
|
||||||
|
@ -222,7 +222,7 @@ mod tests {
|
|||||||
|
|
||||||
let worktree_ids = project.read_with(cx, |project, cx| {
|
let worktree_ids = project.read_with(cx, |project, cx| {
|
||||||
project
|
project
|
||||||
.worktrees()
|
.worktrees(cx)
|
||||||
.map(|worktree| worktree.read(cx).id())
|
.map(|worktree| worktree.read(cx).id())
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
});
|
});
|
||||||
|
@ -526,7 +526,7 @@ impl Room {
|
|||||||
rejoined_projects.push(proto::RejoinProject {
|
rejoined_projects.push(proto::RejoinProject {
|
||||||
id: project_id,
|
id: project_id,
|
||||||
worktrees: project
|
worktrees: project
|
||||||
.worktrees()
|
.worktrees(cx)
|
||||||
.map(|worktree| {
|
.map(|worktree| {
|
||||||
let worktree = worktree.read(cx);
|
let worktree = worktree.read(cx);
|
||||||
proto::RejoinWorktree {
|
proto::RejoinWorktree {
|
||||||
|
@ -52,7 +52,7 @@ async fn test_channel_guests(
|
|||||||
assert!(project_b.read_with(cx_b, |project, _| project.is_read_only()));
|
assert!(project_b.read_with(cx_b, |project, _| project.is_read_only()));
|
||||||
assert!(project_b
|
assert!(project_b
|
||||||
.update(cx_b, |project, cx| {
|
.update(cx_b, |project, cx| {
|
||||||
let worktree_id = project.worktrees().next().unwrap().read(cx).id();
|
let worktree_id = project.worktrees(cx).next().unwrap().read(cx).id();
|
||||||
project.create_entry((worktree_id, "b.txt"), false, cx)
|
project.create_entry((worktree_id, "b.txt"), false, cx)
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
|
@ -76,7 +76,7 @@ async fn test_host_disconnect(
|
|||||||
let active_call_a = cx_a.read(ActiveCall::global);
|
let active_call_a = cx_a.read(ActiveCall::global);
|
||||||
let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
|
let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
|
||||||
|
|
||||||
let worktree_a = project_a.read_with(cx_a, |project, _| project.worktrees().next().unwrap());
|
let worktree_a = project_a.read_with(cx_a, |project, cx| project.worktrees(cx).next().unwrap());
|
||||||
let project_id = active_call_a
|
let project_id = active_call_a
|
||||||
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
|
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
|
||||||
.await
|
.await
|
||||||
@ -1144,7 +1144,7 @@ async fn test_share_project(
|
|||||||
});
|
});
|
||||||
|
|
||||||
project_b.read_with(cx_b, |project, cx| {
|
project_b.read_with(cx_b, |project, cx| {
|
||||||
let worktree = project.worktrees().next().unwrap().read(cx);
|
let worktree = project.worktrees(cx).next().unwrap().read(cx);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
worktree.paths().map(AsRef::as_ref).collect::<Vec<_>>(),
|
worktree.paths().map(AsRef::as_ref).collect::<Vec<_>>(),
|
||||||
[
|
[
|
||||||
@ -1158,7 +1158,7 @@ async fn test_share_project(
|
|||||||
|
|
||||||
project_b
|
project_b
|
||||||
.update(cx_b, |project, cx| {
|
.update(cx_b, |project, cx| {
|
||||||
let worktree = project.worktrees().next().unwrap();
|
let worktree = project.worktrees(cx).next().unwrap();
|
||||||
let entry = worktree.read(cx).entry_for_path("ignored-dir").unwrap();
|
let entry = worktree.read(cx).entry_for_path("ignored-dir").unwrap();
|
||||||
project.expand_entry(worktree_id, entry.id, cx).unwrap()
|
project.expand_entry(worktree_id, entry.id, cx).unwrap()
|
||||||
})
|
})
|
||||||
@ -1166,7 +1166,7 @@ async fn test_share_project(
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
project_b.read_with(cx_b, |project, cx| {
|
project_b.read_with(cx_b, |project, cx| {
|
||||||
let worktree = project.worktrees().next().unwrap().read(cx);
|
let worktree = project.worktrees(cx).next().unwrap().read(cx);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
worktree.paths().map(AsRef::as_ref).collect::<Vec<_>>(),
|
worktree.paths().map(AsRef::as_ref).collect::<Vec<_>>(),
|
||||||
[
|
[
|
||||||
|
@ -1377,7 +1377,7 @@ async fn test_unshare_project(
|
|||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let worktree_a = project_a.read_with(cx_a, |project, _| project.worktrees().next().unwrap());
|
let worktree_a = project_a.read_with(cx_a, |project, cx| project.worktrees(cx).next().unwrap());
|
||||||
let project_b = client_b.build_dev_server_project(project_id, cx_b).await;
|
let project_b = client_b.build_dev_server_project(project_id, cx_b).await;
|
||||||
executor.run_until_parked();
|
executor.run_until_parked();
|
||||||
|
|
||||||
@ -1505,7 +1505,8 @@ async fn test_project_reconnect(
|
|||||||
let (project_a1, _) = client_a.build_local_project("/root-1/dir1", cx_a).await;
|
let (project_a1, _) = client_a.build_local_project("/root-1/dir1", cx_a).await;
|
||||||
let (project_a2, _) = client_a.build_local_project("/root-2", cx_a).await;
|
let (project_a2, _) = client_a.build_local_project("/root-2", cx_a).await;
|
||||||
let (project_a3, _) = client_a.build_local_project("/root-3", cx_a).await;
|
let (project_a3, _) = client_a.build_local_project("/root-3", cx_a).await;
|
||||||
let worktree_a1 = project_a1.read_with(cx_a, |project, _| project.worktrees().next().unwrap());
|
let worktree_a1 =
|
||||||
|
project_a1.read_with(cx_a, |project, cx| project.worktrees(cx).next().unwrap());
|
||||||
let project1_id = active_call_a
|
let project1_id = active_call_a
|
||||||
.update(cx_a, |call, cx| call.share_project(project_a1.clone(), cx))
|
.update(cx_a, |call, cx| call.share_project(project_a1.clone(), cx))
|
||||||
.await
|
.await
|
||||||
@ -2308,7 +2309,7 @@ async fn test_propagate_saves_and_fs_changes(
|
|||||||
.await;
|
.await;
|
||||||
let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
|
let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
|
||||||
|
|
||||||
let worktree_a = project_a.read_with(cx_a, |p, _| p.worktrees().next().unwrap());
|
let worktree_a = project_a.read_with(cx_a, |p, cx| p.worktrees(cx).next().unwrap());
|
||||||
let project_id = active_call_a
|
let project_id = active_call_a
|
||||||
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
|
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
|
||||||
.await
|
.await
|
||||||
@ -2318,9 +2319,9 @@ async fn test_propagate_saves_and_fs_changes(
|
|||||||
let project_b = client_b.build_dev_server_project(project_id, cx_b).await;
|
let project_b = client_b.build_dev_server_project(project_id, cx_b).await;
|
||||||
let project_c = client_c.build_dev_server_project(project_id, cx_c).await;
|
let project_c = client_c.build_dev_server_project(project_id, cx_c).await;
|
||||||
|
|
||||||
let worktree_b = project_b.read_with(cx_b, |p, _| p.worktrees().next().unwrap());
|
let worktree_b = project_b.read_with(cx_b, |p, cx| p.worktrees(cx).next().unwrap());
|
||||||
|
|
||||||
let worktree_c = project_c.read_with(cx_c, |p, _| p.worktrees().next().unwrap());
|
let worktree_c = project_c.read_with(cx_c, |p, cx| p.worktrees(cx).next().unwrap());
|
||||||
|
|
||||||
// Open and edit a buffer as both guests B and C.
|
// Open and edit a buffer as both guests B and C.
|
||||||
let buffer_b = project_b
|
let buffer_b = project_b
|
||||||
@ -3022,8 +3023,8 @@ async fn test_fs_operations(
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
let project_b = client_b.build_dev_server_project(project_id, cx_b).await;
|
let project_b = client_b.build_dev_server_project(project_id, cx_b).await;
|
||||||
|
|
||||||
let worktree_a = project_a.read_with(cx_a, |project, _| project.worktrees().next().unwrap());
|
let worktree_a = project_a.read_with(cx_a, |project, cx| project.worktrees(cx).next().unwrap());
|
||||||
let worktree_b = project_b.read_with(cx_b, |project, _| project.worktrees().next().unwrap());
|
let worktree_b = project_b.read_with(cx_b, |project, cx| project.worktrees(cx).next().unwrap());
|
||||||
|
|
||||||
let entry = project_b
|
let entry = project_b
|
||||||
.update(cx_b, |project, cx| {
|
.update(cx_b, |project, cx| {
|
||||||
@ -3323,7 +3324,7 @@ async fn test_local_settings(
|
|||||||
// As client B, join that project and observe the local settings.
|
// As client B, join that project and observe the local settings.
|
||||||
let project_b = client_b.build_dev_server_project(project_id, cx_b).await;
|
let project_b = client_b.build_dev_server_project(project_id, cx_b).await;
|
||||||
|
|
||||||
let worktree_b = project_b.read_with(cx_b, |project, _| project.worktrees().next().unwrap());
|
let worktree_b = project_b.read_with(cx_b, |project, cx| project.worktrees(cx).next().unwrap());
|
||||||
executor.run_until_parked();
|
executor.run_until_parked();
|
||||||
cx_b.read(|cx| {
|
cx_b.read(|cx| {
|
||||||
let store = cx.global::<SettingsStore>();
|
let store = cx.global::<SettingsStore>();
|
||||||
@ -3735,7 +3736,7 @@ async fn test_leaving_project(
|
|||||||
// Client B opens a buffer.
|
// Client B opens a buffer.
|
||||||
let buffer_b1 = project_b1
|
let buffer_b1 = project_b1
|
||||||
.update(cx_b, |project, cx| {
|
.update(cx_b, |project, cx| {
|
||||||
let worktree_id = project.worktrees().next().unwrap().read(cx).id();
|
let worktree_id = project.worktrees(cx).next().unwrap().read(cx).id();
|
||||||
project.open_buffer((worktree_id, "a.txt"), cx)
|
project.open_buffer((worktree_id, "a.txt"), cx)
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
@ -3773,7 +3774,7 @@ async fn test_leaving_project(
|
|||||||
|
|
||||||
let buffer_b2 = project_b2
|
let buffer_b2 = project_b2
|
||||||
.update(cx_b, |project, cx| {
|
.update(cx_b, |project, cx| {
|
||||||
let worktree_id = project.worktrees().next().unwrap().read(cx).id();
|
let worktree_id = project.worktrees(cx).next().unwrap().read(cx).id();
|
||||||
project.open_buffer((worktree_id, "a.txt"), cx)
|
project.open_buffer((worktree_id, "a.txt"), cx)
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
@ -4627,7 +4628,7 @@ async fn test_definition(
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
cx_b.read(|cx| {
|
cx_b.read(|cx| {
|
||||||
assert_eq!(definitions_1.len(), 1);
|
assert_eq!(definitions_1.len(), 1);
|
||||||
assert_eq!(project_b.read(cx).worktrees().count(), 2);
|
assert_eq!(project_b.read(cx).worktrees(cx).count(), 2);
|
||||||
let target_buffer = definitions_1[0].target.buffer.read(cx);
|
let target_buffer = definitions_1[0].target.buffer.read(cx);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
target_buffer.text(),
|
target_buffer.text(),
|
||||||
@ -4656,7 +4657,7 @@ async fn test_definition(
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
cx_b.read(|cx| {
|
cx_b.read(|cx| {
|
||||||
assert_eq!(definitions_2.len(), 1);
|
assert_eq!(definitions_2.len(), 1);
|
||||||
assert_eq!(project_b.read(cx).worktrees().count(), 2);
|
assert_eq!(project_b.read(cx).worktrees(cx).count(), 2);
|
||||||
let target_buffer = definitions_2[0].target.buffer.read(cx);
|
let target_buffer = definitions_2[0].target.buffer.read(cx);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
target_buffer.text(),
|
target_buffer.text(),
|
||||||
@ -4814,7 +4815,7 @@ async fn test_references(
|
|||||||
assert!(status.pending_work.is_empty());
|
assert!(status.pending_work.is_empty());
|
||||||
|
|
||||||
assert_eq!(references.len(), 3);
|
assert_eq!(references.len(), 3);
|
||||||
assert_eq!(project.worktrees().count(), 2);
|
assert_eq!(project.worktrees(cx).count(), 2);
|
||||||
|
|
||||||
let two_buffer = references[0].buffer.read(cx);
|
let two_buffer = references[0].buffer.read(cx);
|
||||||
let three_buffer = references[2].buffer.read(cx);
|
let three_buffer = references[2].buffer.read(cx);
|
||||||
@ -6199,7 +6200,7 @@ async fn test_preview_tabs(cx: &mut TestAppContext) {
|
|||||||
let project = workspace.update(cx, |workspace, _| workspace.project().clone());
|
let project = workspace.update(cx, |workspace, _| workspace.project().clone());
|
||||||
|
|
||||||
let worktree_id = project.update(cx, |project, cx| {
|
let worktree_id = project.update(cx, |project, cx| {
|
||||||
project.worktrees().next().unwrap().read(cx).id()
|
project.worktrees(cx).next().unwrap().read(cx).id()
|
||||||
});
|
});
|
||||||
|
|
||||||
let path_1 = ProjectPath {
|
let path_1 = ProjectPath {
|
||||||
|
@ -301,7 +301,7 @@ impl RandomizedTest for ProjectCollaborationTest {
|
|||||||
let is_local = project.read_with(cx, |project, _| project.is_local());
|
let is_local = project.read_with(cx, |project, _| project.is_local());
|
||||||
let worktree = project.read_with(cx, |project, cx| {
|
let worktree = project.read_with(cx, |project, cx| {
|
||||||
project
|
project
|
||||||
.worktrees()
|
.worktrees(cx)
|
||||||
.filter(|worktree| {
|
.filter(|worktree| {
|
||||||
let worktree = worktree.read(cx);
|
let worktree = worktree.read(cx);
|
||||||
worktree.is_visible()
|
worktree.is_visible()
|
||||||
@ -423,7 +423,7 @@ impl RandomizedTest for ProjectCollaborationTest {
|
|||||||
81.. => {
|
81.. => {
|
||||||
let worktree = project.read_with(cx, |project, cx| {
|
let worktree = project.read_with(cx, |project, cx| {
|
||||||
project
|
project
|
||||||
.worktrees()
|
.worktrees(cx)
|
||||||
.filter(|worktree| {
|
.filter(|worktree| {
|
||||||
let worktree = worktree.read(cx);
|
let worktree = worktree.read(cx);
|
||||||
worktree.is_visible()
|
worktree.is_visible()
|
||||||
@ -1172,7 +1172,7 @@ impl RandomizedTest for ProjectCollaborationTest {
|
|||||||
let host_worktree_snapshots =
|
let host_worktree_snapshots =
|
||||||
host_project.read_with(host_cx, |host_project, cx| {
|
host_project.read_with(host_cx, |host_project, cx| {
|
||||||
host_project
|
host_project
|
||||||
.worktrees()
|
.worktrees(cx)
|
||||||
.map(|worktree| {
|
.map(|worktree| {
|
||||||
let worktree = worktree.read(cx);
|
let worktree = worktree.read(cx);
|
||||||
(worktree.id(), worktree.snapshot())
|
(worktree.id(), worktree.snapshot())
|
||||||
@ -1180,7 +1180,7 @@ impl RandomizedTest for ProjectCollaborationTest {
|
|||||||
.collect::<BTreeMap<_, _>>()
|
.collect::<BTreeMap<_, _>>()
|
||||||
});
|
});
|
||||||
let guest_worktree_snapshots = guest_project
|
let guest_worktree_snapshots = guest_project
|
||||||
.worktrees()
|
.worktrees(cx)
|
||||||
.map(|worktree| {
|
.map(|worktree| {
|
||||||
let worktree = worktree.read(cx);
|
let worktree = worktree.read(cx);
|
||||||
(worktree.id(), worktree.snapshot())
|
(worktree.id(), worktree.snapshot())
|
||||||
@ -1538,7 +1538,7 @@ fn project_path_for_full_path(
|
|||||||
let root_name = components.next().unwrap().as_os_str().to_str().unwrap();
|
let root_name = components.next().unwrap().as_os_str().to_str().unwrap();
|
||||||
let path = components.as_path().into();
|
let path = components.as_path().into();
|
||||||
let worktree_id = project.read_with(cx, |project, cx| {
|
let worktree_id = project.read_with(cx, |project, cx| {
|
||||||
project.worktrees().find_map(|worktree| {
|
project.worktrees(cx).find_map(|worktree| {
|
||||||
let worktree = worktree.read(cx);
|
let worktree = worktree.read(cx);
|
||||||
if worktree.root_name() == root_name {
|
if worktree.root_name() == root_name {
|
||||||
Some(worktree.id())
|
Some(worktree.id())
|
||||||
|
@ -6253,8 +6253,8 @@ async fn test_multibuffer_format_during_save(cx: &mut gpui::TestAppContext) {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
let worktree = project.update(cx, |project, _| {
|
let worktree = project.update(cx, |project, cx| {
|
||||||
let mut worktrees = project.worktrees().collect::<Vec<_>>();
|
let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
|
||||||
assert_eq!(worktrees.len(), 1);
|
assert_eq!(worktrees.len(), 1);
|
||||||
worktrees.pop().unwrap()
|
worktrees.pop().unwrap()
|
||||||
});
|
});
|
||||||
@ -9319,7 +9319,7 @@ async fn test_on_type_formatting_not_triggered(cx: &mut gpui::TestAppContext) {
|
|||||||
let worktree_id = workspace
|
let worktree_id = workspace
|
||||||
.update(cx, |workspace, cx| {
|
.update(cx, |workspace, cx| {
|
||||||
workspace.project().update(cx, |project, cx| {
|
workspace.project().update(cx, |project, cx| {
|
||||||
project.worktrees().next().unwrap().read(cx).id()
|
project.worktrees(cx).next().unwrap().read(cx).id()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -2581,7 +2581,7 @@ pub mod tests {
|
|||||||
);
|
);
|
||||||
|
|
||||||
let worktree_id = project.update(cx, |project, cx| {
|
let worktree_id = project.update(cx, |project, cx| {
|
||||||
project.worktrees().next().unwrap().read(cx).id()
|
project.worktrees(cx).next().unwrap().read(cx).id()
|
||||||
});
|
});
|
||||||
|
|
||||||
let buffer_1 = project
|
let buffer_1 = project
|
||||||
@ -2931,7 +2931,7 @@ pub mod tests {
|
|||||||
);
|
);
|
||||||
|
|
||||||
let worktree_id = project.update(cx, |project, cx| {
|
let worktree_id = project.update(cx, |project, cx| {
|
||||||
project.worktrees().next().unwrap().read(cx).id()
|
project.worktrees(cx).next().unwrap().read(cx).id()
|
||||||
});
|
});
|
||||||
|
|
||||||
let buffer_1 = project
|
let buffer_1 = project
|
||||||
|
@ -1496,7 +1496,7 @@ async fn test_search_results_refreshed_on_adding_and_removing_worktrees(
|
|||||||
let project = Project::test(app_state.fs.clone(), ["/test/project_1".as_ref()], cx).await;
|
let project = Project::test(app_state.fs.clone(), ["/test/project_1".as_ref()], cx).await;
|
||||||
let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
|
let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
|
||||||
let worktree_1_id = project.update(cx, |project, cx| {
|
let worktree_1_id = project.update(cx, |project, cx| {
|
||||||
let worktree = project.worktrees().last().expect("worktree not found");
|
let worktree = project.worktrees(cx).last().expect("worktree not found");
|
||||||
worktree.read(cx).id()
|
worktree.read(cx).id()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -32,7 +32,7 @@ impl Match {
|
|||||||
path_match.path.join(suffix),
|
path_match.path.join(suffix),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
(project.worktrees().next(), PathBuf::from(suffix))
|
(project.worktrees(cx).next(), PathBuf::from(suffix))
|
||||||
};
|
};
|
||||||
|
|
||||||
worktree.and_then(|worktree| worktree.read(cx).entry_for_path(path))
|
worktree.and_then(|worktree| worktree.read(cx).entry_for_path(path))
|
||||||
@ -72,7 +72,7 @@ impl Match {
|
|||||||
let worktree_id = if let Some(path_match) = &self.path_match {
|
let worktree_id = if let Some(path_match) = &self.path_match {
|
||||||
WorktreeId::from_usize(path_match.worktree_id)
|
WorktreeId::from_usize(path_match.worktree_id)
|
||||||
} else {
|
} else {
|
||||||
project.worktrees().next()?.read(cx).id()
|
project.worktrees(cx).next()?.read(cx).id()
|
||||||
};
|
};
|
||||||
|
|
||||||
let path = PathBuf::from(self.relative_path());
|
let path = PathBuf::from(self.relative_path());
|
||||||
@ -84,7 +84,7 @@ impl Match {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn existing_prefix(&self, project: &Project, cx: &WindowContext) -> Option<PathBuf> {
|
fn existing_prefix(&self, project: &Project, cx: &WindowContext) -> Option<PathBuf> {
|
||||||
let worktree = project.worktrees().next()?.read(cx);
|
let worktree = project.worktrees(cx).next()?.read(cx);
|
||||||
let mut prefix = PathBuf::new();
|
let mut prefix = PathBuf::new();
|
||||||
let parts = self.suffix.as_ref()?.split('/');
|
let parts = self.suffix.as_ref()?.split('/');
|
||||||
for part in parts {
|
for part in parts {
|
||||||
|
@ -75,6 +75,9 @@ impl RealGitRepository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// https://git-scm.com/book/en/v2/Git-Internals-Git-Objects
|
||||||
|
const GIT_MODE_SYMLINK: u32 = 0o120000;
|
||||||
|
|
||||||
impl GitRepository for RealGitRepository {
|
impl GitRepository for RealGitRepository {
|
||||||
fn reload_index(&self) {
|
fn reload_index(&self) {
|
||||||
if let Ok(mut index) = self.repository.lock().index() {
|
if let Ok(mut index) = self.repository.lock().index() {
|
||||||
@ -91,8 +94,8 @@ impl GitRepository for RealGitRepository {
|
|||||||
check_path_to_repo_path_errors(relative_file_path)?;
|
check_path_to_repo_path_errors(relative_file_path)?;
|
||||||
|
|
||||||
let oid = match index.get_path(relative_file_path, STAGE_NORMAL) {
|
let oid = match index.get_path(relative_file_path, STAGE_NORMAL) {
|
||||||
Some(entry) => entry.id,
|
Some(entry) if entry.mode != GIT_MODE_SYMLINK => entry.id,
|
||||||
None => return Ok(None),
|
_ => return Ok(None),
|
||||||
};
|
};
|
||||||
|
|
||||||
let content = repo.find_blob(oid)?.content().to_owned();
|
let content = repo.find_blob(oid)?.content().to_owned();
|
||||||
|
@ -258,7 +258,7 @@ mod tests {
|
|||||||
let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
|
let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
|
||||||
let worktree_id = workspace.update(cx, |workspace, cx| {
|
let worktree_id = workspace.update(cx, |workspace, cx| {
|
||||||
workspace.project().update(cx, |project, cx| {
|
workspace.project().update(cx, |project, cx| {
|
||||||
project.worktrees().next().unwrap().read(cx).id()
|
project.worktrees(cx).next().unwrap().read(cx).id()
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
let _buffer = project
|
let _buffer = project
|
||||||
|
@ -551,7 +551,7 @@ impl LspLogView {
|
|||||||
self.project
|
self.project
|
||||||
.read(cx)
|
.read(cx)
|
||||||
.supplementary_language_servers()
|
.supplementary_language_servers()
|
||||||
.filter_map(|(&server_id, (name, _))| {
|
.filter_map(|(&server_id, name)| {
|
||||||
let state = log_store.language_servers.get(&server_id)?;
|
let state = log_store.language_servers.get(&server_id)?;
|
||||||
Some(LogMenuItem {
|
Some(LogMenuItem {
|
||||||
server_id,
|
server_id,
|
||||||
|
@ -85,7 +85,7 @@ async fn test_lsp_logs(cx: &mut TestAppContext) {
|
|||||||
server_name: LanguageServerName("the-rust-language-server".into()),
|
server_name: LanguageServerName("the-rust-language-server".into()),
|
||||||
worktree_root_name: project
|
worktree_root_name: project
|
||||||
.read(cx)
|
.read(cx)
|
||||||
.worktrees()
|
.worktrees(cx)
|
||||||
.next()
|
.next()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.read(cx)
|
.read(cx)
|
||||||
|
@ -321,7 +321,7 @@ mod tests {
|
|||||||
let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
|
let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
|
||||||
let worktree_id = workspace.update(cx, |workspace, cx| {
|
let worktree_id = workspace.update(cx, |workspace, cx| {
|
||||||
workspace.project().update(cx, |project, cx| {
|
workspace.project().update(cx, |project, cx| {
|
||||||
project.worktrees().next().unwrap().read(cx).id()
|
project.worktrees(cx).next().unwrap().read(cx).id()
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
let _buffer = project
|
let _buffer = project
|
||||||
|
@ -1,13 +1,16 @@
|
|||||||
use crate::ProjectPath;
|
use crate::{
|
||||||
use anyhow::{anyhow, Context as _, Result};
|
worktree_store::{WorktreeStore, WorktreeStoreEvent},
|
||||||
|
ProjectPath,
|
||||||
|
};
|
||||||
|
use anyhow::{anyhow, Result};
|
||||||
use collections::{hash_map, HashMap};
|
use collections::{hash_map, HashMap};
|
||||||
use futures::{channel::oneshot, StreamExt as _};
|
use futures::{channel::oneshot, stream::FuturesUnordered, StreamExt as _};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
AppContext, AsyncAppContext, Context as _, EventEmitter, Model, ModelContext, Task, WeakModel,
|
AppContext, AsyncAppContext, Context as _, EventEmitter, Model, ModelContext, Task, WeakModel,
|
||||||
};
|
};
|
||||||
use language::{
|
use language::{
|
||||||
proto::{deserialize_version, serialize_version, split_operations},
|
proto::{deserialize_line_ending, deserialize_version, serialize_version, split_operations},
|
||||||
Buffer, Capability, Language, Operation,
|
Buffer, Capability, Event as BufferEvent, Language, Operation,
|
||||||
};
|
};
|
||||||
use rpc::{
|
use rpc::{
|
||||||
proto::{self, AnyProtoClient, PeerId},
|
proto::{self, AnyProtoClient, PeerId},
|
||||||
@ -16,11 +19,15 @@ use rpc::{
|
|||||||
use std::{io, path::Path, sync::Arc};
|
use std::{io, path::Path, sync::Arc};
|
||||||
use text::BufferId;
|
use text::BufferId;
|
||||||
use util::{debug_panic, maybe, ResultExt as _};
|
use util::{debug_panic, maybe, ResultExt as _};
|
||||||
use worktree::{File, ProjectEntryId, RemoteWorktree, Worktree};
|
use worktree::{
|
||||||
|
File, PathChange, ProjectEntryId, RemoteWorktree, UpdatedGitRepositoriesSet, Worktree,
|
||||||
|
};
|
||||||
|
|
||||||
/// A set of open buffers.
|
/// A set of open buffers.
|
||||||
pub struct BufferStore {
|
pub struct BufferStore {
|
||||||
retain_buffers: bool,
|
retain_buffers: bool,
|
||||||
|
#[allow(unused)]
|
||||||
|
worktree_store: Model<WorktreeStore>,
|
||||||
opened_buffers: HashMap<BufferId, OpenBuffer>,
|
opened_buffers: HashMap<BufferId, OpenBuffer>,
|
||||||
local_buffer_ids_by_path: HashMap<ProjectPath, BufferId>,
|
local_buffer_ids_by_path: HashMap<ProjectPath, BufferId>,
|
||||||
local_buffer_ids_by_entry_id: HashMap<ProjectEntryId, BufferId>,
|
local_buffer_ids_by_entry_id: HashMap<ProjectEntryId, BufferId>,
|
||||||
@ -51,6 +58,12 @@ pub enum BufferStoreEvent {
|
|||||||
has_changed_file: bool,
|
has_changed_file: bool,
|
||||||
saved_version: clock::Global,
|
saved_version: clock::Global,
|
||||||
},
|
},
|
||||||
|
LocalBufferUpdated {
|
||||||
|
buffer: Model<Buffer>,
|
||||||
|
},
|
||||||
|
DiffBaseUpdated {
|
||||||
|
buffer: Model<Buffer>,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EventEmitter<BufferStoreEvent> for BufferStore {}
|
impl EventEmitter<BufferStoreEvent> for BufferStore {}
|
||||||
@ -62,9 +75,22 @@ impl BufferStore {
|
|||||||
/// and won't be released unless they are explicitly removed, or `retain_buffers`
|
/// and won't be released unless they are explicitly removed, or `retain_buffers`
|
||||||
/// is set to `false` via `set_retain_buffers`. Otherwise, buffers are stored as
|
/// is set to `false` via `set_retain_buffers`. Otherwise, buffers are stored as
|
||||||
/// weak handles.
|
/// weak handles.
|
||||||
pub fn new(retain_buffers: bool) -> Self {
|
pub fn new(
|
||||||
|
worktree_store: Model<WorktreeStore>,
|
||||||
|
retain_buffers: bool,
|
||||||
|
cx: &mut ModelContext<Self>,
|
||||||
|
) -> Self {
|
||||||
|
cx.subscribe(&worktree_store, |this, _, event, cx| match event {
|
||||||
|
WorktreeStoreEvent::WorktreeAdded(worktree) => {
|
||||||
|
this.subscribe_to_worktree(worktree, cx);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
retain_buffers,
|
retain_buffers,
|
||||||
|
worktree_store,
|
||||||
opened_buffers: Default::default(),
|
opened_buffers: Default::default(),
|
||||||
remote_buffer_listeners: Default::default(),
|
remote_buffer_listeners: Default::default(),
|
||||||
loading_remote_buffers_by_id: Default::default(),
|
loading_remote_buffers_by_id: Default::default(),
|
||||||
@ -77,7 +103,6 @@ impl BufferStore {
|
|||||||
pub fn open_buffer(
|
pub fn open_buffer(
|
||||||
&mut self,
|
&mut self,
|
||||||
project_path: ProjectPath,
|
project_path: ProjectPath,
|
||||||
worktree: Model<Worktree>,
|
|
||||||
cx: &mut ModelContext<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) -> Task<Result<Model<Buffer>>> {
|
) -> Task<Result<Model<Buffer>>> {
|
||||||
let existing_buffer = self.get_by_path(&project_path, cx);
|
let existing_buffer = self.get_by_path(&project_path, cx);
|
||||||
@ -85,6 +110,14 @@ impl BufferStore {
|
|||||||
return Task::ready(Ok(existing_buffer));
|
return Task::ready(Ok(existing_buffer));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let Some(worktree) = self
|
||||||
|
.worktree_store
|
||||||
|
.read(cx)
|
||||||
|
.worktree_for_id(project_path.worktree_id, cx)
|
||||||
|
else {
|
||||||
|
return Task::ready(Err(anyhow!("no such worktree")));
|
||||||
|
};
|
||||||
|
|
||||||
let loading_watch = match self.loading_buffers_by_path.entry(project_path.clone()) {
|
let loading_watch = match self.loading_buffers_by_path.entry(project_path.clone()) {
|
||||||
// If the given path is already being loaded, then wait for that existing
|
// If the given path is already being loaded, then wait for that existing
|
||||||
// task to complete and return the same buffer.
|
// task to complete and return the same buffer.
|
||||||
@ -127,6 +160,131 @@ impl BufferStore {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn subscribe_to_worktree(&mut self, worktree: &Model<Worktree>, cx: &mut ModelContext<Self>) {
|
||||||
|
cx.subscribe(worktree, |this, worktree, event, cx| {
|
||||||
|
if worktree.read(cx).is_local() {
|
||||||
|
match event {
|
||||||
|
worktree::Event::UpdatedEntries(changes) => {
|
||||||
|
this.local_worktree_entries_changed(&worktree, changes, cx);
|
||||||
|
}
|
||||||
|
worktree::Event::UpdatedGitRepositories(updated_repos) => {
|
||||||
|
this.local_worktree_git_repos_changed(worktree.clone(), updated_repos, cx)
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn local_worktree_entries_changed(
|
||||||
|
&mut self,
|
||||||
|
worktree_handle: &Model<Worktree>,
|
||||||
|
changes: &[(Arc<Path>, ProjectEntryId, PathChange)],
|
||||||
|
cx: &mut ModelContext<Self>,
|
||||||
|
) {
|
||||||
|
let snapshot = worktree_handle.read(cx).snapshot();
|
||||||
|
for (path, entry_id, _) in changes {
|
||||||
|
self.local_worktree_entry_changed(*entry_id, path, worktree_handle, &snapshot, cx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn local_worktree_git_repos_changed(
|
||||||
|
&mut self,
|
||||||
|
worktree_handle: Model<Worktree>,
|
||||||
|
changed_repos: &UpdatedGitRepositoriesSet,
|
||||||
|
cx: &mut ModelContext<Self>,
|
||||||
|
) {
|
||||||
|
debug_assert!(worktree_handle.read(cx).is_local());
|
||||||
|
|
||||||
|
// Identify the loading buffers whose containing repository that has changed.
|
||||||
|
let future_buffers = self
|
||||||
|
.loading_buffers()
|
||||||
|
.filter_map(|(project_path, receiver)| {
|
||||||
|
if project_path.worktree_id != worktree_handle.read(cx).id() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let path = &project_path.path;
|
||||||
|
changed_repos
|
||||||
|
.iter()
|
||||||
|
.find(|(work_dir, _)| path.starts_with(work_dir))?;
|
||||||
|
let path = path.clone();
|
||||||
|
Some(async move {
|
||||||
|
Self::wait_for_loading_buffer(receiver)
|
||||||
|
.await
|
||||||
|
.ok()
|
||||||
|
.map(|buffer| (buffer, path))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect::<FuturesUnordered<_>>();
|
||||||
|
|
||||||
|
// Identify the current buffers whose containing repository has changed.
|
||||||
|
let current_buffers = self
|
||||||
|
.buffers()
|
||||||
|
.filter_map(|buffer| {
|
||||||
|
let file = File::from_dyn(buffer.read(cx).file())?;
|
||||||
|
if file.worktree != worktree_handle {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
changed_repos
|
||||||
|
.iter()
|
||||||
|
.find(|(work_dir, _)| file.path.starts_with(work_dir))?;
|
||||||
|
Some((buffer, file.path.clone()))
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
if future_buffers.len() + current_buffers.len() == 0 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
cx.spawn(move |this, mut cx| async move {
|
||||||
|
// Wait for all of the buffers to load.
|
||||||
|
let future_buffers = future_buffers.collect::<Vec<_>>().await;
|
||||||
|
|
||||||
|
// Reload the diff base for every buffer whose containing git repository has changed.
|
||||||
|
let snapshot =
|
||||||
|
worktree_handle.update(&mut cx, |tree, _| tree.as_local().unwrap().snapshot())?;
|
||||||
|
let diff_bases_by_buffer = cx
|
||||||
|
.background_executor()
|
||||||
|
.spawn(async move {
|
||||||
|
let mut diff_base_tasks = future_buffers
|
||||||
|
.into_iter()
|
||||||
|
.flatten()
|
||||||
|
.chain(current_buffers)
|
||||||
|
.filter_map(|(buffer, path)| {
|
||||||
|
let (repo_entry, local_repo_entry) = snapshot.repo_for_path(&path)?;
|
||||||
|
let relative_path = repo_entry.relativize(&snapshot, &path).ok()?;
|
||||||
|
Some(async move {
|
||||||
|
let base_text =
|
||||||
|
local_repo_entry.repo().load_index_text(&relative_path);
|
||||||
|
Some((buffer, base_text))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect::<FuturesUnordered<_>>();
|
||||||
|
|
||||||
|
let mut diff_bases = Vec::with_capacity(diff_base_tasks.len());
|
||||||
|
while let Some(diff_base) = diff_base_tasks.next().await {
|
||||||
|
if let Some(diff_base) = diff_base {
|
||||||
|
diff_bases.push(diff_base);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
diff_bases
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
|
||||||
|
this.update(&mut cx, |_, cx| {
|
||||||
|
// Assign the new diff bases on all of the buffers.
|
||||||
|
for (buffer, diff_base) in diff_bases_by_buffer {
|
||||||
|
buffer.update(cx, |buffer, cx| {
|
||||||
|
buffer.set_diff_base(diff_base.clone(), cx);
|
||||||
|
});
|
||||||
|
cx.emit(BufferStoreEvent::DiffBaseUpdated { buffer })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.detach_and_log_err(cx);
|
||||||
|
}
|
||||||
|
|
||||||
fn open_local_buffer_internal(
|
fn open_local_buffer_internal(
|
||||||
&mut self,
|
&mut self,
|
||||||
path: Arc<Path>,
|
path: Arc<Path>,
|
||||||
@ -265,9 +423,16 @@ impl BufferStore {
|
|||||||
&mut self,
|
&mut self,
|
||||||
buffer: Model<Buffer>,
|
buffer: Model<Buffer>,
|
||||||
path: ProjectPath,
|
path: ProjectPath,
|
||||||
worktree: Model<Worktree>,
|
|
||||||
cx: &mut ModelContext<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) -> Task<Result<()>> {
|
) -> Task<Result<()>> {
|
||||||
|
let Some(worktree) = self
|
||||||
|
.worktree_store
|
||||||
|
.read(cx)
|
||||||
|
.worktree_for_id(path.worktree_id, cx)
|
||||||
|
else {
|
||||||
|
return Task::ready(Err(anyhow!("no such worktree")));
|
||||||
|
};
|
||||||
|
|
||||||
let old_file = File::from_dyn(buffer.read(cx).file())
|
let old_file = File::from_dyn(buffer.read(cx).file())
|
||||||
.cloned()
|
.cloned()
|
||||||
.map(Arc::new);
|
.map(Arc::new);
|
||||||
@ -411,6 +576,7 @@ impl BufferStore {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cx.subscribe(&buffer, Self::on_buffer_event).detach();
|
||||||
cx.emit(BufferStoreEvent::BufferAdded(buffer));
|
cx.emit(BufferStoreEvent::BufferAdded(buffer));
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -461,31 +627,6 @@ impl BufferStore {
|
|||||||
.or_else(|| self.loading_remote_buffers_by_id.get(&buffer_id).cloned())
|
.or_else(|| self.loading_remote_buffers_by_id.get(&buffer_id).cloned())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_or_remove_by_path(
|
|
||||||
&mut self,
|
|
||||||
entry_id: ProjectEntryId,
|
|
||||||
project_path: &ProjectPath,
|
|
||||||
) -> Option<(BufferId, Model<Buffer>)> {
|
|
||||||
let buffer_id = match self.local_buffer_ids_by_entry_id.get(&entry_id) {
|
|
||||||
Some(&buffer_id) => buffer_id,
|
|
||||||
None => match self.local_buffer_ids_by_path.get(project_path) {
|
|
||||||
Some(&buffer_id) => buffer_id,
|
|
||||||
None => {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
let buffer = if let Some(buffer) = self.get(buffer_id) {
|
|
||||||
buffer
|
|
||||||
} else {
|
|
||||||
self.opened_buffers.remove(&buffer_id);
|
|
||||||
self.local_buffer_ids_by_path.remove(project_path);
|
|
||||||
self.local_buffer_ids_by_entry_id.remove(&entry_id);
|
|
||||||
return None;
|
|
||||||
};
|
|
||||||
Some((buffer_id, buffer))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn wait_for_remote_buffer(
|
pub fn wait_for_remote_buffer(
|
||||||
&mut self,
|
&mut self,
|
||||||
id: BufferId,
|
id: BufferId,
|
||||||
@ -561,25 +702,48 @@ impl BufferStore {
|
|||||||
.retain(|_, buffer| !matches!(buffer, OpenBuffer::Operations(_)));
|
.retain(|_, buffer| !matches!(buffer, OpenBuffer::Operations(_)));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn file_changed(
|
fn on_buffer_event(
|
||||||
|
&mut self,
|
||||||
|
buffer: Model<Buffer>,
|
||||||
|
event: &BufferEvent,
|
||||||
|
cx: &mut ModelContext<Self>,
|
||||||
|
) {
|
||||||
|
match event {
|
||||||
|
BufferEvent::FileHandleChanged => {
|
||||||
|
self.buffer_changed_file(buffer, cx);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn local_worktree_entry_changed(
|
||||||
&mut self,
|
&mut self,
|
||||||
path: Arc<Path>,
|
|
||||||
entry_id: ProjectEntryId,
|
entry_id: ProjectEntryId,
|
||||||
worktree_handle: &Model<worktree::Worktree>,
|
path: &Arc<Path>,
|
||||||
|
worktree: &Model<worktree::Worktree>,
|
||||||
snapshot: &worktree::Snapshot,
|
snapshot: &worktree::Snapshot,
|
||||||
cx: &mut ModelContext<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) -> Option<(Model<Buffer>, Arc<File>, Arc<File>)> {
|
) -> Option<()> {
|
||||||
let (buffer_id, buffer) = self.get_or_remove_by_path(
|
let project_path = ProjectPath {
|
||||||
entry_id,
|
|
||||||
&ProjectPath {
|
|
||||||
worktree_id: snapshot.id(),
|
worktree_id: snapshot.id(),
|
||||||
path,
|
path: path.clone(),
|
||||||
},
|
};
|
||||||
)?;
|
let buffer_id = match self.local_buffer_ids_by_entry_id.get(&entry_id) {
|
||||||
|
Some(&buffer_id) => buffer_id,
|
||||||
|
None => self.local_buffer_ids_by_path.get(&project_path).copied()?,
|
||||||
|
};
|
||||||
|
let buffer = if let Some(buffer) = self.get(buffer_id) {
|
||||||
|
buffer
|
||||||
|
} else {
|
||||||
|
self.opened_buffers.remove(&buffer_id);
|
||||||
|
self.local_buffer_ids_by_path.remove(&project_path);
|
||||||
|
self.local_buffer_ids_by_entry_id.remove(&entry_id);
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
|
||||||
let result = buffer.update(cx, |buffer, cx| {
|
let (old_file, new_file) = buffer.update(cx, |buffer, cx| {
|
||||||
let old_file = File::from_dyn(buffer.file())?;
|
let old_file = File::from_dyn(buffer.file())?;
|
||||||
if old_file.worktree != *worktree_handle {
|
if old_file.worktree != *worktree {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -592,7 +756,7 @@ impl BufferStore {
|
|||||||
entry_id: Some(entry.id),
|
entry_id: Some(entry.id),
|
||||||
mtime: entry.mtime,
|
mtime: entry.mtime,
|
||||||
path: entry.path.clone(),
|
path: entry.path.clone(),
|
||||||
worktree: worktree_handle.clone(),
|
worktree: worktree.clone(),
|
||||||
is_deleted: false,
|
is_deleted: false,
|
||||||
is_private: entry.is_private,
|
is_private: entry.is_private,
|
||||||
}
|
}
|
||||||
@ -602,7 +766,7 @@ impl BufferStore {
|
|||||||
entry_id: Some(entry.id),
|
entry_id: Some(entry.id),
|
||||||
mtime: entry.mtime,
|
mtime: entry.mtime,
|
||||||
path: entry.path.clone(),
|
path: entry.path.clone(),
|
||||||
worktree: worktree_handle.clone(),
|
worktree: worktree.clone(),
|
||||||
is_deleted: false,
|
is_deleted: false,
|
||||||
is_private: entry.is_private,
|
is_private: entry.is_private,
|
||||||
}
|
}
|
||||||
@ -612,7 +776,7 @@ impl BufferStore {
|
|||||||
entry_id: old_file.entry_id,
|
entry_id: old_file.entry_id,
|
||||||
path: old_file.path.clone(),
|
path: old_file.path.clone(),
|
||||||
mtime: old_file.mtime,
|
mtime: old_file.mtime,
|
||||||
worktree: worktree_handle.clone(),
|
worktree: worktree.clone(),
|
||||||
is_deleted: true,
|
is_deleted: true,
|
||||||
is_private: old_file.is_private,
|
is_private: old_file.is_private,
|
||||||
}
|
}
|
||||||
@ -625,10 +789,9 @@ impl BufferStore {
|
|||||||
let old_file = Arc::new(old_file.clone());
|
let old_file = Arc::new(old_file.clone());
|
||||||
let new_file = Arc::new(new_file);
|
let new_file = Arc::new(new_file);
|
||||||
buffer.file_updated(new_file.clone(), cx);
|
buffer.file_updated(new_file.clone(), cx);
|
||||||
Some((cx.handle(), old_file, new_file))
|
Some((old_file, new_file))
|
||||||
});
|
})?;
|
||||||
|
|
||||||
if let Some((buffer, old_file, new_file)) = &result {
|
|
||||||
if new_file.path != old_file.path {
|
if new_file.path != old_file.path {
|
||||||
self.local_buffer_ids_by_path.remove(&ProjectPath {
|
self.local_buffer_ids_by_path.remove(&ProjectPath {
|
||||||
path: old_file.path.clone(),
|
path: old_file.path.clone(),
|
||||||
@ -656,16 +819,12 @@ impl BufferStore {
|
|||||||
.insert(entry_id, buffer_id);
|
.insert(entry_id, buffer_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cx.emit(BufferStoreEvent::LocalBufferUpdated { buffer });
|
||||||
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
result
|
fn buffer_changed_file(&mut self, buffer: Model<Buffer>, cx: &mut AppContext) -> Option<()> {
|
||||||
}
|
|
||||||
|
|
||||||
pub fn buffer_changed_file(
|
|
||||||
&mut self,
|
|
||||||
buffer: Model<Buffer>,
|
|
||||||
cx: &mut AppContext,
|
|
||||||
) -> Option<()> {
|
|
||||||
let file = File::from_dyn(buffer.read(cx).file())?;
|
let file = File::from_dyn(buffer.read(cx).file())?;
|
||||||
|
|
||||||
let remote_id = buffer.read(cx).remote_id();
|
let remote_id = buffer.read(cx).remote_id();
|
||||||
@ -862,7 +1021,6 @@ impl BufferStore {
|
|||||||
pub async fn handle_save_buffer(
|
pub async fn handle_save_buffer(
|
||||||
this: Model<Self>,
|
this: Model<Self>,
|
||||||
project_id: u64,
|
project_id: u64,
|
||||||
worktree: Option<Model<Worktree>>,
|
|
||||||
envelope: TypedEnvelope<proto::SaveBuffer>,
|
envelope: TypedEnvelope<proto::SaveBuffer>,
|
||||||
mut cx: AsyncAppContext,
|
mut cx: AsyncAppContext,
|
||||||
) -> Result<proto::BufferSaved> {
|
) -> Result<proto::BufferSaved> {
|
||||||
@ -876,10 +1034,9 @@ impl BufferStore {
|
|||||||
let buffer_id = buffer.update(&mut cx, |buffer, _| buffer.remote_id())?;
|
let buffer_id = buffer.update(&mut cx, |buffer, _| buffer.remote_id())?;
|
||||||
|
|
||||||
if let Some(new_path) = envelope.payload.new_path {
|
if let Some(new_path) = envelope.payload.new_path {
|
||||||
let worktree = worktree.context("no such worktree")?;
|
|
||||||
let new_path = ProjectPath::from_proto(new_path);
|
let new_path = ProjectPath::from_proto(new_path);
|
||||||
this.update(&mut cx, |this, cx| {
|
this.update(&mut cx, |this, cx| {
|
||||||
this.save_buffer_as(buffer.clone(), new_path, worktree, cx)
|
this.save_buffer_as(buffer.clone(), new_path, cx)
|
||||||
})?
|
})?
|
||||||
.await?;
|
.await?;
|
||||||
} else {
|
} else {
|
||||||
@ -895,6 +1052,44 @@ impl BufferStore {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn handle_buffer_saved(
|
||||||
|
this: Model<Self>,
|
||||||
|
envelope: TypedEnvelope<proto::BufferSaved>,
|
||||||
|
mut cx: AsyncAppContext,
|
||||||
|
) -> Result<()> {
|
||||||
|
let buffer_id = BufferId::new(envelope.payload.buffer_id)?;
|
||||||
|
let version = deserialize_version(&envelope.payload.version);
|
||||||
|
let mtime = envelope.payload.mtime.map(|time| time.into());
|
||||||
|
this.update(&mut cx, |this, cx| {
|
||||||
|
if let Some(buffer) = this.get_possibly_incomplete(buffer_id) {
|
||||||
|
buffer.update(cx, |buffer, cx| {
|
||||||
|
buffer.did_save(version, mtime, cx);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn handle_buffer_reloaded(
|
||||||
|
this: Model<Self>,
|
||||||
|
envelope: TypedEnvelope<proto::BufferReloaded>,
|
||||||
|
mut cx: AsyncAppContext,
|
||||||
|
) -> Result<()> {
|
||||||
|
let buffer_id = BufferId::new(envelope.payload.buffer_id)?;
|
||||||
|
let version = deserialize_version(&envelope.payload.version);
|
||||||
|
let mtime = envelope.payload.mtime.map(|time| time.into());
|
||||||
|
let line_ending = deserialize_line_ending(
|
||||||
|
proto::LineEnding::from_i32(envelope.payload.line_ending)
|
||||||
|
.ok_or_else(|| anyhow!("missing line ending"))?,
|
||||||
|
);
|
||||||
|
this.update(&mut cx, |this, cx| {
|
||||||
|
if let Some(buffer) = this.get_possibly_incomplete(buffer_id) {
|
||||||
|
buffer.update(cx, |buffer, cx| {
|
||||||
|
buffer.did_reload(version, line_ending, mtime, cx);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn wait_for_loading_buffer(
|
pub async fn wait_for_loading_buffer(
|
||||||
mut receiver: postage::watch::Receiver<Option<Result<Model<Buffer>, Arc<anyhow::Error>>>>,
|
mut receiver: postage::watch::Receiver<Option<Result<Model<Buffer>, Arc<anyhow::Error>>>>,
|
||||||
) -> Result<Model<Buffer>, Arc<anyhow::Error>> {
|
) -> Result<Model<Buffer>, Arc<anyhow::Error>> {
|
||||||
|
@ -85,7 +85,7 @@ impl Manager {
|
|||||||
Some(proto::RejoinProject {
|
Some(proto::RejoinProject {
|
||||||
id: project_id,
|
id: project_id,
|
||||||
worktrees: project
|
worktrees: project
|
||||||
.worktrees()
|
.worktrees(cx)
|
||||||
.map(|worktree| {
|
.map(|worktree| {
|
||||||
let worktree = worktree.read(cx);
|
let worktree = worktree.read(cx);
|
||||||
proto::RejoinWorktree {
|
proto::RejoinWorktree {
|
||||||
|
@ -1,26 +1,25 @@
|
|||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
use futures::{channel::oneshot, FutureExt};
|
use futures::{channel::oneshot, FutureExt};
|
||||||
use gpui::{ModelContext, Task};
|
use gpui::{ModelContext, Task};
|
||||||
|
use std::{marker::PhantomData, time::Duration};
|
||||||
|
|
||||||
use crate::Project;
|
pub struct DebouncedDelay<E: 'static> {
|
||||||
|
|
||||||
pub struct DebouncedDelay {
|
|
||||||
task: Option<Task<()>>,
|
task: Option<Task<()>>,
|
||||||
cancel_channel: Option<oneshot::Sender<()>>,
|
cancel_channel: Option<oneshot::Sender<()>>,
|
||||||
|
_phantom_data: PhantomData<E>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DebouncedDelay {
|
impl<E: 'static> DebouncedDelay<E> {
|
||||||
pub fn new() -> DebouncedDelay {
|
pub fn new() -> Self {
|
||||||
DebouncedDelay {
|
Self {
|
||||||
task: None,
|
task: None,
|
||||||
cancel_channel: None,
|
cancel_channel: None,
|
||||||
|
_phantom_data: PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn fire_new<F>(&mut self, delay: Duration, cx: &mut ModelContext<Project>, func: F)
|
pub fn fire_new<F>(&mut self, delay: Duration, cx: &mut ModelContext<E>, func: F)
|
||||||
where
|
where
|
||||||
F: 'static + Send + FnOnce(&mut Project, &mut ModelContext<Project>) -> Task<()>,
|
F: 'static + Send + FnOnce(&mut E, &mut ModelContext<E>) -> Task<()>,
|
||||||
{
|
{
|
||||||
if let Some(channel) = self.cancel_channel.take() {
|
if let Some(channel) = self.cancel_channel.take() {
|
||||||
_ = channel.send(());
|
_ = channel.send(());
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -80,7 +80,7 @@ async fn test_symlinks(cx: &mut gpui::TestAppContext) {
|
|||||||
let project = Project::test(Arc::new(RealFs::default()), [root_link_path.as_ref()], cx).await;
|
let project = Project::test(Arc::new(RealFs::default()), [root_link_path.as_ref()], cx).await;
|
||||||
|
|
||||||
project.update(cx, |project, cx| {
|
project.update(cx, |project, cx| {
|
||||||
let tree = project.worktrees().next().unwrap().read(cx);
|
let tree = project.worktrees(cx).next().unwrap().read(cx);
|
||||||
assert_eq!(tree.file_count(), 5);
|
assert_eq!(tree.file_count(), 5);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
tree.inode_for_path("fennel/grape"),
|
tree.inode_for_path("fennel/grape"),
|
||||||
@ -124,13 +124,13 @@ async fn test_managing_project_specific_settings(cx: &mut gpui::TestAppContext)
|
|||||||
.await;
|
.await;
|
||||||
|
|
||||||
let project = Project::test(fs.clone(), ["/the-root".as_ref()], cx).await;
|
let project = Project::test(fs.clone(), ["/the-root".as_ref()], cx).await;
|
||||||
let worktree = project.update(cx, |project, _| project.worktrees().next().unwrap());
|
let worktree = project.update(cx, |project, cx| project.worktrees(cx).next().unwrap());
|
||||||
let task_context = TaskContext::default();
|
let task_context = TaskContext::default();
|
||||||
|
|
||||||
cx.executor().run_until_parked();
|
cx.executor().run_until_parked();
|
||||||
let worktree_id = cx.update(|cx| {
|
let worktree_id = cx.update(|cx| {
|
||||||
project.update(cx, |project, cx| {
|
project.update(cx, |project, cx| {
|
||||||
project.worktrees().next().unwrap().read(cx).id()
|
project.worktrees(cx).next().unwrap().read(cx).id()
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
let global_task_source_kind = TaskSourceKind::Worktree {
|
let global_task_source_kind = TaskSourceKind::Worktree {
|
||||||
@ -734,7 +734,7 @@ async fn test_reporting_fs_changes_to_language_servers(cx: &mut gpui::TestAppCon
|
|||||||
|
|
||||||
// Initially, we don't load ignored files because the language server has not explicitly asked us to watch them.
|
// Initially, we don't load ignored files because the language server has not explicitly asked us to watch them.
|
||||||
project.update(cx, |project, cx| {
|
project.update(cx, |project, cx| {
|
||||||
let worktree = project.worktrees().next().unwrap();
|
let worktree = project.worktrees(cx).next().unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
worktree
|
worktree
|
||||||
.read(cx)
|
.read(cx)
|
||||||
@ -808,7 +808,7 @@ async fn test_reporting_fs_changes_to_language_servers(cx: &mut gpui::TestAppCon
|
|||||||
// Now the language server has asked us to watch an ignored directory path,
|
// Now the language server has asked us to watch an ignored directory path,
|
||||||
// so we recursively load it.
|
// so we recursively load it.
|
||||||
project.update(cx, |project, cx| {
|
project.update(cx, |project, cx| {
|
||||||
let worktree = project.worktrees().next().unwrap();
|
let worktree = project.worktrees(cx).next().unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
worktree
|
worktree
|
||||||
.read(cx)
|
.read(cx)
|
||||||
@ -1132,7 +1132,7 @@ async fn test_disk_based_diagnostics_progress(cx: &mut gpui::TestAppContext) {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
let worktree_id = project.update(cx, |p, cx| p.worktrees().next().unwrap().read(cx).id());
|
let worktree_id = project.update(cx, |p, cx| p.worktrees(cx).next().unwrap().read(cx).id());
|
||||||
|
|
||||||
// Cause worktree to start the fake language server
|
// Cause worktree to start the fake language server
|
||||||
let _buffer = project
|
let _buffer = project
|
||||||
@ -2477,7 +2477,7 @@ async fn test_definition(cx: &mut gpui::TestAppContext) {
|
|||||||
) -> Vec<(&'a Path, bool)> {
|
) -> Vec<(&'a Path, bool)> {
|
||||||
project
|
project
|
||||||
.read(cx)
|
.read(cx)
|
||||||
.worktrees()
|
.worktrees(cx)
|
||||||
.map(|worktree| {
|
.map(|worktree| {
|
||||||
let worktree = worktree.read(cx);
|
let worktree = worktree.read(cx);
|
||||||
(
|
(
|
||||||
@ -2821,7 +2821,7 @@ async fn test_file_changes_multiple_times_on_disk(cx: &mut gpui::TestAppContext)
|
|||||||
.await;
|
.await;
|
||||||
|
|
||||||
let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
|
let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
|
||||||
let worktree = project.read_with(cx, |project, _| project.worktrees().next().unwrap());
|
let worktree = project.read_with(cx, |project, cx| project.worktrees(cx).next().unwrap());
|
||||||
let buffer = project
|
let buffer = project
|
||||||
.update(cx, |p, cx| p.open_local_buffer("/dir/file1", cx))
|
.update(cx, |p, cx| p.open_local_buffer("/dir/file1", cx))
|
||||||
.await
|
.await
|
||||||
@ -2876,7 +2876,7 @@ async fn test_edit_buffer_while_it_reloads(cx: &mut gpui::TestAppContext) {
|
|||||||
.await;
|
.await;
|
||||||
|
|
||||||
let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
|
let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
|
||||||
let worktree = project.read_with(cx, |project, _| project.worktrees().next().unwrap());
|
let worktree = project.read_with(cx, |project, cx| project.worktrees(cx).next().unwrap());
|
||||||
let buffer = project
|
let buffer = project
|
||||||
.update(cx, |p, cx| p.open_local_buffer("/dir/file1", cx))
|
.update(cx, |p, cx| p.open_local_buffer("/dir/file1", cx))
|
||||||
.await
|
.await
|
||||||
@ -2978,7 +2978,7 @@ async fn test_save_as(cx: &mut gpui::TestAppContext) {
|
|||||||
});
|
});
|
||||||
project
|
project
|
||||||
.update(cx, |project, cx| {
|
.update(cx, |project, cx| {
|
||||||
let worktree_id = project.worktrees().next().unwrap().read(cx).id();
|
let worktree_id = project.worktrees(cx).next().unwrap().read(cx).id();
|
||||||
let path = ProjectPath {
|
let path = ProjectPath {
|
||||||
worktree_id,
|
worktree_id,
|
||||||
path: Arc::from(Path::new("file1.rs")),
|
path: Arc::from(Path::new("file1.rs")),
|
||||||
@ -3038,7 +3038,7 @@ async fn test_rescan_and_remote_updates(cx: &mut gpui::TestAppContext) {
|
|||||||
};
|
};
|
||||||
let id_for_path = |path: &'static str, cx: &mut gpui::TestAppContext| {
|
let id_for_path = |path: &'static str, cx: &mut gpui::TestAppContext| {
|
||||||
project.update(cx, |project, cx| {
|
project.update(cx, |project, cx| {
|
||||||
let tree = project.worktrees().next().unwrap();
|
let tree = project.worktrees(cx).next().unwrap();
|
||||||
tree.read(cx)
|
tree.read(cx)
|
||||||
.entry_for_path(path)
|
.entry_for_path(path)
|
||||||
.unwrap_or_else(|| panic!("no entry for path {}", path))
|
.unwrap_or_else(|| panic!("no entry for path {}", path))
|
||||||
@ -3056,7 +3056,7 @@ async fn test_rescan_and_remote_updates(cx: &mut gpui::TestAppContext) {
|
|||||||
let file4_id = id_for_path("b/c/file4", cx);
|
let file4_id = id_for_path("b/c/file4", cx);
|
||||||
|
|
||||||
// Create a remote copy of this worktree.
|
// Create a remote copy of this worktree.
|
||||||
let tree = project.update(cx, |project, _| project.worktrees().next().unwrap());
|
let tree = project.update(cx, |project, cx| project.worktrees(cx).next().unwrap());
|
||||||
let metadata = tree.update(cx, |tree, _| tree.metadata_proto());
|
let metadata = tree.update(cx, |tree, _| tree.metadata_proto());
|
||||||
|
|
||||||
let updates = Arc::new(Mutex::new(Vec::new()));
|
let updates = Arc::new(Mutex::new(Vec::new()));
|
||||||
@ -3173,12 +3173,12 @@ async fn test_buffer_identity_across_renames(cx: &mut gpui::TestAppContext) {
|
|||||||
.await;
|
.await;
|
||||||
|
|
||||||
let project = Project::test(fs, [Path::new("/dir")], cx).await;
|
let project = Project::test(fs, [Path::new("/dir")], cx).await;
|
||||||
let tree = project.update(cx, |project, _| project.worktrees().next().unwrap());
|
let tree = project.update(cx, |project, cx| project.worktrees(cx).next().unwrap());
|
||||||
let tree_id = tree.update(cx, |tree, _| tree.id());
|
let tree_id = tree.update(cx, |tree, _| tree.id());
|
||||||
|
|
||||||
let id_for_path = |path: &'static str, cx: &mut gpui::TestAppContext| {
|
let id_for_path = |path: &'static str, cx: &mut gpui::TestAppContext| {
|
||||||
project.update(cx, |project, cx| {
|
project.update(cx, |project, cx| {
|
||||||
let tree = project.worktrees().next().unwrap();
|
let tree = project.worktrees(cx).next().unwrap();
|
||||||
tree.read(cx)
|
tree.read(cx)
|
||||||
.entry_for_path(path)
|
.entry_for_path(path)
|
||||||
.unwrap_or_else(|| panic!("no entry for path {}", path))
|
.unwrap_or_else(|| panic!("no entry for path {}", path))
|
||||||
@ -4549,7 +4549,7 @@ async fn test_create_entry(cx: &mut gpui::TestAppContext) {
|
|||||||
let project = Project::test(fs.clone(), ["/one/two/three".as_ref()], cx).await;
|
let project = Project::test(fs.clone(), ["/one/two/three".as_ref()], cx).await;
|
||||||
project
|
project
|
||||||
.update(cx, |project, cx| {
|
.update(cx, |project, cx| {
|
||||||
let id = project.worktrees().next().unwrap().read(cx).id();
|
let id = project.worktrees(cx).next().unwrap().read(cx).id();
|
||||||
project.create_entry((id, "b.."), true, cx)
|
project.create_entry((id, "b.."), true, cx)
|
||||||
})
|
})
|
||||||
.unwrap()
|
.unwrap()
|
||||||
@ -4560,7 +4560,7 @@ async fn test_create_entry(cx: &mut gpui::TestAppContext) {
|
|||||||
// Can't create paths outside the project
|
// Can't create paths outside the project
|
||||||
let result = project
|
let result = project
|
||||||
.update(cx, |project, cx| {
|
.update(cx, |project, cx| {
|
||||||
let id = project.worktrees().next().unwrap().read(cx).id();
|
let id = project.worktrees(cx).next().unwrap().read(cx).id();
|
||||||
project.create_entry((id, "../../boop"), true, cx)
|
project.create_entry((id, "../../boop"), true, cx)
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
@ -4569,7 +4569,7 @@ async fn test_create_entry(cx: &mut gpui::TestAppContext) {
|
|||||||
// Can't create paths with '..'
|
// Can't create paths with '..'
|
||||||
let result = project
|
let result = project
|
||||||
.update(cx, |project, cx| {
|
.update(cx, |project, cx| {
|
||||||
let id = project.worktrees().next().unwrap().read(cx).id();
|
let id = project.worktrees(cx).next().unwrap().read(cx).id();
|
||||||
project.create_entry((id, "four/../beep"), true, cx)
|
project.create_entry((id, "four/../beep"), true, cx)
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
@ -4592,7 +4592,7 @@ async fn test_create_entry(cx: &mut gpui::TestAppContext) {
|
|||||||
// And we cannot open buffers with '..'
|
// And we cannot open buffers with '..'
|
||||||
let result = project
|
let result = project
|
||||||
.update(cx, |project, cx| {
|
.update(cx, |project, cx| {
|
||||||
let id = project.worktrees().next().unwrap().read(cx).id();
|
let id = project.worktrees(cx).next().unwrap().read(cx).id();
|
||||||
project.open_buffer((id, "../c.rs"), cx)
|
project.open_buffer((id, "../c.rs"), cx)
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
|
311
crates/project/src/worktree_store.rs
Normal file
311
crates/project/src/worktree_store.rs
Normal file
@ -0,0 +1,311 @@
|
|||||||
|
use anyhow::{anyhow, Context as _, Result};
|
||||||
|
use collections::HashMap;
|
||||||
|
use gpui::{AppContext, AsyncAppContext, EntityId, EventEmitter, Model, ModelContext, WeakModel};
|
||||||
|
use rpc::{
|
||||||
|
proto::{self, AnyProtoClient},
|
||||||
|
TypedEnvelope,
|
||||||
|
};
|
||||||
|
use text::ReplicaId;
|
||||||
|
use worktree::{ProjectEntryId, Worktree, WorktreeId};
|
||||||
|
|
||||||
|
pub struct WorktreeStore {
|
||||||
|
is_shared: bool,
|
||||||
|
worktrees: Vec<WorktreeHandle>,
|
||||||
|
worktrees_reordered: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum WorktreeStoreEvent {
|
||||||
|
WorktreeAdded(Model<Worktree>),
|
||||||
|
WorktreeRemoved(EntityId, WorktreeId),
|
||||||
|
WorktreeOrderChanged,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EventEmitter<WorktreeStoreEvent> for WorktreeStore {}
|
||||||
|
|
||||||
|
impl WorktreeStore {
|
||||||
|
pub fn new(retain_worktrees: bool) -> Self {
|
||||||
|
Self {
|
||||||
|
is_shared: retain_worktrees,
|
||||||
|
worktrees: Vec::new(),
|
||||||
|
worktrees_reordered: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Iterates through all worktrees, including ones that don't appear in the project panel
|
||||||
|
pub fn worktrees(&self) -> impl '_ + DoubleEndedIterator<Item = Model<Worktree>> {
|
||||||
|
self.worktrees
|
||||||
|
.iter()
|
||||||
|
.filter_map(move |worktree| worktree.upgrade())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Iterates through all user-visible worktrees, the ones that appear in the project panel.
|
||||||
|
pub fn visible_worktrees<'a>(
|
||||||
|
&'a self,
|
||||||
|
cx: &'a AppContext,
|
||||||
|
) -> impl 'a + DoubleEndedIterator<Item = Model<Worktree>> {
|
||||||
|
self.worktrees()
|
||||||
|
.filter(|worktree| worktree.read(cx).is_visible())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn worktree_for_id(&self, id: WorktreeId, cx: &AppContext) -> Option<Model<Worktree>> {
|
||||||
|
self.worktrees()
|
||||||
|
.find(|worktree| worktree.read(cx).id() == id)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn worktree_for_entry(
|
||||||
|
&self,
|
||||||
|
entry_id: ProjectEntryId,
|
||||||
|
cx: &AppContext,
|
||||||
|
) -> Option<Model<Worktree>> {
|
||||||
|
self.worktrees()
|
||||||
|
.find(|worktree| worktree.read(cx).contains_entry(entry_id))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add(&mut self, worktree: &Model<Worktree>, cx: &mut ModelContext<Self>) {
|
||||||
|
let push_strong_handle = self.is_shared || worktree.read(cx).is_visible();
|
||||||
|
let handle = if push_strong_handle {
|
||||||
|
WorktreeHandle::Strong(worktree.clone())
|
||||||
|
} else {
|
||||||
|
WorktreeHandle::Weak(worktree.downgrade())
|
||||||
|
};
|
||||||
|
if self.worktrees_reordered {
|
||||||
|
self.worktrees.push(handle);
|
||||||
|
} else {
|
||||||
|
let i = match self
|
||||||
|
.worktrees
|
||||||
|
.binary_search_by_key(&Some(worktree.read(cx).abs_path()), |other| {
|
||||||
|
other.upgrade().map(|worktree| worktree.read(cx).abs_path())
|
||||||
|
}) {
|
||||||
|
Ok(i) | Err(i) => i,
|
||||||
|
};
|
||||||
|
self.worktrees.insert(i, handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
cx.emit(WorktreeStoreEvent::WorktreeAdded(worktree.clone()));
|
||||||
|
|
||||||
|
let handle_id = worktree.entity_id();
|
||||||
|
cx.observe_release(worktree, move |_, worktree, cx| {
|
||||||
|
cx.emit(WorktreeStoreEvent::WorktreeRemoved(
|
||||||
|
handle_id,
|
||||||
|
worktree.id(),
|
||||||
|
));
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove_worktree(&mut self, id_to_remove: WorktreeId, cx: &mut ModelContext<Self>) {
|
||||||
|
self.worktrees.retain(|worktree| {
|
||||||
|
if let Some(worktree) = worktree.upgrade() {
|
||||||
|
worktree.read(cx).id() != id_to_remove
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_worktrees_reordered(&mut self, worktrees_reordered: bool) {
|
||||||
|
self.worktrees_reordered = worktrees_reordered;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_worktrees_from_proto(
|
||||||
|
&mut self,
|
||||||
|
worktrees: Vec<proto::WorktreeMetadata>,
|
||||||
|
replica_id: ReplicaId,
|
||||||
|
remote_id: u64,
|
||||||
|
client: AnyProtoClient,
|
||||||
|
cx: &mut ModelContext<Self>,
|
||||||
|
) -> Result<()> {
|
||||||
|
let mut old_worktrees_by_id = self
|
||||||
|
.worktrees
|
||||||
|
.drain(..)
|
||||||
|
.filter_map(|worktree| {
|
||||||
|
let worktree = worktree.upgrade()?;
|
||||||
|
Some((worktree.read(cx).id(), worktree))
|
||||||
|
})
|
||||||
|
.collect::<HashMap<_, _>>();
|
||||||
|
|
||||||
|
for worktree in worktrees {
|
||||||
|
if let Some(old_worktree) =
|
||||||
|
old_worktrees_by_id.remove(&WorktreeId::from_proto(worktree.id))
|
||||||
|
{
|
||||||
|
self.worktrees.push(WorktreeHandle::Strong(old_worktree));
|
||||||
|
} else {
|
||||||
|
self.add(
|
||||||
|
&Worktree::remote(remote_id, replica_id, worktree, client.clone(), cx),
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn move_worktree(
|
||||||
|
&mut self,
|
||||||
|
source: WorktreeId,
|
||||||
|
destination: WorktreeId,
|
||||||
|
cx: &mut ModelContext<Self>,
|
||||||
|
) -> Result<()> {
|
||||||
|
if source == destination {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut source_index = None;
|
||||||
|
let mut destination_index = None;
|
||||||
|
for (i, worktree) in self.worktrees.iter().enumerate() {
|
||||||
|
if let Some(worktree) = worktree.upgrade() {
|
||||||
|
let worktree_id = worktree.read(cx).id();
|
||||||
|
if worktree_id == source {
|
||||||
|
source_index = Some(i);
|
||||||
|
if destination_index.is_some() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else if worktree_id == destination {
|
||||||
|
destination_index = Some(i);
|
||||||
|
if source_index.is_some() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let source_index =
|
||||||
|
source_index.with_context(|| format!("Missing worktree for id {source}"))?;
|
||||||
|
let destination_index =
|
||||||
|
destination_index.with_context(|| format!("Missing worktree for id {destination}"))?;
|
||||||
|
|
||||||
|
if source_index == destination_index {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let worktree_to_move = self.worktrees.remove(source_index);
|
||||||
|
self.worktrees.insert(destination_index, worktree_to_move);
|
||||||
|
self.worktrees_reordered = true;
|
||||||
|
cx.emit(WorktreeStoreEvent::WorktreeOrderChanged);
|
||||||
|
cx.notify();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn disconnected_from_host(&mut self, cx: &mut AppContext) {
|
||||||
|
for worktree in &self.worktrees {
|
||||||
|
if let Some(worktree) = worktree.upgrade() {
|
||||||
|
worktree.update(cx, |worktree, _| {
|
||||||
|
if let Some(worktree) = worktree.as_remote_mut() {
|
||||||
|
worktree.disconnected_from_host();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_shared(&mut self, is_shared: bool, cx: &mut ModelContext<Self>) {
|
||||||
|
self.is_shared = is_shared;
|
||||||
|
|
||||||
|
// When shared, retain all worktrees
|
||||||
|
if is_shared {
|
||||||
|
for worktree_handle in self.worktrees.iter_mut() {
|
||||||
|
match worktree_handle {
|
||||||
|
WorktreeHandle::Strong(_) => {}
|
||||||
|
WorktreeHandle::Weak(worktree) => {
|
||||||
|
if let Some(worktree) = worktree.upgrade() {
|
||||||
|
*worktree_handle = WorktreeHandle::Strong(worktree);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// When not shared, only retain the visible worktrees
|
||||||
|
else {
|
||||||
|
for worktree_handle in self.worktrees.iter_mut() {
|
||||||
|
if let WorktreeHandle::Strong(worktree) = worktree_handle {
|
||||||
|
let is_visible = worktree.update(cx, |worktree, _| {
|
||||||
|
worktree.stop_observing_updates();
|
||||||
|
worktree.is_visible()
|
||||||
|
});
|
||||||
|
if !is_visible {
|
||||||
|
*worktree_handle = WorktreeHandle::Weak(worktree.downgrade());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn handle_create_project_entry(
|
||||||
|
this: Model<Self>,
|
||||||
|
envelope: TypedEnvelope<proto::CreateProjectEntry>,
|
||||||
|
mut cx: AsyncAppContext,
|
||||||
|
) -> Result<proto::ProjectEntryResponse> {
|
||||||
|
let worktree = this.update(&mut cx, |this, cx| {
|
||||||
|
let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
|
||||||
|
this.worktree_for_id(worktree_id, cx)
|
||||||
|
.ok_or_else(|| anyhow!("worktree not found"))
|
||||||
|
})??;
|
||||||
|
Worktree::handle_create_entry(worktree, envelope.payload, cx).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn handle_rename_project_entry(
|
||||||
|
this: Model<Self>,
|
||||||
|
envelope: TypedEnvelope<proto::RenameProjectEntry>,
|
||||||
|
mut cx: AsyncAppContext,
|
||||||
|
) -> Result<proto::ProjectEntryResponse> {
|
||||||
|
let entry_id = ProjectEntryId::from_proto(envelope.payload.entry_id);
|
||||||
|
let worktree = this.update(&mut cx, |this, cx| {
|
||||||
|
this.worktree_for_entry(entry_id, cx)
|
||||||
|
.ok_or_else(|| anyhow!("worktree not found"))
|
||||||
|
})??;
|
||||||
|
Worktree::handle_rename_entry(worktree, envelope.payload, cx).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn handle_copy_project_entry(
|
||||||
|
this: Model<Self>,
|
||||||
|
envelope: TypedEnvelope<proto::CopyProjectEntry>,
|
||||||
|
mut cx: AsyncAppContext,
|
||||||
|
) -> Result<proto::ProjectEntryResponse> {
|
||||||
|
let entry_id = ProjectEntryId::from_proto(envelope.payload.entry_id);
|
||||||
|
let worktree = this.update(&mut cx, |this, cx| {
|
||||||
|
this.worktree_for_entry(entry_id, cx)
|
||||||
|
.ok_or_else(|| anyhow!("worktree not found"))
|
||||||
|
})??;
|
||||||
|
Worktree::handle_copy_entry(worktree, envelope.payload, cx).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn handle_delete_project_entry(
|
||||||
|
this: Model<Self>,
|
||||||
|
envelope: TypedEnvelope<proto::DeleteProjectEntry>,
|
||||||
|
mut cx: AsyncAppContext,
|
||||||
|
) -> Result<proto::ProjectEntryResponse> {
|
||||||
|
let entry_id = ProjectEntryId::from_proto(envelope.payload.entry_id);
|
||||||
|
let worktree = this.update(&mut cx, |this, cx| {
|
||||||
|
this.worktree_for_entry(entry_id, cx)
|
||||||
|
.ok_or_else(|| anyhow!("worktree not found"))
|
||||||
|
})??;
|
||||||
|
Worktree::handle_delete_entry(worktree, envelope.payload, cx).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn handle_expand_project_entry(
|
||||||
|
this: Model<Self>,
|
||||||
|
envelope: TypedEnvelope<proto::ExpandProjectEntry>,
|
||||||
|
mut cx: AsyncAppContext,
|
||||||
|
) -> Result<proto::ExpandProjectEntryResponse> {
|
||||||
|
let entry_id = ProjectEntryId::from_proto(envelope.payload.entry_id);
|
||||||
|
let worktree = this
|
||||||
|
.update(&mut cx, |this, cx| this.worktree_for_entry(entry_id, cx))?
|
||||||
|
.ok_or_else(|| anyhow!("invalid request"))?;
|
||||||
|
Worktree::handle_expand_entry(worktree, envelope.payload, cx).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
enum WorktreeHandle {
|
||||||
|
Strong(Model<Worktree>),
|
||||||
|
Weak(WeakModel<Worktree>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WorktreeHandle {
|
||||||
|
fn upgrade(&self) -> Option<Model<Worktree>> {
|
||||||
|
match self {
|
||||||
|
WorktreeHandle::Strong(handle) => Some(handle.clone()),
|
||||||
|
WorktreeHandle::Weak(handle) => handle.upgrade(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -4173,7 +4173,7 @@ mod tests {
|
|||||||
|
|
||||||
let project = Project::test(fs.clone(), ["/project_root".as_ref()], cx).await;
|
let project = Project::test(fs.clone(), ["/project_root".as_ref()], cx).await;
|
||||||
let worktree_id =
|
let worktree_id =
|
||||||
cx.update(|cx| project.read(cx).worktrees().next().unwrap().read(cx).id());
|
cx.update(|cx| project.read(cx).worktrees(cx).next().unwrap().read(cx).id());
|
||||||
let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
|
let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
|
||||||
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
||||||
let panel = workspace
|
let panel = workspace
|
||||||
@ -4969,7 +4969,7 @@ mod tests {
|
|||||||
) {
|
) {
|
||||||
let path = path.as_ref();
|
let path = path.as_ref();
|
||||||
panel.update(cx, |panel, cx| {
|
panel.update(cx, |panel, cx| {
|
||||||
for worktree in panel.project.read(cx).worktrees().collect::<Vec<_>>() {
|
for worktree in panel.project.read(cx).worktrees(cx).collect::<Vec<_>>() {
|
||||||
let worktree = worktree.read(cx);
|
let worktree = worktree.read(cx);
|
||||||
if let Ok(relative_path) = path.strip_prefix(worktree.root_name()) {
|
if let Ok(relative_path) = path.strip_prefix(worktree.root_name()) {
|
||||||
let entry_id = worktree.entry_for_path(relative_path).unwrap().id;
|
let entry_id = worktree.entry_for_path(relative_path).unwrap().id;
|
||||||
@ -4984,7 +4984,7 @@ mod tests {
|
|||||||
fn select_path(panel: &View<ProjectPanel>, path: impl AsRef<Path>, cx: &mut VisualTestContext) {
|
fn select_path(panel: &View<ProjectPanel>, path: impl AsRef<Path>, cx: &mut VisualTestContext) {
|
||||||
let path = path.as_ref();
|
let path = path.as_ref();
|
||||||
panel.update(cx, |panel, cx| {
|
panel.update(cx, |panel, cx| {
|
||||||
for worktree in panel.project.read(cx).worktrees().collect::<Vec<_>>() {
|
for worktree in panel.project.read(cx).worktrees(cx).collect::<Vec<_>>() {
|
||||||
let worktree = worktree.read(cx);
|
let worktree = worktree.read(cx);
|
||||||
if let Ok(relative_path) = path.strip_prefix(worktree.root_name()) {
|
if let Ok(relative_path) = path.strip_prefix(worktree.root_name()) {
|
||||||
let entry_id = worktree.entry_for_path(relative_path).unwrap().id;
|
let entry_id = worktree.entry_for_path(relative_path).unwrap().id;
|
||||||
@ -5006,7 +5006,7 @@ mod tests {
|
|||||||
) -> Option<ProjectEntryId> {
|
) -> Option<ProjectEntryId> {
|
||||||
let path = path.as_ref();
|
let path = path.as_ref();
|
||||||
panel.update(cx, |panel, cx| {
|
panel.update(cx, |panel, cx| {
|
||||||
for worktree in panel.project.read(cx).worktrees().collect::<Vec<_>>() {
|
for worktree in panel.project.read(cx).worktrees(cx).collect::<Vec<_>>() {
|
||||||
let worktree = worktree.read(cx);
|
let worktree = worktree.read(cx);
|
||||||
if let Ok(relative_path) = path.strip_prefix(worktree.root_name()) {
|
if let Ok(relative_path) = path.strip_prefix(worktree.root_name()) {
|
||||||
return worktree.entry_for_path(relative_path).map(|entry| entry.id);
|
return worktree.entry_for_path(relative_path).map(|entry| entry.id);
|
||||||
|
@ -534,11 +534,14 @@ impl SshClientState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mut server_binary_exists = false;
|
let mut server_binary_exists = false;
|
||||||
if let Ok(installed_version) = run_cmd(self.ssh_command(&dst_path).arg("version")).await {
|
if cfg!(not(debug_assertions)) {
|
||||||
|
if let Ok(installed_version) = run_cmd(self.ssh_command(&dst_path).arg("version")).await
|
||||||
|
{
|
||||||
if installed_version.trim() == version.to_string() {
|
if installed_version.trim() == version.to_string() {
|
||||||
server_binary_exists = true;
|
server_binary_exists = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if server_binary_exists {
|
if server_binary_exists {
|
||||||
log::info!("remote development server already present",);
|
log::info!("remote development server already present",);
|
||||||
|
@ -32,6 +32,7 @@ remote.workspace = true
|
|||||||
rpc.workspace = true
|
rpc.workspace = true
|
||||||
settings.workspace = true
|
settings.workspace = true
|
||||||
smol.workspace = true
|
smol.workspace = true
|
||||||
|
util.workspace = true
|
||||||
worktree.workspace = true
|
worktree.workspace = true
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
@ -1,7 +1,11 @@
|
|||||||
use anyhow::{Context as _, Result};
|
use anyhow::Result;
|
||||||
use fs::Fs;
|
use fs::Fs;
|
||||||
use gpui::{AppContext, AsyncAppContext, Context, Model, ModelContext};
|
use gpui::{AppContext, AsyncAppContext, Context, Model, ModelContext};
|
||||||
use project::{buffer_store::BufferStore, ProjectPath, WorktreeId, WorktreeSettings};
|
use project::{
|
||||||
|
buffer_store::{BufferStore, BufferStoreEvent},
|
||||||
|
worktree_store::WorktreeStore,
|
||||||
|
ProjectPath, WorktreeId, WorktreeSettings,
|
||||||
|
};
|
||||||
use remote::SshSession;
|
use remote::SshSession;
|
||||||
use rpc::{
|
use rpc::{
|
||||||
proto::{self, AnyProtoClient, PeerId},
|
proto::{self, AnyProtoClient, PeerId},
|
||||||
@ -12,6 +16,7 @@ use std::{
|
|||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
sync::{atomic::AtomicUsize, Arc},
|
sync::{atomic::AtomicUsize, Arc},
|
||||||
};
|
};
|
||||||
|
use util::ResultExt as _;
|
||||||
use worktree::Worktree;
|
use worktree::Worktree;
|
||||||
|
|
||||||
const PEER_ID: PeerId = PeerId { owner_id: 0, id: 0 };
|
const PEER_ID: PeerId = PeerId { owner_id: 0, id: 0 };
|
||||||
@ -20,7 +25,7 @@ const PROJECT_ID: u64 = 0;
|
|||||||
pub struct HeadlessProject {
|
pub struct HeadlessProject {
|
||||||
pub fs: Arc<dyn Fs>,
|
pub fs: Arc<dyn Fs>,
|
||||||
pub session: AnyProtoClient,
|
pub session: AnyProtoClient,
|
||||||
pub worktrees: Vec<Model<Worktree>>,
|
pub worktree_store: Model<WorktreeStore>,
|
||||||
pub buffer_store: Model<BufferStore>,
|
pub buffer_store: Model<BufferStore>,
|
||||||
pub next_entry_id: Arc<AtomicUsize>,
|
pub next_entry_id: Arc<AtomicUsize>,
|
||||||
}
|
}
|
||||||
@ -34,27 +39,45 @@ impl HeadlessProject {
|
|||||||
pub fn new(session: Arc<SshSession>, fs: Arc<dyn Fs>, cx: &mut ModelContext<Self>) -> Self {
|
pub fn new(session: Arc<SshSession>, fs: Arc<dyn Fs>, cx: &mut ModelContext<Self>) -> Self {
|
||||||
let this = cx.weak_model();
|
let this = cx.weak_model();
|
||||||
|
|
||||||
|
let worktree_store = cx.new_model(|_| WorktreeStore::new(true));
|
||||||
|
let buffer_store = cx.new_model(|cx| BufferStore::new(worktree_store.clone(), true, cx));
|
||||||
|
cx.subscribe(&buffer_store, Self::on_buffer_store_event)
|
||||||
|
.detach();
|
||||||
|
|
||||||
session.add_request_handler(this.clone(), Self::handle_add_worktree);
|
session.add_request_handler(this.clone(), Self::handle_add_worktree);
|
||||||
session.add_request_handler(this.clone(), Self::handle_open_buffer_by_path);
|
session.add_request_handler(this.clone(), Self::handle_open_buffer_by_path);
|
||||||
session.add_request_handler(this.clone(), Self::handle_update_buffer);
|
session.add_request_handler(this.clone(), Self::handle_update_buffer);
|
||||||
session.add_request_handler(this.clone(), Self::handle_save_buffer);
|
session.add_request_handler(this.clone(), Self::handle_save_buffer);
|
||||||
|
session.add_request_handler(
|
||||||
|
worktree_store.downgrade(),
|
||||||
|
WorktreeStore::handle_create_project_entry,
|
||||||
|
);
|
||||||
|
session.add_request_handler(
|
||||||
|
worktree_store.downgrade(),
|
||||||
|
WorktreeStore::handle_rename_project_entry,
|
||||||
|
);
|
||||||
|
session.add_request_handler(
|
||||||
|
worktree_store.downgrade(),
|
||||||
|
WorktreeStore::handle_copy_project_entry,
|
||||||
|
);
|
||||||
|
session.add_request_handler(
|
||||||
|
worktree_store.downgrade(),
|
||||||
|
WorktreeStore::handle_delete_project_entry,
|
||||||
|
);
|
||||||
|
session.add_request_handler(
|
||||||
|
worktree_store.downgrade(),
|
||||||
|
WorktreeStore::handle_expand_project_entry,
|
||||||
|
);
|
||||||
|
|
||||||
HeadlessProject {
|
HeadlessProject {
|
||||||
session: session.into(),
|
session: session.into(),
|
||||||
fs,
|
fs,
|
||||||
worktrees: Vec::new(),
|
worktree_store,
|
||||||
buffer_store: cx.new_model(|_| BufferStore::new(true)),
|
buffer_store,
|
||||||
next_entry_id: Default::default(),
|
next_entry_id: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn worktree_for_id(&self, id: WorktreeId, cx: &AppContext) -> Option<Model<Worktree>> {
|
|
||||||
self.worktrees
|
|
||||||
.iter()
|
|
||||||
.find(|worktree| worktree.read(cx).id() == id)
|
|
||||||
.cloned()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn handle_add_worktree(
|
pub async fn handle_add_worktree(
|
||||||
this: Model<Self>,
|
this: Model<Self>,
|
||||||
message: TypedEnvelope<proto::AddWorktree>,
|
message: TypedEnvelope<proto::AddWorktree>,
|
||||||
@ -74,7 +97,9 @@ impl HeadlessProject {
|
|||||||
|
|
||||||
this.update(&mut cx, |this, cx| {
|
this.update(&mut cx, |this, cx| {
|
||||||
let session = this.session.clone();
|
let session = this.session.clone();
|
||||||
this.worktrees.push(worktree.clone());
|
this.worktree_store.update(cx, |worktree_store, cx| {
|
||||||
|
worktree_store.add(&worktree, cx);
|
||||||
|
});
|
||||||
worktree.update(cx, |worktree, cx| {
|
worktree.update(cx, |worktree, cx| {
|
||||||
worktree.observe_updates(0, cx, move |update| {
|
worktree.observe_updates(0, cx, move |update| {
|
||||||
session.send(update).ok();
|
session.send(update).ok();
|
||||||
@ -104,19 +129,8 @@ impl HeadlessProject {
|
|||||||
envelope: TypedEnvelope<proto::SaveBuffer>,
|
envelope: TypedEnvelope<proto::SaveBuffer>,
|
||||||
mut cx: AsyncAppContext,
|
mut cx: AsyncAppContext,
|
||||||
) -> Result<proto::BufferSaved> {
|
) -> Result<proto::BufferSaved> {
|
||||||
let (buffer_store, worktree) = this.update(&mut cx, |this, cx| {
|
let buffer_store = this.update(&mut cx, |this, _| this.buffer_store.clone())?;
|
||||||
let buffer_store = this.buffer_store.clone();
|
BufferStore::handle_save_buffer(buffer_store, PROJECT_ID, envelope, cx).await
|
||||||
let worktree = if let Some(path) = &envelope.payload.new_path {
|
|
||||||
Some(
|
|
||||||
this.worktree_for_id(WorktreeId::from_proto(path.worktree_id), cx)
|
|
||||||
.context("worktree does not exist")?,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
anyhow::Ok((buffer_store, worktree))
|
|
||||||
})??;
|
|
||||||
BufferStore::handle_save_buffer(buffer_store, PROJECT_ID, worktree, envelope, cx).await
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn handle_open_buffer_by_path(
|
pub async fn handle_open_buffer_by_path(
|
||||||
@ -126,9 +140,6 @@ impl HeadlessProject {
|
|||||||
) -> Result<proto::OpenBufferResponse> {
|
) -> Result<proto::OpenBufferResponse> {
|
||||||
let worktree_id = WorktreeId::from_proto(message.payload.worktree_id);
|
let worktree_id = WorktreeId::from_proto(message.payload.worktree_id);
|
||||||
let (buffer_store, buffer, session) = this.update(&mut cx, |this, cx| {
|
let (buffer_store, buffer, session) = this.update(&mut cx, |this, cx| {
|
||||||
let worktree = this
|
|
||||||
.worktree_for_id(worktree_id, cx)
|
|
||||||
.context("no such worktree")?;
|
|
||||||
let buffer_store = this.buffer_store.clone();
|
let buffer_store = this.buffer_store.clone();
|
||||||
let buffer = this.buffer_store.update(cx, |buffer_store, cx| {
|
let buffer = this.buffer_store.update(cx, |buffer_store, cx| {
|
||||||
buffer_store.open_buffer(
|
buffer_store.open_buffer(
|
||||||
@ -136,7 +147,6 @@ impl HeadlessProject {
|
|||||||
worktree_id,
|
worktree_id,
|
||||||
path: PathBuf::from(message.payload.path).into(),
|
path: PathBuf::from(message.payload.path).into(),
|
||||||
},
|
},
|
||||||
worktree,
|
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
@ -163,4 +173,41 @@ impl HeadlessProject {
|
|||||||
buffer_id: buffer_id.to_proto(),
|
buffer_id: buffer_id.to_proto(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn on_buffer_store_event(
|
||||||
|
&mut self,
|
||||||
|
_: Model<BufferStore>,
|
||||||
|
event: &BufferStoreEvent,
|
||||||
|
cx: &mut ModelContext<Self>,
|
||||||
|
) {
|
||||||
|
match event {
|
||||||
|
BufferStoreEvent::LocalBufferUpdated { buffer } => {
|
||||||
|
let buffer = buffer.read(cx);
|
||||||
|
let buffer_id = buffer.remote_id();
|
||||||
|
let Some(new_file) = buffer.file() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
self.session
|
||||||
|
.send(proto::UpdateBufferFile {
|
||||||
|
project_id: 0,
|
||||||
|
buffer_id: buffer_id.into(),
|
||||||
|
file: Some(new_file.to_proto(cx)),
|
||||||
|
})
|
||||||
|
.log_err();
|
||||||
|
}
|
||||||
|
BufferStoreEvent::DiffBaseUpdated { buffer } => {
|
||||||
|
let buffer = buffer.read(cx);
|
||||||
|
let buffer_id = buffer.remote_id();
|
||||||
|
let diff_base = buffer.diff_base();
|
||||||
|
self.session
|
||||||
|
.send(proto::UpdateDiffBase {
|
||||||
|
project_id: 0,
|
||||||
|
buffer_id: buffer_id.to_proto(),
|
||||||
|
diff_base: diff_base.map(|b| b.to_string()),
|
||||||
|
})
|
||||||
|
.log_err();
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,6 @@ use std::{env, io, mem, process, sync::Arc};
|
|||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
env::set_var("RUST_BACKTRACE", "1");
|
env::set_var("RUST_BACKTRACE", "1");
|
||||||
env::set_var("RUST_LOG", "remote=trace");
|
|
||||||
|
|
||||||
let subcommand = std::env::args().nth(1);
|
let subcommand = std::env::args().nth(1);
|
||||||
match subcommand.as_deref() {
|
match subcommand.as_deref() {
|
||||||
|
@ -12,15 +12,23 @@ use serde_json::json;
|
|||||||
use settings::SettingsStore;
|
use settings::SettingsStore;
|
||||||
use std::{path::Path, sync::Arc};
|
use std::{path::Path, sync::Arc};
|
||||||
|
|
||||||
|
fn init_logger() {
|
||||||
|
if std::env::var("RUST_LOG").is_ok() {
|
||||||
|
env_logger::try_init().ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_remote_editing(cx: &mut TestAppContext, server_cx: &mut TestAppContext) {
|
async fn test_remote_editing(cx: &mut TestAppContext, server_cx: &mut TestAppContext) {
|
||||||
let (client_ssh, server_ssh) = SshSession::fake(cx, server_cx);
|
let (client_ssh, server_ssh) = SshSession::fake(cx, server_cx);
|
||||||
|
init_logger();
|
||||||
|
|
||||||
let fs = FakeFs::new(server_cx.executor());
|
let fs = FakeFs::new(server_cx.executor());
|
||||||
fs.insert_tree(
|
fs.insert_tree(
|
||||||
"/code",
|
"/code",
|
||||||
json!({
|
json!({
|
||||||
"project1": {
|
"project1": {
|
||||||
|
".git": {},
|
||||||
"README.md": "# project 1",
|
"README.md": "# project 1",
|
||||||
"src": {
|
"src": {
|
||||||
"lib.rs": "fn one() -> usize { 1 }"
|
"lib.rs": "fn one() -> usize { 1 }"
|
||||||
@ -32,6 +40,10 @@ async fn test_remote_editing(cx: &mut TestAppContext, server_cx: &mut TestAppCon
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
fs.set_index_for_repo(
|
||||||
|
Path::new("/code/project1/.git"),
|
||||||
|
&[(Path::new("src/lib.rs"), "fn one() -> usize { 0 }".into())],
|
||||||
|
);
|
||||||
|
|
||||||
server_cx.update(HeadlessProject::init);
|
server_cx.update(HeadlessProject::init);
|
||||||
let _headless_project =
|
let _headless_project =
|
||||||
@ -52,6 +64,7 @@ async fn test_remote_editing(cx: &mut TestAppContext, server_cx: &mut TestAppCon
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
worktree.paths().map(Arc::as_ref).collect::<Vec<_>>(),
|
worktree.paths().map(Arc::as_ref).collect::<Vec<_>>(),
|
||||||
vec![
|
vec![
|
||||||
|
Path::new(".git"),
|
||||||
Path::new("README.md"),
|
Path::new("README.md"),
|
||||||
Path::new("src"),
|
Path::new("src"),
|
||||||
Path::new("src/lib.rs"),
|
Path::new("src/lib.rs"),
|
||||||
@ -69,6 +82,10 @@ async fn test_remote_editing(cx: &mut TestAppContext, server_cx: &mut TestAppCon
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
buffer.update(cx, |buffer, cx| {
|
buffer.update(cx, |buffer, cx| {
|
||||||
assert_eq!(buffer.text(), "fn one() -> usize { 1 }");
|
assert_eq!(buffer.text(), "fn one() -> usize { 1 }");
|
||||||
|
assert_eq!(
|
||||||
|
buffer.diff_base().unwrap().to_string(),
|
||||||
|
"fn one() -> usize { 0 }"
|
||||||
|
);
|
||||||
let ix = buffer.text().find('1').unwrap();
|
let ix = buffer.text().find('1').unwrap();
|
||||||
buffer.edit([(ix..ix + 1, "100")], None, cx);
|
buffer.edit([(ix..ix + 1, "100")], None, cx);
|
||||||
});
|
});
|
||||||
@ -76,7 +93,7 @@ async fn test_remote_editing(cx: &mut TestAppContext, server_cx: &mut TestAppCon
|
|||||||
// The user saves the buffer. The new contents are written to the
|
// The user saves the buffer. The new contents are written to the
|
||||||
// remote filesystem.
|
// remote filesystem.
|
||||||
project
|
project
|
||||||
.update(cx, |project, cx| project.save_buffer(buffer, cx))
|
.update(cx, |project, cx| project.save_buffer(buffer.clone(), cx))
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@ -98,6 +115,7 @@ async fn test_remote_editing(cx: &mut TestAppContext, server_cx: &mut TestAppCon
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
worktree.paths().map(Arc::as_ref).collect::<Vec<_>>(),
|
worktree.paths().map(Arc::as_ref).collect::<Vec<_>>(),
|
||||||
vec![
|
vec![
|
||||||
|
Path::new(".git"),
|
||||||
Path::new("README.md"),
|
Path::new("README.md"),
|
||||||
Path::new("src"),
|
Path::new("src"),
|
||||||
Path::new("src/lib.rs"),
|
Path::new("src/lib.rs"),
|
||||||
@ -105,6 +123,31 @@ async fn test_remote_editing(cx: &mut TestAppContext, server_cx: &mut TestAppCon
|
|||||||
]
|
]
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// A file that is currently open in a buffer is renamed.
|
||||||
|
fs.rename(
|
||||||
|
"/code/project1/src/lib.rs".as_ref(),
|
||||||
|
"/code/project1/src/lib2.rs".as_ref(),
|
||||||
|
Default::default(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
cx.executor().run_until_parked();
|
||||||
|
buffer.update(cx, |buffer, _| {
|
||||||
|
assert_eq!(&**buffer.file().unwrap().path(), Path::new("src/lib2.rs"));
|
||||||
|
});
|
||||||
|
|
||||||
|
fs.set_index_for_repo(
|
||||||
|
Path::new("/code/project1/.git"),
|
||||||
|
&[(Path::new("src/lib2.rs"), "fn one() -> usize { 100 }".into())],
|
||||||
|
);
|
||||||
|
cx.executor().run_until_parked();
|
||||||
|
buffer.update(cx, |buffer, _| {
|
||||||
|
assert_eq!(
|
||||||
|
buffer.diff_base().unwrap().to_string(),
|
||||||
|
"fn one() -> usize { 100 }"
|
||||||
|
);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_project(ssh: Arc<SshSession>, cx: &mut TestAppContext) -> Model<Project> {
|
fn build_project(ssh: Arc<SshSession>, cx: &mut TestAppContext) -> Model<Project> {
|
||||||
|
@ -2400,7 +2400,7 @@ pub mod tests {
|
|||||||
.await;
|
.await;
|
||||||
let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
|
let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
|
||||||
let worktree_id = project.read_with(cx, |project, cx| {
|
let worktree_id = project.read_with(cx, |project, cx| {
|
||||||
project.worktrees().next().unwrap().read(cx).id()
|
project.worktrees(cx).next().unwrap().read(cx).id()
|
||||||
});
|
});
|
||||||
let window = cx.add_window(|cx| Workspace::test_new(project, cx));
|
let window = cx.add_window(|cx| Workspace::test_new(project, cx));
|
||||||
let workspace = window.root(cx).unwrap();
|
let workspace = window.root(cx).unwrap();
|
||||||
@ -2836,7 +2836,7 @@ pub mod tests {
|
|||||||
.await;
|
.await;
|
||||||
let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
|
let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
|
||||||
let worktree_id = project.update(cx, |this, cx| {
|
let worktree_id = project.update(cx, |this, cx| {
|
||||||
this.worktrees().next().unwrap().read(cx).id()
|
this.worktrees(cx).next().unwrap().read(cx).id()
|
||||||
});
|
});
|
||||||
|
|
||||||
let window = cx.add_window(|cx| Workspace::test_new(project, cx));
|
let window = cx.add_window(|cx| Workspace::test_new(project, cx));
|
||||||
@ -3053,7 +3053,7 @@ pub mod tests {
|
|||||||
.await;
|
.await;
|
||||||
let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
|
let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
|
||||||
let worktree_id = project.update(cx, |this, cx| {
|
let worktree_id = project.update(cx, |this, cx| {
|
||||||
this.worktrees().next().unwrap().read(cx).id()
|
this.worktrees(cx).next().unwrap().read(cx).id()
|
||||||
});
|
});
|
||||||
let window = cx.add_window(|cx| Workspace::test_new(project, cx));
|
let window = cx.add_window(|cx| Workspace::test_new(project, cx));
|
||||||
let panes: Vec<_> = window
|
let panes: Vec<_> = window
|
||||||
|
@ -273,7 +273,7 @@ async fn open_buffer(
|
|||||||
) -> Box<dyn ItemHandle> {
|
) -> Box<dyn ItemHandle> {
|
||||||
let project = workspace.update(cx, |workspace, _| workspace.project().clone());
|
let project = workspace.update(cx, |workspace, _| workspace.project().clone());
|
||||||
let worktree_id = project.update(cx, |project, cx| {
|
let worktree_id = project.update(cx, |project, cx| {
|
||||||
let worktree = project.worktrees().last().expect("worktree not found");
|
let worktree = project.worktrees(cx).last().expect("worktree not found");
|
||||||
worktree.read(cx).id()
|
worktree.read(cx).id()
|
||||||
});
|
});
|
||||||
let project_path = ProjectPath {
|
let project_path = ProjectPath {
|
||||||
|
@ -256,7 +256,7 @@ mod tests {
|
|||||||
);
|
);
|
||||||
|
|
||||||
let worktree_id = project.update(cx, |project, cx| {
|
let worktree_id = project.update(cx, |project, cx| {
|
||||||
project.worktrees().next().unwrap().read(cx).id()
|
project.worktrees(cx).next().unwrap().read(cx).id()
|
||||||
});
|
});
|
||||||
let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
|
let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
|
||||||
|
|
||||||
|
@ -2229,7 +2229,7 @@ impl Render for Pane {
|
|||||||
pane.child(self.render_tab_bar(cx))
|
pane.child(self.render_tab_bar(cx))
|
||||||
})
|
})
|
||||||
.child({
|
.child({
|
||||||
let has_worktrees = self.project.read(cx).worktrees().next().is_some();
|
let has_worktrees = self.project.read(cx).worktrees(cx).next().is_some();
|
||||||
// main content
|
// main content
|
||||||
div()
|
div()
|
||||||
.flex_1()
|
.flex_1()
|
||||||
|
@ -1077,8 +1077,8 @@ impl Workspace {
|
|||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
if paths_order.iter().enumerate().any(|(i, &j)| i != j) {
|
if paths_order.iter().enumerate().any(|(i, &j)| i != j) {
|
||||||
project_handle
|
project_handle
|
||||||
.update(&mut cx, |project, _| {
|
.update(&mut cx, |project, cx| {
|
||||||
project.set_worktrees_reordered(true);
|
project.set_worktrees_reordered(true, cx);
|
||||||
})
|
})
|
||||||
.log_err();
|
.log_err();
|
||||||
}
|
}
|
||||||
@ -1567,7 +1567,7 @@ impl Workspace {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn worktrees<'a>(&self, cx: &'a AppContext) -> impl 'a + Iterator<Item = Model<Worktree>> {
|
pub fn worktrees<'a>(&self, cx: &'a AppContext) -> impl 'a + Iterator<Item = Model<Worktree>> {
|
||||||
self.project.read(cx).worktrees()
|
self.project.read(cx).worktrees(cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn visible_worktrees<'a>(
|
pub fn visible_worktrees<'a>(
|
||||||
@ -1861,7 +1861,7 @@ impl Workspace {
|
|||||||
) -> Task<Result<()>> {
|
) -> Task<Result<()>> {
|
||||||
let window = cx.window_handle().downcast::<Self>();
|
let window = cx.window_handle().downcast::<Self>();
|
||||||
let is_remote = self.project.read(cx).is_remote();
|
let is_remote = self.project.read(cx).is_remote();
|
||||||
let has_worktree = self.project.read(cx).worktrees().next().is_some();
|
let has_worktree = self.project.read(cx).worktrees(cx).next().is_some();
|
||||||
let has_dirty_items = self.items(cx).any(|item| item.is_dirty(cx));
|
let has_dirty_items = self.items(cx).any(|item| item.is_dirty(cx));
|
||||||
|
|
||||||
let window_to_replace = if replace_current_window {
|
let window_to_replace = if replace_current_window {
|
||||||
@ -5685,7 +5685,7 @@ mod tests {
|
|||||||
let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
|
let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
|
||||||
let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
|
let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
|
||||||
let worktree_id = project.update(cx, |project, cx| {
|
let worktree_id = project.update(cx, |project, cx| {
|
||||||
project.worktrees().next().unwrap().read(cx).id()
|
project.worktrees(cx).next().unwrap().read(cx).id()
|
||||||
});
|
});
|
||||||
|
|
||||||
let item1 = cx.new_view(|cx| {
|
let item1 = cx.new_view(|cx| {
|
||||||
@ -6809,7 +6809,7 @@ mod tests {
|
|||||||
let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
|
let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
|
||||||
|
|
||||||
let worktree_id = project.update(cx, |project, cx| {
|
let worktree_id = project.update(cx, |project, cx| {
|
||||||
project.worktrees().next().unwrap().read(cx).id()
|
project.worktrees(cx).next().unwrap().read(cx).id()
|
||||||
});
|
});
|
||||||
|
|
||||||
let handle = workspace
|
let handle = workspace
|
||||||
@ -6872,7 +6872,7 @@ mod tests {
|
|||||||
let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
|
let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
|
||||||
|
|
||||||
let worktree_id = project.update(cx, |project, cx| {
|
let worktree_id = project.update(cx, |project, cx| {
|
||||||
project.worktrees().next().unwrap().read(cx).id()
|
project.worktrees(cx).next().unwrap().read(cx).id()
|
||||||
});
|
});
|
||||||
|
|
||||||
let handle = workspace
|
let handle = workspace
|
||||||
|
@ -354,6 +354,7 @@ struct UpdateObservationState {
|
|||||||
pub enum Event {
|
pub enum Event {
|
||||||
UpdatedEntries(UpdatedEntriesSet),
|
UpdatedEntries(UpdatedEntriesSet),
|
||||||
UpdatedGitRepositories(UpdatedGitRepositoriesSet),
|
UpdatedGitRepositories(UpdatedGitRepositoriesSet),
|
||||||
|
DeletedEntry(ProjectEntryId),
|
||||||
}
|
}
|
||||||
|
|
||||||
static EMPTY_PATH: &str = "";
|
static EMPTY_PATH: &str = "";
|
||||||
@ -738,10 +739,12 @@ impl Worktree {
|
|||||||
trash: bool,
|
trash: bool,
|
||||||
cx: &mut ModelContext<Worktree>,
|
cx: &mut ModelContext<Worktree>,
|
||||||
) -> Option<Task<Result<()>>> {
|
) -> Option<Task<Result<()>>> {
|
||||||
match self {
|
let task = match self {
|
||||||
Worktree::Local(this) => this.delete_entry(entry_id, trash, cx),
|
Worktree::Local(this) => this.delete_entry(entry_id, trash, cx),
|
||||||
Worktree::Remote(this) => this.delete_entry(entry_id, trash, cx),
|
Worktree::Remote(this) => this.delete_entry(entry_id, trash, cx),
|
||||||
}
|
}?;
|
||||||
|
cx.emit(Event::DeletedEntry(entry_id));
|
||||||
|
Some(task)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn rename_entry(
|
pub fn rename_entry(
|
||||||
@ -1208,25 +1211,10 @@ impl LocalWorktree {
|
|||||||
if let Some(repo_path) = repo.relativize(&snapshot, &path).log_err() {
|
if let Some(repo_path) = repo.relativize(&snapshot, &path).log_err() {
|
||||||
if let Some(git_repo) = snapshot.git_repositories.get(&*repo.work_directory) {
|
if let Some(git_repo) = snapshot.git_repositories.get(&*repo.work_directory) {
|
||||||
let git_repo = git_repo.repo_ptr.clone();
|
let git_repo = git_repo.repo_ptr.clone();
|
||||||
index_task = Some(cx.background_executor().spawn({
|
index_task = Some(
|
||||||
let fs = fs.clone();
|
cx.background_executor()
|
||||||
let abs_path = abs_path.clone();
|
.spawn(async move { git_repo.load_index_text(&repo_path) }),
|
||||||
async move {
|
);
|
||||||
let metadata = fs
|
|
||||||
.metadata(&abs_path)
|
|
||||||
.await
|
|
||||||
.with_context(|| {
|
|
||||||
format!("loading file and FS metadata for {abs_path:?}")
|
|
||||||
})
|
|
||||||
.log_err()
|
|
||||||
.flatten()?;
|
|
||||||
if metadata.is_dir || metadata.is_symlink {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
git_repo.load_index_text(&repo_path)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user