From 07120d47f92abb624de31d6e2657312aa43f8673 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 2 Mar 2022 18:07:32 +0100 Subject: [PATCH 01/16] Convert weak worktree handles into strong handles when sharing Co-Authored-By: Max Brunsfeld --- crates/project/src/project.rs | 43 +++++++++++++++++++++++++++------- crates/project/src/worktree.rs | 4 ++++ 2 files changed, 38 insertions(+), 9 deletions(-) diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index c789bad6c2..c11831d8ce 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -514,6 +514,7 @@ impl Project { } = &mut this.client_state { *is_shared = true; + for open_buffer in this.opened_buffers.values_mut() { match open_buffer { OpenBuffer::Strong(_) => {} @@ -525,6 +526,18 @@ impl Project { OpenBuffer::Loading(_) => unreachable!(), } } + + for worktree_handle in this.worktrees.iter_mut() { + match worktree_handle { + WorktreeHandle::Strong(_) => {} + WorktreeHandle::Weak(worktree) => { + if let Some(worktree) = worktree.upgrade(cx) { + *worktree_handle = WorktreeHandle::Strong(worktree); + } + } + } + } + remote_id_rx .borrow() .ok_or_else(|| anyhow!("no project id")) @@ -555,7 +568,7 @@ impl Project { pub fn unshare(&self, cx: &mut ModelContext) -> Task> { let rpc = self.client.clone(); cx.spawn(|this, mut cx| async move { - let project_id = this.update(&mut cx, |this, _| { + let project_id = this.update(&mut cx, |this, cx| { if let ProjectClientState::Local { is_shared, remote_id_rx, @@ -563,15 +576,27 @@ impl Project { } = &mut this.client_state { *is_shared = false; + for open_buffer in this.opened_buffers.values_mut() { match open_buffer { OpenBuffer::Strong(buffer) => { *open_buffer = OpenBuffer::Weak(buffer.downgrade()); } - OpenBuffer::Weak(_) => {} - OpenBuffer::Loading(_) => unreachable!(), + _ => {} } } + + for worktree_handle in this.worktrees.iter_mut() { + match worktree_handle { + WorktreeHandle::Strong(worktree) => { + if worktree.read(cx).is_weak() { + *worktree_handle = WorktreeHandle::Weak(worktree.downgrade()); + } + } + _ => {} + } + } + remote_id_rx .borrow() .ok_or_else(|| anyhow!("no project id")) @@ -2367,11 +2392,14 @@ impl Project { .detach(); } - let push_weak_handle = { + let push_strong_handle = { let worktree = worktree.read(cx); - worktree.is_local() && worktree.is_weak() + self.is_shared() || worktree.is_remote() }; - if push_weak_handle { + if push_strong_handle { + self.worktrees + .push(WorktreeHandle::Strong(worktree.clone())); + } else { cx.observe_release(&worktree, |this, cx| { this.worktrees .retain(|worktree| worktree.upgrade(cx).is_some()); @@ -2380,9 +2408,6 @@ impl Project { .detach(); self.worktrees .push(WorktreeHandle::Weak(worktree.downgrade())); - } else { - self.worktrees - .push(WorktreeHandle::Strong(worktree.clone())); } cx.notify(); } diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index 59498a3d75..661d1c9257 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -345,6 +345,10 @@ impl Worktree { matches!(self, Worktree::Local(_)) } + pub fn is_remote(&self) -> bool { + !self.is_local() + } + pub fn snapshot(&self) -> Snapshot { match self { Worktree::Local(worktree) => worktree.snapshot().snapshot, From a3c88922528a671ff430f346c747cd58b3b04ec8 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 2 Mar 2022 18:15:07 +0100 Subject: [PATCH 02/16] Move `SearchProject` to the background Co-Authored-By: Max Brunsfeld Co-Authored-By: Nathan Sobo --- crates/rpc/src/proto.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/rpc/src/proto.rs b/crates/rpc/src/proto.rs index 1bfb392db0..c5da067f17 100644 --- a/crates/rpc/src/proto.rs +++ b/crates/rpc/src/proto.rs @@ -190,8 +190,8 @@ messages!( (RegisterWorktree, Foreground), (RemoveProjectCollaborator, Foreground), (SaveBuffer, Foreground), - (SearchProject, Foreground), - (SearchProjectResponse, Foreground), + (SearchProject, Background), + (SearchProjectResponse, Background), (SendChannelMessage, Foreground), (SendChannelMessageResponse, Foreground), (ShareProject, Foreground), From 68cfce1fb89e72e49f45f3d272767ebf42f81302 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 2 Mar 2022 20:04:48 +0100 Subject: [PATCH 03/16] Rename `weak` to `visible` Co-Authored-By: Max Brunsfeld --- crates/diagnostics/src/diagnostics.rs | 2 +- crates/editor/src/editor.rs | 2 +- crates/file_finder/src/file_finder.rs | 6 +-- crates/project/src/project.rs | 63 +++++++++++------------ crates/project/src/worktree.rs | 33 ++++++------ crates/project_panel/src/project_panel.rs | 6 +-- crates/rpc/proto/zed.proto | 4 +- crates/search/src/project_search.rs | 2 +- crates/server/src/rpc.rs | 55 ++++++++++---------- crates/server/src/rpc/store.rs | 4 +- crates/workspace/src/workspace.rs | 2 +- crates/zed/src/zed.rs | 12 ++--- 12 files changed, 95 insertions(+), 96 deletions(-) diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index 60a57d3cca..5fbd5c2bcb 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -761,7 +761,7 @@ mod tests { project .update(cx, |project, cx| { - project.find_or_create_local_worktree("/test", false, cx) + project.find_or_create_local_worktree("/test", true, cx) }) .await .unwrap(); diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index f36edb9904..4b75627ef3 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -8217,7 +8217,7 @@ mod tests { let (worktree, relative_path) = project .update(cx, |project, cx| { - project.find_or_create_local_worktree("/file", false, cx) + project.find_or_create_local_worktree("/file", true, cx) }) .await .unwrap(); diff --git a/crates/file_finder/src/file_finder.rs b/crates/file_finder/src/file_finder.rs index 572ce7b473..552c7f4131 100644 --- a/crates/file_finder/src/file_finder.rs +++ b/crates/file_finder/src/file_finder.rs @@ -448,7 +448,7 @@ mod tests { params .project .update(cx, |project, cx| { - project.find_or_create_local_worktree("/root", false, cx) + project.find_or_create_local_worktree("/root", true, cx) }) .await .unwrap(); @@ -517,7 +517,7 @@ mod tests { params .project .update(cx, |project, cx| { - project.find_or_create_local_worktree("/dir", false, cx) + project.find_or_create_local_worktree("/dir", true, cx) }) .await .unwrap(); @@ -583,7 +583,7 @@ mod tests { params .project .update(cx, |project, cx| { - project.find_or_create_local_worktree("/root/the-parent-dir/the-file", false, cx) + project.find_or_create_local_worktree("/root/the-parent-dir/the-file", true, cx) }) .await .unwrap(); diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index c11831d8ce..4a385c7bb8 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -485,10 +485,10 @@ impl Project { ) -> impl 'a + Iterator> { self.worktrees.iter().filter_map(|worktree| { worktree.upgrade(cx).and_then(|worktree| { - if worktree.read(cx).is_weak() { - None - } else { + if worktree.read(cx).is_visible() { Some(worktree) + } else { + None } }) }) @@ -589,7 +589,7 @@ impl Project { for worktree_handle in this.worktrees.iter_mut() { match worktree_handle { WorktreeHandle::Strong(worktree) => { - if worktree.read(cx).is_weak() { + if !worktree.read(cx).is_visible() { *worktree_handle = WorktreeHandle::Weak(worktree.downgrade()); } } @@ -768,7 +768,7 @@ impl Project { } else { let worktree = this .update(&mut cx, |this, cx| { - this.create_local_worktree(&abs_path, true, cx) + this.create_local_worktree(&abs_path, false, cx) }) .await?; this.update(&mut cx, |this, cx| { @@ -793,7 +793,7 @@ impl Project { abs_path: PathBuf, cx: &mut ModelContext, ) -> Task> { - let worktree_task = self.find_or_create_local_worktree(&abs_path, false, cx); + let worktree_task = self.find_or_create_local_worktree(&abs_path, true, cx); cx.spawn(|this, mut cx| async move { let (worktree, path) = worktree_task.await?; worktree @@ -2301,14 +2301,14 @@ impl Project { pub fn find_or_create_local_worktree( &self, abs_path: impl AsRef, - weak: bool, + visible: bool, cx: &mut ModelContext, ) -> Task, PathBuf)>> { let abs_path = abs_path.as_ref(); if let Some((tree, relative_path)) = self.find_local_worktree(abs_path, cx) { Task::ready(Ok((tree.clone(), relative_path.into()))) } else { - let worktree = self.create_local_worktree(abs_path, weak, cx); + let worktree = self.create_local_worktree(abs_path, visible, cx); cx.foreground() .spawn(async move { Ok((worktree.await?, PathBuf::new())) }) } @@ -2341,14 +2341,14 @@ impl Project { fn create_local_worktree( &self, abs_path: impl AsRef, - weak: bool, + visible: bool, cx: &mut ModelContext, ) -> Task>> { let fs = self.fs.clone(); let client = self.client.clone(); let path = Arc::from(abs_path.as_ref()); cx.spawn(|project, mut cx| async move { - let worktree = Worktree::local(client.clone(), path, weak, fs, &mut cx).await?; + let worktree = Worktree::local(client.clone(), path, visible, fs, &mut cx).await?; let (remote_project_id, is_shared) = project.update(&mut cx, |project, cx| { project.add_worktree(&worktree, cx); @@ -2394,7 +2394,7 @@ impl Project { let push_strong_handle = { let worktree = worktree.read(cx); - self.is_shared() || worktree.is_remote() + self.is_shared() || worktree.is_visible() || worktree.is_remote() }; if push_strong_handle { self.worktrees @@ -2627,7 +2627,7 @@ impl Project { root_name: envelope.payload.root_name, entries: Default::default(), diagnostic_summaries: Default::default(), - weak: envelope.payload.weak, + visible: envelope.payload.visible, }; let (worktree, load_task) = Worktree::remote(remote_id, replica_id, worktree, client, cx); @@ -3361,7 +3361,7 @@ impl Project { ) -> impl 'a + Future> { let worktrees = self .worktrees(cx) - .filter(|worktree| !worktree.read(cx).is_weak()) + .filter(|worktree| worktree.read(cx).is_visible()) .collect::>(); let include_root_name = worktrees.len() > 1; let candidate_sets = worktrees @@ -3656,7 +3656,7 @@ mod tests { let (tree, _) = project .update(cx, |project, cx| { - project.find_or_create_local_worktree(&root_link_path, false, cx) + project.find_or_create_local_worktree(&root_link_path, true, cx) }) .await .unwrap(); @@ -3725,7 +3725,7 @@ mod tests { let (tree, _) = project .update(cx, |project, cx| { - project.find_or_create_local_worktree("/dir", false, cx) + project.find_or_create_local_worktree("/dir", true, cx) }) .await .unwrap(); @@ -3823,7 +3823,7 @@ mod tests { let project = Project::test(Arc::new(RealFs), cx); let (tree, _) = project .update(cx, |project, cx| { - project.find_or_create_local_worktree(&dir.path(), false, cx) + project.find_or_create_local_worktree(&dir.path(), true, cx) }) .await .unwrap(); @@ -3871,7 +3871,7 @@ mod tests { let (tree, _) = project .update(cx, |project, cx| { - project.find_or_create_local_worktree("/dir/b.rs", false, cx) + project.find_or_create_local_worktree("/dir/b.rs", true, cx) }) .await .unwrap(); @@ -3928,16 +3928,13 @@ mod tests { assert_eq!(definition.range.to_offset(target_buffer), 9..10); assert_eq!( list_worktrees(&project, cx), - [("/dir/b.rs".as_ref(), false), ("/dir/a.rs".as_ref(), true)] + [("/dir/b.rs".as_ref(), true), ("/dir/a.rs".as_ref(), false)] ); drop(definition); }); cx.read(|cx| { - assert_eq!( - list_worktrees(&project, cx), - [("/dir/b.rs".as_ref(), false)] - ); + assert_eq!(list_worktrees(&project, cx), [("/dir/b.rs".as_ref(), true)]); }); fn list_worktrees<'a>( @@ -3951,7 +3948,7 @@ mod tests { let worktree = worktree.read(cx); ( worktree.as_local().unwrap().abs_path().as_ref(), - worktree.is_weak(), + worktree.is_visible(), ) }) .collect::>() @@ -3972,7 +3969,7 @@ mod tests { let project = Project::test(fs.clone(), cx); let worktree_id = project .update(cx, |p, cx| { - p.find_or_create_local_worktree("/dir", false, cx) + p.find_or_create_local_worktree("/dir", true, cx) }) .await .unwrap() @@ -4010,7 +4007,7 @@ mod tests { let project = Project::test(fs.clone(), cx); let worktree_id = project .update(cx, |p, cx| { - p.find_or_create_local_worktree("/dir/file1", false, cx) + p.find_or_create_local_worktree("/dir/file1", true, cx) }) .await .unwrap() @@ -4054,7 +4051,7 @@ mod tests { let (tree, _) = project .update(cx, |p, cx| { - p.find_or_create_local_worktree(dir.path(), false, cx) + p.find_or_create_local_worktree(dir.path(), true, cx) }) .await .unwrap(); @@ -4091,7 +4088,7 @@ mod tests { Worktree::remote( 1, 1, - initial_snapshot.to_proto(&Default::default(), Default::default()), + initial_snapshot.to_proto(&Default::default(), true), rpc.clone(), cx, ) @@ -4200,7 +4197,7 @@ mod tests { let project = Project::test(fs.clone(), cx); let worktree_id = project .update(cx, |p, cx| { - p.find_or_create_local_worktree("/the-dir", false, cx) + p.find_or_create_local_worktree("/the-dir", true, cx) }) .await .unwrap() @@ -4250,7 +4247,7 @@ mod tests { let project = Project::test(Arc::new(RealFs), cx); let (worktree, _) = project .update(cx, |p, cx| { - p.find_or_create_local_worktree(dir.path(), false, cx) + p.find_or_create_local_worktree(dir.path(), true, cx) }) .await .unwrap(); @@ -4384,7 +4381,7 @@ mod tests { let project = Project::test(Arc::new(RealFs), cx); let (worktree, _) = project .update(cx, |p, cx| { - p.find_or_create_local_worktree(dir.path(), false, cx) + p.find_or_create_local_worktree(dir.path(), true, cx) }) .await .unwrap(); @@ -4493,7 +4490,7 @@ mod tests { let project = Project::test(fs.clone(), cx); let (worktree, _) = project .update(cx, |p, cx| { - p.find_or_create_local_worktree("/the-dir", false, cx) + p.find_or_create_local_worktree("/the-dir", true, cx) }) .await .unwrap(); @@ -4761,7 +4758,7 @@ mod tests { let (tree, _) = project .update(cx, |project, cx| { - project.find_or_create_local_worktree("/dir", false, cx) + project.find_or_create_local_worktree("/dir", true, cx) }) .await .unwrap(); @@ -4889,7 +4886,7 @@ mod tests { let project = Project::test(fs.clone(), cx); let (tree, _) = project .update(cx, |project, cx| { - project.find_or_create_local_worktree("/dir", false, cx) + project.find_or_create_local_worktree("/dir", true, cx) }) .await .unwrap(); diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index 661d1c9257..ffe229abf2 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -71,7 +71,7 @@ pub struct LocalWorktree { queued_operations: Vec<(u64, Operation)>, client: Arc, fs: Arc, - weak: bool, + visible: bool, } pub struct RemoteWorktree { @@ -83,7 +83,7 @@ pub struct RemoteWorktree { replica_id: ReplicaId, queued_operations: Vec<(u64, Operation)>, diagnostic_summaries: TreeMap, - weak: bool, + visible: bool, } #[derive(Clone)] @@ -169,11 +169,12 @@ impl Worktree { pub async fn local( client: Arc, path: impl Into>, - weak: bool, + visible: bool, fs: Arc, cx: &mut AsyncAppContext, ) -> Result> { - let (tree, scan_states_tx) = LocalWorktree::new(client, path, weak, fs.clone(), cx).await?; + let (tree, scan_states_tx) = + LocalWorktree::new(client, path, visible, fs.clone(), cx).await?; tree.update(cx, |tree, cx| { let tree = tree.as_local_mut().unwrap(); let abs_path = tree.abs_path().clone(); @@ -203,7 +204,7 @@ impl Worktree { .map(|c| c.to_ascii_lowercase()) .collect(); let root_name = worktree.root_name.clone(); - let weak = worktree.weak; + let visible = worktree.visible; let snapshot = Snapshot { id: WorktreeId(remote_id as usize), root_name, @@ -236,7 +237,7 @@ impl Worktree { ) }), ), - weak, + visible, }) }); @@ -356,10 +357,10 @@ impl Worktree { } } - pub fn is_weak(&self) -> bool { + pub fn is_visible(&self) -> bool { match self { - Worktree::Local(worktree) => worktree.weak, - Worktree::Remote(worktree) => worktree.weak, + Worktree::Local(worktree) => worktree.visible, + Worktree::Remote(worktree) => worktree.visible, } } @@ -458,7 +459,7 @@ impl LocalWorktree { async fn new( client: Arc, path: impl Into>, - weak: bool, + visible: bool, fs: Arc, cx: &mut AsyncAppContext, ) -> Result<(ModelHandle, UnboundedSender)> { @@ -525,7 +526,7 @@ impl LocalWorktree { queued_operations: Default::default(), client, fs, - weak, + visible, }; cx.spawn_weak(|this, mut cx| async move { @@ -738,7 +739,7 @@ impl LocalWorktree { worktree_id: self.id().to_proto(), root_name: self.root_name().to_string(), authorized_logins: self.authorized_logins(), - weak: self.weak, + visible: self.visible, }; cx.spawn(|this, mut cx| async move { let response = client.request(register_message).await; @@ -1028,7 +1029,7 @@ impl LocalSnapshot { pub(crate) fn to_proto( &self, diagnostic_summaries: &TreeMap, - weak: bool, + visible: bool, ) -> proto::Worktree { let root_name = self.root_name.clone(); proto::Worktree { @@ -1044,7 +1045,7 @@ impl LocalSnapshot { .iter() .map(|(path, summary)| summary.to_proto(&path.0)) .collect(), - weak, + visible, } } @@ -2468,7 +2469,7 @@ mod tests { let tree = Worktree::local( client, Arc::from(Path::new("/root")), - false, + true, fs, &mut cx.to_async(), ) @@ -2511,7 +2512,7 @@ mod tests { let tree = Worktree::local( client, dir.path(), - false, + true, Arc::new(RealFs), &mut cx.to_async(), ) diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 1317ceb7ec..76d320b894 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -327,7 +327,7 @@ impl ProjectPanel { .project .read(cx) .worktrees(cx) - .filter(|worktree| !worktree.read(cx).is_weak()); + .filter(|worktree| worktree.read(cx).is_visible()); self.visible_entries.clear(); let mut entry_ix = 0; @@ -642,7 +642,7 @@ mod tests { }); let (root1, _) = project .update(cx, |project, cx| { - project.find_or_create_local_worktree("/root1", false, cx) + project.find_or_create_local_worktree("/root1", true, cx) }) .await .unwrap(); @@ -651,7 +651,7 @@ mod tests { .await; let (root2, _) = project .update(cx, |project, cx| { - project.find_or_create_local_worktree("/root2", false, cx) + project.find_or_create_local_worktree("/root2", true, cx) }) .await .unwrap(); diff --git a/crates/rpc/proto/zed.proto b/crates/rpc/proto/zed.proto index 18e3256026..4d55208872 100644 --- a/crates/rpc/proto/zed.proto +++ b/crates/rpc/proto/zed.proto @@ -134,7 +134,7 @@ message RegisterWorktree { uint64 worktree_id = 2; string root_name = 3; repeated string authorized_logins = 4; - bool weak = 5; + bool visible = 5; } message UnregisterWorktree { @@ -502,7 +502,7 @@ message Worktree { string root_name = 2; repeated Entry entries = 3; repeated DiagnosticSummary diagnostic_summaries = 4; - bool weak = 5; + bool visible = 5; } message File { diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index c345172d04..3e649fa4dd 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -735,7 +735,7 @@ mod tests { let project = Project::test(fs.clone(), cx); let (tree, _) = project .update(cx, |project, cx| { - project.find_or_create_local_worktree("/dir", false, cx) + project.find_or_create_local_worktree("/dir", true, cx) }) .await .unwrap(); diff --git a/crates/server/src/rpc.rs b/crates/server/src/rpc.rs index 52c2e43832..8a1a78b356 100644 --- a/crates/server/src/rpc.rs +++ b/crates/server/src/rpc.rs @@ -351,7 +351,7 @@ impl Server { .values() .cloned() .collect(), - weak: worktree.weak, + visible: worktree.visible, }) }) .collect(); @@ -440,7 +440,7 @@ impl Server { Worktree { authorized_user_ids: contact_user_ids.clone(), root_name: request.payload.root_name.clone(), - weak: request.payload.weak, + visible: request.payload.visible, }, )?; } @@ -1070,7 +1070,7 @@ mod tests { }); let (worktree_a, _) = project_a .update(cx_a, |p, cx| { - p.find_or_create_local_worktree("/a", false, cx) + p.find_or_create_local_worktree("/a", true, cx) }) .await .unwrap(); @@ -1202,7 +1202,7 @@ mod tests { }); let (worktree_a, _) = project_a .update(cx_a, |p, cx| { - p.find_or_create_local_worktree("/a", false, cx) + p.find_or_create_local_worktree("/a", true, cx) }) .await .unwrap(); @@ -1303,7 +1303,7 @@ mod tests { }); let (worktree_a, _) = project_a .update(cx_a, |p, cx| { - p.find_or_create_local_worktree("/a", false, cx) + p.find_or_create_local_worktree("/a", true, cx) }) .await .unwrap(); @@ -1475,7 +1475,7 @@ mod tests { }); let (worktree_a, _) = project_a .update(cx_a, |p, cx| { - p.find_or_create_local_worktree("/dir", false, cx) + p.find_or_create_local_worktree("/dir", true, cx) }) .await .unwrap(); @@ -1557,7 +1557,7 @@ mod tests { }); let (worktree_a, _) = project_a .update(cx_a, |p, cx| { - p.find_or_create_local_worktree("/dir", false, cx) + p.find_or_create_local_worktree("/dir", true, cx) }) .await .unwrap(); @@ -1638,7 +1638,7 @@ mod tests { }); let (worktree_a, _) = project_a .update(cx_a, |p, cx| { - p.find_or_create_local_worktree("/dir", false, cx) + p.find_or_create_local_worktree("/dir", true, cx) }) .await .unwrap(); @@ -1717,7 +1717,7 @@ mod tests { }); let (worktree_a, _) = project_a .update(cx_a, |p, cx| { - p.find_or_create_local_worktree("/dir", false, cx) + p.find_or_create_local_worktree("/dir", true, cx) }) .await .unwrap(); @@ -1790,7 +1790,7 @@ mod tests { }); let (worktree_a, _) = project_a .update(cx_a, |p, cx| { - p.find_or_create_local_worktree("/a", false, cx) + p.find_or_create_local_worktree("/a", true, cx) }) .await .unwrap(); @@ -1878,7 +1878,7 @@ mod tests { }); let (worktree_a, _) = project_a .update(cx_a, |p, cx| { - p.find_or_create_local_worktree("/a", false, cx) + p.find_or_create_local_worktree("/a", true, cx) }) .await .unwrap(); @@ -2104,7 +2104,7 @@ mod tests { }); let (worktree_a, _) = project_a .update(cx_a, |p, cx| { - p.find_or_create_local_worktree("/a", false, cx) + p.find_or_create_local_worktree("/a", true, cx) }) .await .unwrap(); @@ -2303,7 +2303,7 @@ mod tests { }); let (worktree_a, _) = project_a .update(cx_a, |p, cx| { - p.find_or_create_local_worktree("/a", false, cx) + p.find_or_create_local_worktree("/a", true, cx) }) .await .unwrap(); @@ -2409,7 +2409,7 @@ mod tests { }); let (worktree_a, _) = project_a .update(cx_a, |p, cx| { - p.find_or_create_local_worktree("/root-1", false, cx) + p.find_or_create_local_worktree("/root-1", true, cx) }) .await .unwrap(); @@ -2545,7 +2545,7 @@ mod tests { }); let (worktree_a, _) = project_a .update(cx_a, |p, cx| { - p.find_or_create_local_worktree("/root-1", false, cx) + p.find_or_create_local_worktree("/root-1", true, cx) }) .await .unwrap(); @@ -2666,7 +2666,7 @@ mod tests { let (worktree_1, _) = project_a .update(cx_a, |p, cx| { - p.find_or_create_local_worktree("/root-1", false, cx) + p.find_or_create_local_worktree("/root-1", true, cx) }) .await .unwrap(); @@ -2675,7 +2675,7 @@ mod tests { .await; let (worktree_2, _) = project_a .update(cx_a, |p, cx| { - p.find_or_create_local_worktree("/root-2", false, cx) + p.find_or_create_local_worktree("/root-2", true, cx) }) .await .unwrap(); @@ -2775,7 +2775,7 @@ mod tests { }); let (worktree_a, _) = project_a .update(cx_a, |p, cx| { - p.find_or_create_local_worktree("/root-1", false, cx) + p.find_or_create_local_worktree("/root-1", true, cx) }) .await .unwrap(); @@ -2921,7 +2921,7 @@ mod tests { }); let (worktree_a, _) = project_a .update(cx_a, |p, cx| { - p.find_or_create_local_worktree("/code/crate-1", false, cx) + p.find_or_create_local_worktree("/code/crate-1", true, cx) }) .await .unwrap(); @@ -3051,7 +3051,7 @@ mod tests { let (worktree_a, _) = project_a .update(cx_a, |p, cx| { - p.find_or_create_local_worktree("/root", false, cx) + p.find_or_create_local_worktree("/root", true, cx) }) .await .unwrap(); @@ -3155,7 +3155,7 @@ mod tests { }); let (worktree_a, _) = project_a .update(cx_a, |p, cx| { - p.find_or_create_local_worktree("/a", false, cx) + p.find_or_create_local_worktree("/a", true, cx) }) .await .unwrap(); @@ -3391,7 +3391,7 @@ mod tests { }); let (worktree_a, _) = project_a .update(cx_a, |p, cx| { - p.find_or_create_local_worktree("/dir", false, cx) + p.find_or_create_local_worktree("/dir", true, cx) }) .await .unwrap(); @@ -4005,7 +4005,7 @@ mod tests { }); let (worktree_a, _) = project_a .update(cx_a, |p, cx| { - p.find_or_create_local_worktree("/a", false, cx) + p.find_or_create_local_worktree("/a", true, cx) }) .await .unwrap(); @@ -4165,7 +4165,7 @@ mod tests { let (collab_worktree, _) = host_project .update(&mut host_cx, |project, cx| { - project.find_or_create_local_worktree("/_collab", false, cx) + project.find_or_create_local_worktree("/_collab", true, cx) }) .await .unwrap(); @@ -4662,7 +4662,7 @@ mod tests { log::info!("Host: find/create local worktree {:?}", path); project .update(&mut cx, |project, cx| { - project.find_or_create_local_worktree(path, false, cx) + project.find_or_create_local_worktree(path, true, cx) }) .await .unwrap(); @@ -4674,7 +4674,7 @@ mod tests { .update(&mut cx, |project, cx| { project.find_or_create_local_worktree( file.clone(), - false, + true, cx, ) }) @@ -4769,7 +4769,8 @@ mod tests { .worktrees(&cx) .filter(|worktree| { let worktree = worktree.read(cx); - !worktree.is_weak() && worktree.entries(false).any(|e| e.is_file()) + worktree.is_visible() + && worktree.entries(false).any(|e| e.is_file()) }) .choose(&mut *rng.lock()) }) { diff --git a/crates/server/src/rpc/store.rs b/crates/server/src/rpc/store.rs index a7e89f3bf5..d51b43610e 100644 --- a/crates/server/src/rpc/store.rs +++ b/crates/server/src/rpc/store.rs @@ -30,7 +30,7 @@ pub struct Project { pub struct Worktree { pub authorized_user_ids: Vec, pub root_name: String, - pub weak: bool, + pub visible: bool, } #[derive(Default)] @@ -204,7 +204,7 @@ impl Store { let mut worktree_root_names = project .worktrees .values() - .filter(|worktree| !worktree.weak) + .filter(|worktree| worktree.visible) .map(|worktree| worktree.root_name.clone()) .collect::>(); worktree_root_names.sort_unstable(); diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 85d5566e04..2691516e2f 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -737,7 +737,7 @@ impl Workspace { cx: &mut ViewContext, ) -> Task> { let entry = self.project().update(cx, |project, cx| { - project.find_or_create_local_worktree(abs_path, false, cx) + project.find_or_create_local_worktree(abs_path, true, cx) }); cx.spawn(|_, cx| async move { let (worktree, path) = entry.await?; diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 558bf87fcb..e5312a5512 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -257,7 +257,7 @@ mod tests { params .project .update(cx, |project, cx| { - project.find_or_create_local_worktree("/root", false, cx) + project.find_or_create_local_worktree("/root", true, cx) }) .await .unwrap(); @@ -370,7 +370,7 @@ mod tests { params .project .update(cx, |project, cx| { - project.find_or_create_local_worktree("/dir1", false, cx) + project.find_or_create_local_worktree("/dir1", true, cx) }) .await .unwrap(); @@ -445,7 +445,7 @@ mod tests { params .project .update(cx, |project, cx| { - project.find_or_create_local_worktree("/root", false, cx) + project.find_or_create_local_worktree("/root", true, cx) }) .await .unwrap(); @@ -492,7 +492,7 @@ mod tests { params .project .update(cx, |project, cx| { - project.find_or_create_local_worktree("/root", false, cx) + project.find_or_create_local_worktree("/root", true, cx) }) .await .unwrap(); @@ -644,7 +644,7 @@ mod tests { params .project .update(cx, |project, cx| { - project.find_or_create_local_worktree("/root", false, cx) + project.find_or_create_local_worktree("/root", true, cx) }) .await .unwrap(); @@ -707,7 +707,7 @@ mod tests { params .project .update(cx, |project, cx| { - project.find_or_create_local_worktree("/root", false, cx) + project.find_or_create_local_worktree("/root", true, cx) }) .await .unwrap(); From ca920e155207830f3644acb20ee5cc4e8ca068c8 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 2 Mar 2022 13:09:38 -0800 Subject: [PATCH 04/16] Rename strong_worktrees -> visible_worktrees --- crates/project/src/project.rs | 6 +++--- crates/project_symbols/src/project_symbols.rs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 4a385c7bb8..99a86d97c0 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -479,7 +479,7 @@ impl Project { .filter_map(move |worktree| worktree.upgrade(cx)) } - pub fn strong_worktrees<'a>( + pub fn visible_worktrees<'a>( &'a self, cx: &'a AppContext, ) -> impl 'a + Iterator> { @@ -2065,7 +2065,7 @@ impl Project { ) -> Task, Vec>>>> { if self.is_local() { let snapshots = self - .strong_worktrees(cx) + .visible_worktrees(cx) .filter_map(|tree| { let tree = tree.read(cx).as_local()?; Some(tree.snapshot()) @@ -2735,7 +2735,7 @@ impl Project { buffer.update(cx, |buffer, cx| buffer.apply_ops(ops, cx))?; } OpenBuffer::Loading(operations) => operations.extend_from_slice(&ops), - _ => unreachable!(), + OpenBuffer::Weak(_) => {} }, hash_map::Entry::Vacant(e) => { e.insert(OpenBuffer::Loading(ops)); diff --git a/crates/project_symbols/src/project_symbols.rs b/crates/project_symbols/src/project_symbols.rs index 174239ff52..6de4814335 100644 --- a/crates/project_symbols/src/project_symbols.rs +++ b/crates/project_symbols/src/project_symbols.rs @@ -270,7 +270,7 @@ impl ProjectSymbolsView { range.end = cmp::min(range.end, view.matches.len()); let show_worktree_root_name = - view.project.read(cx).strong_worktrees(cx).count() > 1; + view.project.read(cx).visible_worktrees(cx).count() > 1; items.extend(view.matches[range].iter().enumerate().map(move |(ix, m)| { view.render_match(m, start + ix, show_worktree_root_name, cx) })); From d4ae38fcee6ba1d726a5669d4bc9ad69bc157127 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 2 Mar 2022 13:12:03 -0800 Subject: [PATCH 05/16] Check projects' buffers have unique absolute paths in random collab test --- crates/project/src/project.rs | 78 ++++++++++++++++++++++++++--------- crates/server/src/rpc.rs | 32 +++++++------- 2 files changed, 77 insertions(+), 33 deletions(-) diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 99a86d97c0..c3915cc8b4 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -413,6 +413,65 @@ impl Project { &self.languages } + #[cfg(any(test, feature = "test-support"))] + pub fn check_invariants(&self, cx: &AppContext) { + if self.is_local() { + let buffers = self.buffers(cx); + for (i, buffer) in buffers.iter().enumerate() { + let buffer = buffer.read(cx); + let path = buffer.file().unwrap().as_local().unwrap().abs_path(cx); + for other_buffer in &buffers[0..i] { + let other_buffer = other_buffer.read(cx); + let other_path = other_buffer + .file() + .unwrap() + .as_local() + .unwrap() + .abs_path(cx); + if other_path == path { + panic!( + "buffers {} and {} have the same absolute path: {:?}", + buffer.remote_id(), + other_buffer.remote_id(), + path, + ); + } + } + } + } + } + + #[cfg(any(test, feature = "test-support"))] + pub fn buffers(&self, cx: &AppContext) -> Vec> { + self.opened_buffers + .values() + .filter_map(|buffer| match buffer { + OpenBuffer::Strong(buffer) => Some(buffer.clone()), + OpenBuffer::Weak(buffer) => buffer.upgrade(cx), + OpenBuffer::Loading(_) => None, + }) + .collect() + } + + #[cfg(any(test, feature = "test-support"))] + pub fn has_open_buffer(&self, path: impl Into, cx: &AppContext) -> bool { + let path = path.into(); + if let Some(worktree) = self.worktree_for_id(path.worktree_id, cx) { + self.opened_buffers.iter().any(|(_, buffer)| { + if let Some(buffer) = buffer.upgrade(cx) { + if let Some(file) = File::from_dyn(buffer.read(cx).file()) { + if file.worktree == worktree && file.path() == &path.path { + return true; + } + } + } + false + }) + } else { + false + } + } + pub fn fs(&self) -> &Arc { &self.fs } @@ -811,25 +870,6 @@ impl Project { }) } - #[cfg(any(test, feature = "test-support"))] - pub fn has_open_buffer(&self, path: impl Into, cx: &AppContext) -> bool { - let path = path.into(); - if let Some(worktree) = self.worktree_for_id(path.worktree_id, cx) { - self.opened_buffers.iter().any(|(_, buffer)| { - if let Some(buffer) = buffer.upgrade(cx) { - if let Some(file) = File::from_dyn(buffer.read(cx).file()) { - if file.worktree == worktree && file.path() == &path.path { - return true; - } - } - } - false - }) - } else { - false - } - } - pub fn get_open_buffer( &mut self, path: &ProjectPath, diff --git a/crates/server/src/rpc.rs b/crates/server/src/rpc.rs index 8a1a78b356..a41f3a6108 100644 --- a/crates/server/src/rpc.rs +++ b/crates/server/src/rpc.rs @@ -4246,6 +4246,12 @@ mod tests { .collect::>() }); + host_client + .project + .as_ref() + .unwrap() + .read_with(&host_cx, |project, cx| project.check_invariants(cx)); + for (guest_client, mut guest_cx) in clients.into_iter() { let guest_id = guest_client.client.id(); let worktree_snapshots = @@ -4287,18 +4293,6 @@ mod tests { ); } - guest_client - .project - .as_ref() - .unwrap() - .read_with(&guest_cx, |project, cx| { - assert!( - !project.has_deferred_operations(cx), - "guest {} has deferred operations", - guest_id, - ); - }); - for guest_buffer in &guest_client.buffers { let buffer_id = guest_buffer.read_with(&guest_cx, |buffer, _| buffer.remote_id()); let host_buffer = host_project.read_with(&host_cx, |project, cx| { @@ -4307,14 +4301,24 @@ mod tests { guest_id, guest_client.peer_id, buffer_id )) }); + let path = host_buffer + .read_with(&host_cx, |buffer, cx| buffer.file().unwrap().full_path(cx)); + + assert_eq!( + guest_buffer.read_with(&guest_cx, |buffer, _| buffer.deferred_ops_len()), + 0, + "guest {}, buffer {}, path {:?} has deferred operations", + guest_id, + buffer_id, + path, + ); assert_eq!( guest_buffer.read_with(&guest_cx, |buffer, _| buffer.text()), host_buffer.read_with(&host_cx, |buffer, _| buffer.text()), "guest {}, buffer {}, path {:?}, differs from the host's buffer", guest_id, buffer_id, - host_buffer - .read_with(&host_cx, |buffer, cx| buffer.file().unwrap().full_path(cx)) + path ); } From 530f15b46bb562eacd9659e926773ac066df4c78 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 2 Mar 2022 13:12:56 -0800 Subject: [PATCH 06/16] Tweak log format in random collab test --- crates/server/src/rpc.rs | 36 ++++++++++++++++++++++++------------ 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/crates/server/src/rpc.rs b/crates/server/src/rpc.rs index a41f3a6108..6330e39aef 100644 --- a/crates/server/src/rpc.rs +++ b/crates/server/src/rpc.rs @@ -4686,7 +4686,12 @@ mod tests { .unwrap(); let project_path = worktree.read_with(&cx, |worktree, _| (worktree.id(), path)); - log::info!("Host: opening path {:?}, {:?}", file, project_path); + log::info!( + "Host: opening path {:?}, worktree {}, relative_path {:?}", + file, + project_path.0, + project_path.1 + ); let buffer = project .update(&mut cx, |project, cx| { project.open_buffer(project_path, cx) @@ -4798,11 +4803,11 @@ mod tests { ) }); log::info!( - "Guest {}: opening path in worktree {:?} {:?} {:?}", + "Guest {}: opening path {:?} in worktree {} ({})", guest_id, + project_path.1, project_path.0, worktree_root_name, - project_path.1 ); let buffer = project .update(&mut cx, |project, cx| { @@ -4811,11 +4816,11 @@ mod tests { .await .unwrap(); log::info!( - "Guest {}: path in worktree {:?} {:?} {:?} opened with buffer id {:?}", + "Guest {}: opened path {:?} in worktree {} ({}) with buffer id {}", guest_id, + project_path.1, project_path.0, worktree_root_name, - project_path.1, buffer.read_with(&cx, |buffer, _| buffer.remote_id()) ); self.buffers.insert(buffer.clone()); @@ -4846,8 +4851,9 @@ mod tests { 10..=19 => { let completions = project.update(&mut cx, |project, cx| { log::info!( - "Guest {}: requesting completions for buffer {:?}", + "Guest {}: requesting completions for buffer {} ({:?})", guest_id, + buffer.read(cx).remote_id(), buffer.read(cx).file().unwrap().full_path(cx) ); let offset = rng.lock().gen_range(0..=buffer.read(cx).len()); @@ -4866,8 +4872,9 @@ mod tests { 20..=29 => { let code_actions = project.update(&mut cx, |project, cx| { log::info!( - "Guest {}: requesting code actions for buffer {:?}", + "Guest {}: requesting code actions for buffer {} ({:?})", guest_id, + buffer.read(cx).remote_id(), buffer.read(cx).file().unwrap().full_path(cx) ); let range = buffer.read(cx).random_byte_range(0, &mut *rng.lock()); @@ -4886,8 +4893,9 @@ mod tests { 30..=39 if buffer.read_with(&cx, |buffer, _| buffer.is_dirty()) => { let (requested_version, save) = buffer.update(&mut cx, |buffer, cx| { log::info!( - "Guest {}: saving buffer {:?}", + "Guest {}: saving buffer {} ({:?})", guest_id, + buffer.remote_id(), buffer.file().unwrap().full_path(cx) ); (buffer.version(), buffer.save(cx)) @@ -4909,8 +4917,9 @@ mod tests { 40..=44 => { let prepare_rename = project.update(&mut cx, |project, cx| { log::info!( - "Guest {}: preparing rename for buffer {:?}", + "Guest {}: preparing rename for buffer {} ({:?})", guest_id, + buffer.read(cx).remote_id(), buffer.read(cx).file().unwrap().full_path(cx) ); let offset = rng.lock().gen_range(0..=buffer.read(cx).len()); @@ -4929,8 +4938,9 @@ mod tests { 45..=49 => { let definitions = project.update(&mut cx, |project, cx| { log::info!( - "Guest {}: requesting definitions for buffer {:?}", + "Guest {}: requesting definitions for buffer {} ({:?})", guest_id, + buffer.read(cx).remote_id(), buffer.read(cx).file().unwrap().full_path(cx) ); let offset = rng.lock().gen_range(0..=buffer.read(cx).len()); @@ -4950,8 +4960,9 @@ mod tests { 50..=54 => { let highlights = project.update(&mut cx, |project, cx| { log::info!( - "Guest {}: requesting highlights for buffer {:?}", + "Guest {}: requesting highlights for buffer {} ({:?})", guest_id, + buffer.read(cx).remote_id(), buffer.read(cx).file().unwrap().full_path(cx) ); let offset = rng.lock().gen_range(0..=buffer.read(cx).len()); @@ -4986,8 +4997,9 @@ mod tests { _ => { buffer.update(&mut cx, |buffer, cx| { log::info!( - "Guest {}: updating buffer {:?}", + "Guest {}: updating buffer {} ({:?})", guest_id, + buffer.remote_id(), buffer.file().unwrap().full_path(cx) ); buffer.randomly_edit(&mut *rng.lock(), 5, cx) From 53327e2bf06ab9c850c1fe10953d9e39cdf9932d Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 3 Mar 2022 10:10:53 +0100 Subject: [PATCH 07/16] Ensure worktree is registered/shared synchronously --- crates/project/src/project.rs | 11 ++--- crates/project/src/worktree.rs | 75 ++++++++++++++++++++-------------- crates/server/src/rpc.rs | 17 +++++--- 3 files changed, 61 insertions(+), 42 deletions(-) diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index c3915cc8b4..015b5e6ccc 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -2396,17 +2396,18 @@ impl Project { }); if let Some(project_id) = remote_project_id { - worktree - .update(&mut cx, |worktree, cx| { - worktree.as_local_mut().unwrap().register(project_id, cx) - }) - .await?; if is_shared { worktree .update(&mut cx, |worktree, cx| { worktree.as_local_mut().unwrap().share(project_id, cx) }) .await?; + } else { + worktree + .update(&mut cx, |worktree, cx| { + worktree.as_local_mut().unwrap().register(project_id, cx) + }) + .await?; } } diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index ffe229abf2..a9ce90ce61 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -741,8 +741,9 @@ impl LocalWorktree { authorized_logins: self.authorized_logins(), visible: self.visible, }; + let request = client.request(register_message); cx.spawn(|this, mut cx| async move { - let response = client.request(register_message).await; + let response = request.await; this.update(&mut cx, |this, _| { let worktree = this.as_local_mut().unwrap(); match response { @@ -759,44 +760,49 @@ impl LocalWorktree { }) } - pub fn share( - &mut self, - project_id: u64, - cx: &mut ModelContext, - ) -> impl Future> { + pub fn share(&mut self, project_id: u64, cx: &mut ModelContext) -> Task> { + let register = self.register(project_id, cx); let (mut share_tx, mut share_rx) = oneshot::channel(); + let (snapshots_to_send_tx, snapshots_to_send_rx) = + smol::channel::unbounded::(); if self.share.is_some() { let _ = share_tx.try_send(Ok(())); } else { let snapshot = self.snapshot(); let rpc = self.client.clone(); let worktree_id = cx.model_id() as u64; - let (snapshots_to_send_tx, snapshots_to_send_rx) = - smol::channel::unbounded::(); + let maintain_remote_snapshot = cx.background().spawn({ let rpc = rpc.clone(); - let snapshot = snapshot.clone(); let diagnostic_summaries = self.diagnostic_summaries.clone(); async move { - if let Err(error) = rpc - .request(proto::UpdateWorktree { - project_id, - worktree_id, - root_name: snapshot.root_name().to_string(), - updated_entries: snapshot - .entries_by_path - .iter() - .filter(|e| !e.is_ignored) - .map(Into::into) - .collect(), - removed_entries: Default::default(), - }) - .await - { - let _ = share_tx.try_send(Err(error)); - return Err(anyhow!("failed to send initial update worktree")); - } else { - let _ = share_tx.try_send(Ok(())); + match snapshots_to_send_rx.recv().await { + Ok(snapshot) => { + if let Err(error) = rpc + .request(proto::UpdateWorktree { + project_id, + worktree_id, + root_name: snapshot.root_name().to_string(), + updated_entries: snapshot + .entries_by_path + .iter() + .filter(|e| !e.is_ignored) + .map(Into::into) + .collect(), + removed_entries: Default::default(), + }) + .await + { + let _ = share_tx.try_send(Err(error)); + return Err(anyhow!("failed to send initial update worktree")); + } else { + let _ = share_tx.try_send(Ok(())); + } + } + Err(error) => { + let _ = share_tx.try_send(Err(error.into())); + return Err(anyhow!("failed to send initial update worktree")); + } } for (path, summary) in diagnostic_summaries.iter() { @@ -821,17 +827,24 @@ impl LocalWorktree { }); self.share = Some(ShareState { project_id, - snapshots_tx: snapshots_to_send_tx, + snapshots_tx: snapshots_to_send_tx.clone(), _maintain_remote_snapshot: Some(maintain_remote_snapshot), }); } - async move { + cx.spawn_weak(|this, cx| async move { + register.await?; + if let Some(this) = this.upgrade(&cx) { + this.read_with(&cx, |this, _| { + let this = this.as_local().unwrap(); + let _ = snapshots_to_send_tx.try_send(this.snapshot()); + }); + } share_rx .next() .await .unwrap_or_else(|| Err(anyhow!("share ended"))) - } + }) } pub fn unshare(&mut self) { diff --git a/crates/server/src/rpc.rs b/crates/server/src/rpc.rs index 6330e39aef..0f6da796e3 100644 --- a/crates/server/src/rpc.rs +++ b/crates/server/src/rpc.rs @@ -4664,12 +4664,17 @@ mod tests { } log::info!("Host: find/create local worktree {:?}", path); - project - .update(&mut cx, |project, cx| { - project.find_or_create_local_worktree(path, true, cx) - }) - .await - .unwrap(); + let find_or_create_worktree = project.update(&mut cx, |project, cx| { + project.find_or_create_local_worktree(path, true, cx) + }); + let find_or_create_worktree = async move { + find_or_create_worktree.await.unwrap(); + }; + if rng.lock().gen() { + cx.background().spawn(find_or_create_worktree).detach(); + } else { + find_or_create_worktree.await; + } } 10..=80 if !files.lock().is_empty() => { let buffer = if self.buffers.is_empty() || rng.lock().gen() { From d171d8ccc4c19e003c0fe248970009cda9d201c9 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 3 Mar 2022 10:51:37 +0100 Subject: [PATCH 08/16] Change the definition of `check_invariants` - On the host, ensure that only one worktree can exist for a given absolute path. Asserting about buffers was cool but I think should be tackled in the context of leaning more on entry ids for collaboration (vs. buffer ids). - On the guest, ensure that all the opened buffers don't contain deferred operations. --- crates/project/src/project.rs | 69 +++++++++++++---------------------- crates/server/src/rpc.rs | 6 +++ 2 files changed, 32 insertions(+), 43 deletions(-) diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 015b5e6ccc..fc6fdaeb06 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -397,17 +397,6 @@ impl Project { .and_then(|buffer| buffer.upgrade(cx)) } - #[cfg(any(test, feature = "test-support"))] - pub fn has_deferred_operations(&self, cx: &AppContext) -> bool { - self.opened_buffers.values().any(|buffer| match buffer { - OpenBuffer::Strong(buffer) => buffer.read(cx).deferred_ops_len() > 0, - OpenBuffer::Weak(buffer) => buffer - .upgrade(cx) - .map_or(false, |buffer| buffer.read(cx).deferred_ops_len() > 0), - OpenBuffer::Loading(_) => false, - }) - } - #[cfg(any(test, feature = "test-support"))] pub fn languages(&self) -> &Arc { &self.languages @@ -416,43 +405,37 @@ impl Project { #[cfg(any(test, feature = "test-support"))] pub fn check_invariants(&self, cx: &AppContext) { if self.is_local() { - let buffers = self.buffers(cx); - for (i, buffer) in buffers.iter().enumerate() { - let buffer = buffer.read(cx); - let path = buffer.file().unwrap().as_local().unwrap().abs_path(cx); - for other_buffer in &buffers[0..i] { - let other_buffer = other_buffer.read(cx); - let other_path = other_buffer - .file() - .unwrap() - .as_local() - .unwrap() - .abs_path(cx); - if other_path == path { - panic!( - "buffers {} and {} have the same absolute path: {:?}", - buffer.remote_id(), - other_buffer.remote_id(), - path, - ); - } + let mut worktree_root_paths = HashMap::default(); + for worktree in self.worktrees(cx) { + let worktree = worktree.read(cx); + let abs_path = worktree.as_local().unwrap().abs_path().clone(); + let prev_worktree_id = worktree_root_paths.insert(abs_path.clone(), worktree.id()); + assert_eq!( + prev_worktree_id, + None, + "abs path {:?} for worktree {:?} is not unique ({:?} was already registered with the same path)", + abs_path, + worktree.id(), + prev_worktree_id + ) + } + } else { + let replica_id = self.replica_id(); + for buffer in self.opened_buffers.values() { + if let Some(buffer) = buffer.upgrade(cx) { + let buffer = buffer.read(cx); + assert_eq!( + buffer.deferred_ops_len(), + 0, + "replica {}, buffer {} has deferred operations", + replica_id, + buffer.remote_id() + ); } } } } - #[cfg(any(test, feature = "test-support"))] - pub fn buffers(&self, cx: &AppContext) -> Vec> { - self.opened_buffers - .values() - .filter_map(|buffer| match buffer { - OpenBuffer::Strong(buffer) => Some(buffer.clone()), - OpenBuffer::Weak(buffer) => buffer.upgrade(cx), - OpenBuffer::Loading(_) => None, - }) - .collect() - } - #[cfg(any(test, feature = "test-support"))] pub fn has_open_buffer(&self, path: impl Into, cx: &AppContext) -> bool { let path = path.into(); diff --git a/crates/server/src/rpc.rs b/crates/server/src/rpc.rs index 0f6da796e3..2b00bfc7f6 100644 --- a/crates/server/src/rpc.rs +++ b/crates/server/src/rpc.rs @@ -4293,6 +4293,12 @@ mod tests { ); } + guest_client + .project + .as_ref() + .unwrap() + .read_with(&guest_cx, |project, cx| project.check_invariants(cx)); + for guest_buffer in &guest_client.buffers { let buffer_id = guest_buffer.read_with(&guest_cx, |buffer, _| buffer.remote_id()); let host_buffer = host_project.read_with(&host_cx, |project, cx| { From 1c14168f386aa9ff4b67cac3cf4aa14ef4fdd91b Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 3 Mar 2022 10:54:52 +0100 Subject: [PATCH 09/16] Ensure no two worktrees can point to the same root path This could happen because there was a pause between creating the worktree and adding it to the list of tracked local worktrees, and so we might end up adding the same worktree twice when calling `create_local_worktree` in rapid succession. --- crates/project/src/project.rs | 81 +++++++++++++++++++++++------------ 1 file changed, 54 insertions(+), 27 deletions(-) diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index fc6fdaeb06..e4b8ed95d3 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -8,7 +8,7 @@ use anyhow::{anyhow, Context, Result}; use client::{proto, Client, PeerId, TypedEnvelope, User, UserStore}; use clock::ReplicaId; use collections::{hash_map, HashMap, HashSet}; -use futures::{future::Shared, Future, FutureExt, StreamExt}; +use futures::{future::Shared, Future, FutureExt, StreamExt, TryFutureExt}; use fuzzy::{PathMatch, PathMatchCandidate, PathMatchCandidateSet}; use gpui::{ AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, MutableAppContext, Task, @@ -64,6 +64,8 @@ pub struct Project { ProjectPath, postage::watch::Receiver, Arc>>>, >, + loading_local_worktrees: + HashMap, Shared, Arc>>>>, opened_buffers: HashMap, nonce: u128, } @@ -282,6 +284,7 @@ impl Project { opened_buffers: Default::default(), shared_buffers: Default::default(), loading_buffers: Default::default(), + loading_local_worktrees: Default::default(), client_state: ProjectClientState::Local { is_shared: false, remote_id_tx, @@ -336,6 +339,7 @@ impl Project { loading_buffers: Default::default(), opened_buffer: (Rc::new(RefCell::new(opened_buffer_tx)), opened_buffer_rx), shared_buffers: Default::default(), + loading_local_worktrees: Default::default(), active_entry: None, collaborators: Default::default(), languages, @@ -830,7 +834,7 @@ impl Project { } pub fn save_buffer_as( - &self, + &mut self, buffer: ModelHandle, abs_path: PathBuf, cx: &mut ModelContext, @@ -2322,7 +2326,7 @@ impl Project { } pub fn find_or_create_local_worktree( - &self, + &mut self, abs_path: impl AsRef, visible: bool, cx: &mut ModelContext, @@ -2362,39 +2366,62 @@ impl Project { } fn create_local_worktree( - &self, + &mut self, abs_path: impl AsRef, visible: bool, cx: &mut ModelContext, ) -> Task>> { let fs = self.fs.clone(); let client = self.client.clone(); - let path = Arc::from(abs_path.as_ref()); - cx.spawn(|project, mut cx| async move { - let worktree = Worktree::local(client.clone(), path, visible, fs, &mut cx).await?; + let path: Arc = abs_path.as_ref().into(); + let task = self + .loading_local_worktrees + .entry(path.clone()) + .or_insert_with(|| { + cx.spawn(|project, mut cx| { + async move { + let worktree = + Worktree::local(client.clone(), path.clone(), visible, fs, &mut cx) + .await; + project.update(&mut cx, |project, _| { + project.loading_local_worktrees.remove(&path); + }); + let worktree = worktree?; - let (remote_project_id, is_shared) = project.update(&mut cx, |project, cx| { - project.add_worktree(&worktree, cx); - (project.remote_id(), project.is_shared()) - }); + let (remote_project_id, is_shared) = + project.update(&mut cx, |project, cx| { + project.add_worktree(&worktree, cx); + (project.remote_id(), project.is_shared()) + }); - if let Some(project_id) = remote_project_id { - if is_shared { - worktree - .update(&mut cx, |worktree, cx| { - worktree.as_local_mut().unwrap().share(project_id, cx) - }) - .await?; - } else { - worktree - .update(&mut cx, |worktree, cx| { - worktree.as_local_mut().unwrap().register(project_id, cx) - }) - .await?; - } + if let Some(project_id) = remote_project_id { + if is_shared { + worktree + .update(&mut cx, |worktree, cx| { + worktree.as_local_mut().unwrap().share(project_id, cx) + }) + .await?; + } else { + worktree + .update(&mut cx, |worktree, cx| { + worktree.as_local_mut().unwrap().register(project_id, cx) + }) + .await?; + } + } + + Ok(worktree) + } + .map_err(|err| Arc::new(err)) + }) + .shared() + }) + .clone(); + cx.foreground().spawn(async move { + match task.await { + Ok(worktree) => Ok(worktree), + Err(err) => Err(anyhow!("{}", err)), } - - Ok(worktree) }) } From 14d26eeedc15e7667eeb73ba61dca0ac127368be Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 3 Mar 2022 12:18:19 +0100 Subject: [PATCH 10/16] Move several buffer-related messages to the background --- crates/language/src/buffer.rs | 7 ++ crates/project/src/lsp_command.rs | 157 +++++++++++++++++++----------- crates/project/src/project.rs | 61 +++++++----- crates/rpc/proto/zed.proto | 6 ++ crates/rpc/src/proto.rs | 22 ++--- crates/server/src/rpc.rs | 7 +- crates/text/src/text.rs | 26 +++++ 7 files changed, 187 insertions(+), 99 deletions(-) diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index ddc6fa7c93..b53e02d733 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -1291,6 +1291,13 @@ impl Buffer { self.text.wait_for_edits(edit_ids) } + pub fn wait_for_anchors<'a>( + &mut self, + anchors: impl IntoIterator, + ) -> impl Future { + self.text.wait_for_anchors(anchors) + } + pub fn wait_for_version(&mut self, version: clock::Global) -> impl Future { self.text.wait_for_version(version) } diff --git a/crates/project/src/lsp_command.rs b/crates/project/src/lsp_command.rs index 3b502fc8fa..408c8a150c 100644 --- a/crates/project/src/lsp_command.rs +++ b/crates/project/src/lsp_command.rs @@ -31,10 +31,11 @@ pub(crate) trait LspCommand: 'static + Sized { ) -> Result; fn to_proto(&self, project_id: u64, buffer: &Buffer) -> Self::ProtoRequest; - fn from_proto( + async fn from_proto( message: Self::ProtoRequest, - project: &mut Project, - buffer: &Buffer, + project: ModelHandle, + buffer: ModelHandle, + cx: AsyncAppContext, ) -> Result; fn response_to_proto( response: Self::Response, @@ -121,19 +122,28 @@ impl LspCommand for PrepareRename { position: Some(language::proto::serialize_anchor( &buffer.anchor_before(self.position), )), + version: (&buffer.version()).into(), } } - fn from_proto(message: proto::PrepareRename, _: &mut Project, buffer: &Buffer) -> Result { + async fn from_proto( + message: proto::PrepareRename, + _: ModelHandle, + buffer: ModelHandle, + mut cx: AsyncAppContext, + ) -> Result { let position = message .position .and_then(deserialize_anchor) .ok_or_else(|| anyhow!("invalid position"))?; - if !buffer.can_resolve(&position) { - Err(anyhow!("cannot resolve position"))?; - } + buffer + .update(&mut cx, |buffer, _| { + buffer.wait_for_version(message.version.into()) + }) + .await; + Ok(Self { - position: position.to_point_utf16(buffer), + position: buffer.read_with(&cx, |buffer, _| position.to_point_utf16(buffer)), }) } @@ -241,19 +251,27 @@ impl LspCommand for PerformRename { &buffer.anchor_before(self.position), )), new_name: self.new_name.clone(), + version: (&buffer.version()).into(), } } - fn from_proto(message: proto::PerformRename, _: &mut Project, buffer: &Buffer) -> Result { + async fn from_proto( + message: proto::PerformRename, + _: ModelHandle, + buffer: ModelHandle, + mut cx: AsyncAppContext, + ) -> Result { let position = message .position .and_then(deserialize_anchor) .ok_or_else(|| anyhow!("invalid position"))?; - if !buffer.can_resolve(&position) { - Err(anyhow!("cannot resolve position"))?; - } + buffer + .update(&mut cx, |buffer, _| { + buffer.wait_for_version(message.version.into()) + }) + .await; Ok(Self { - position: position.to_point_utf16(buffer), + position: buffer.read_with(&cx, |buffer, _| position.to_point_utf16(buffer)), new_name: message.new_name, push_to_history: false, }) @@ -385,19 +403,27 @@ impl LspCommand for GetDefinition { position: Some(language::proto::serialize_anchor( &buffer.anchor_before(self.position), )), + version: (&buffer.version()).into(), } } - fn from_proto(message: proto::GetDefinition, _: &mut Project, buffer: &Buffer) -> Result { + async fn from_proto( + message: proto::GetDefinition, + _: ModelHandle, + buffer: ModelHandle, + mut cx: AsyncAppContext, + ) -> Result { let position = message .position .and_then(deserialize_anchor) .ok_or_else(|| anyhow!("invalid position"))?; - if !buffer.can_resolve(&position) { - Err(anyhow!("cannot resolve position"))?; - } + buffer + .update(&mut cx, |buffer, _| { + buffer.wait_for_version(message.version.into()) + }) + .await; Ok(Self { - position: position.to_point_utf16(buffer), + position: buffer.read_with(&cx, |buffer, _| position.to_point_utf16(buffer)), }) } @@ -443,6 +469,9 @@ impl LspCommand for GetDefinition { .end .and_then(deserialize_anchor) .ok_or_else(|| anyhow!("missing target end"))?; + buffer + .update(&mut cx, |buffer, _| buffer.wait_for_anchors([&start, &end])) + .await; locations.push(Location { buffer, range: start..end, @@ -533,19 +562,27 @@ impl LspCommand for GetReferences { position: Some(language::proto::serialize_anchor( &buffer.anchor_before(self.position), )), + version: (&buffer.version()).into(), } } - fn from_proto(message: proto::GetReferences, _: &mut Project, buffer: &Buffer) -> Result { + async fn from_proto( + message: proto::GetReferences, + _: ModelHandle, + buffer: ModelHandle, + mut cx: AsyncAppContext, + ) -> Result { let position = message .position .and_then(deserialize_anchor) .ok_or_else(|| anyhow!("invalid position"))?; - if !buffer.can_resolve(&position) { - Err(anyhow!("cannot resolve position"))?; - } + buffer + .update(&mut cx, |buffer, _| { + buffer.wait_for_version(message.version.into()) + }) + .await; Ok(Self { - position: position.to_point_utf16(buffer), + position: buffer.read_with(&cx, |buffer, _| position.to_point_utf16(buffer)), }) } @@ -591,6 +628,9 @@ impl LspCommand for GetReferences { .end .and_then(deserialize_anchor) .ok_or_else(|| anyhow!("missing target end"))?; + target_buffer + .update(&mut cx, |buffer, _| buffer.wait_for_anchors([&start, &end])) + .await; locations.push(Location { buffer: target_buffer, range: start..end, @@ -658,23 +698,27 @@ impl LspCommand for GetDocumentHighlights { position: Some(language::proto::serialize_anchor( &buffer.anchor_before(self.position), )), + version: (&buffer.version()).into(), } } - fn from_proto( + async fn from_proto( message: proto::GetDocumentHighlights, - _: &mut Project, - buffer: &Buffer, + _: ModelHandle, + buffer: ModelHandle, + mut cx: AsyncAppContext, ) -> Result { let position = message .position .and_then(deserialize_anchor) .ok_or_else(|| anyhow!("invalid position"))?; - if !buffer.can_resolve(&position) { - Err(anyhow!("cannot resolve position"))?; - } + buffer + .update(&mut cx, |buffer, _| { + buffer.wait_for_version(message.version.into()) + }) + .await; Ok(Self { - position: position.to_point_utf16(buffer), + position: buffer.read_with(&cx, |buffer, _| position.to_point_utf16(buffer)), }) } @@ -705,33 +749,34 @@ impl LspCommand for GetDocumentHighlights { self, message: proto::GetDocumentHighlightsResponse, _: ModelHandle, - _: ModelHandle, - _: AsyncAppContext, + buffer: ModelHandle, + mut cx: AsyncAppContext, ) -> Result> { - Ok(message - .highlights - .into_iter() - .map(|highlight| { - let start = highlight - .start - .and_then(deserialize_anchor) - .ok_or_else(|| anyhow!("missing target start"))?; - let end = highlight - .end - .and_then(deserialize_anchor) - .ok_or_else(|| anyhow!("missing target end"))?; - let kind = match proto::document_highlight::Kind::from_i32(highlight.kind) { - Some(proto::document_highlight::Kind::Text) => DocumentHighlightKind::TEXT, - Some(proto::document_highlight::Kind::Read) => DocumentHighlightKind::READ, - Some(proto::document_highlight::Kind::Write) => DocumentHighlightKind::WRITE, - None => DocumentHighlightKind::TEXT, - }; - Ok(DocumentHighlight { - range: start..end, - kind, - }) - }) - .collect::>>()?) + let mut highlights = Vec::new(); + for highlight in message.highlights { + let start = highlight + .start + .and_then(deserialize_anchor) + .ok_or_else(|| anyhow!("missing target start"))?; + let end = highlight + .end + .and_then(deserialize_anchor) + .ok_or_else(|| anyhow!("missing target end"))?; + buffer + .update(&mut cx, |buffer, _| buffer.wait_for_anchors([&start, &end])) + .await; + let kind = match proto::document_highlight::Kind::from_i32(highlight.kind) { + Some(proto::document_highlight::Kind::Text) => DocumentHighlightKind::TEXT, + Some(proto::document_highlight::Kind::Read) => DocumentHighlightKind::READ, + Some(proto::document_highlight::Kind::Write) => DocumentHighlightKind::WRITE, + None => DocumentHighlightKind::TEXT, + }; + highlights.push(DocumentHighlight { + range: start..end, + kind, + }); + } + Ok(highlights) } fn buffer_id_from_proto(message: &proto::GetDocumentHighlights) -> u64 { diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index e4b8ed95d3..616c555151 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -1815,6 +1815,7 @@ impl Project { }) } else if let Some(project_id) = self.remote_id() { let rpc = self.client.clone(); + let version = buffer.version(); cx.spawn_weak(|_, mut cx| async move { let response = rpc .request(proto::GetCodeActions { @@ -1822,6 +1823,7 @@ impl Project { buffer_id, start: Some(language::proto::serialize_anchor(&range.start)), end: Some(language::proto::serialize_anchor(&range.end)), + version: (&version).into(), }) .await?; @@ -2840,13 +2842,11 @@ impl Project { .ok_or_else(|| anyhow!("unknown buffer id {}", buffer_id))?; Ok::<_, anyhow::Error>((project_id, buffer)) })?; - - if !buffer - .read_with(&cx, |buffer, _| buffer.version()) - .observed_all(&requested_version) - { - Err(anyhow!("save request depends on unreceived edits"))?; - } + buffer + .update(&mut cx, |buffer, _| { + buffer.wait_for_version(requested_version) + }) + .await; let (saved_version, mtime) = buffer.update(&mut cx, |buffer, cx| buffer.save(cx)).await?; Ok(proto::BufferSaved { @@ -2904,12 +2904,9 @@ impl Project { .map(|buffer| buffer.upgrade(cx).unwrap()) .ok_or_else(|| anyhow!("unknown buffer id {}", envelope.payload.buffer_id)) })?; - if !buffer - .read_with(&cx, |buffer, _| buffer.version()) - .observed_all(&version) - { - Err(anyhow!("completion request depends on unreceived edits"))?; - } + buffer + .update(&mut cx, |buffer, _| buffer.wait_for_version(version)) + .await; let version = buffer.read_with(&cx, |buffer, _| buffer.version()); let completions = this .update(&mut cx, |this, cx| this.completions(&buffer, position, cx)) @@ -2979,10 +2976,13 @@ impl Project { .map(|buffer| buffer.upgrade(cx).unwrap()) .ok_or_else(|| anyhow!("unknown buffer id {}", envelope.payload.buffer_id)) })?; + buffer + .update(&mut cx, |buffer, _| { + buffer.wait_for_version(envelope.payload.version.into()) + }) + .await; + let version = buffer.read_with(&cx, |buffer, _| buffer.version()); - if !version.observed(start.timestamp) || !version.observed(end.timestamp) { - Err(anyhow!("code action request references unreceived edits"))?; - } let code_actions = this.update(&mut cx, |this, cx| { Ok::<_, anyhow::Error>(this.code_actions(&buffer, start..end, cx)) })?; @@ -3038,19 +3038,26 @@ impl Project { ::Result: Send, { let sender_id = envelope.original_sender_id()?; - let (request, buffer_version) = this.update(&mut cx, |this, cx| { - let buffer_id = T::buffer_id_from_proto(&envelope.payload); - let buffer_handle = this - .opened_buffers + let buffer_id = T::buffer_id_from_proto(&envelope.payload); + let buffer_handle = this.read_with(&cx, |this, _| { + this.opened_buffers .get(&buffer_id) - .map(|buffer| buffer.upgrade(cx).unwrap()) - .ok_or_else(|| anyhow!("unknown buffer id {}", buffer_id))?; - let buffer = buffer_handle.read(cx); - let buffer_version = buffer.version(); - let request = T::from_proto(envelope.payload, this, buffer)?; - Ok::<_, anyhow::Error>((this.request_lsp(buffer_handle, request, cx), buffer_version)) + .map(|buffer| buffer.upgrade(&cx).unwrap()) + .ok_or_else(|| anyhow!("unknown buffer id {}", buffer_id)) })?; - let response = request.await?; + let request = T::from_proto( + envelope.payload, + this.clone(), + buffer_handle.clone(), + cx.clone(), + ) + .await?; + let buffer_version = buffer_handle.read_with(&cx, |buffer, _| buffer.version()); + let response = this + .update(&mut cx, |this, cx| { + this.request_lsp(buffer_handle, request, cx) + }) + .await?; this.update(&mut cx, |this, cx| { Ok(T::response_to_proto( response, diff --git a/crates/rpc/proto/zed.proto b/crates/rpc/proto/zed.proto index 4d55208872..801334d3fb 100644 --- a/crates/rpc/proto/zed.proto +++ b/crates/rpc/proto/zed.proto @@ -164,6 +164,7 @@ message GetDefinition { uint64 project_id = 1; uint64 buffer_id = 2; Anchor position = 3; + repeated VectorClockEntry version = 4; } message GetDefinitionResponse { @@ -174,6 +175,7 @@ message GetReferences { uint64 project_id = 1; uint64 buffer_id = 2; Anchor position = 3; + repeated VectorClockEntry version = 4; } message GetReferencesResponse { @@ -184,6 +186,7 @@ message GetDocumentHighlights { uint64 project_id = 1; uint64 buffer_id = 2; Anchor position = 3; + repeated VectorClockEntry version = 4; } message GetDocumentHighlightsResponse { @@ -328,6 +331,7 @@ message GetCodeActions { uint64 buffer_id = 2; Anchor start = 3; Anchor end = 4; + repeated VectorClockEntry version = 5; } message GetCodeActionsResponse { @@ -349,6 +353,7 @@ message PrepareRename { uint64 project_id = 1; uint64 buffer_id = 2; Anchor position = 3; + repeated VectorClockEntry version = 4; } message PrepareRenameResponse { @@ -363,6 +368,7 @@ message PerformRename { uint64 buffer_id = 2; Anchor position = 3; string new_name = 4; + repeated VectorClockEntry version = 5; } message PerformRenameResponse { diff --git a/crates/rpc/src/proto.rs b/crates/rpc/src/proto.rs index c5da067f17..f60c2d2c0f 100644 --- a/crates/rpc/src/proto.rs +++ b/crates/rpc/src/proto.rs @@ -157,15 +157,15 @@ messages!( (GetChannels, Foreground), (GetChannelsResponse, Foreground), (GetCodeActions, Background), - (GetCodeActionsResponse, Foreground), + (GetCodeActionsResponse, Background), (GetCompletions, Background), - (GetCompletionsResponse, Foreground), - (GetDefinition, Foreground), - (GetDefinitionResponse, Foreground), + (GetCompletionsResponse, Background), + (GetDefinition, Background), + (GetDefinitionResponse, Background), (GetDocumentHighlights, Background), (GetDocumentHighlightsResponse, Background), - (GetReferences, Foreground), - (GetReferencesResponse, Foreground), + (GetReferences, Background), + (GetReferencesResponse, Background), (GetProjectSymbols, Background), (GetProjectSymbolsResponse, Background), (GetUsers, Foreground), @@ -176,10 +176,10 @@ messages!( (JoinProjectResponse, Foreground), (LeaveChannel, Foreground), (LeaveProject, Foreground), - (OpenBuffer, Foreground), - (OpenBufferForSymbol, Foreground), - (OpenBufferForSymbolResponse, Foreground), - (OpenBufferResponse, Foreground), + (OpenBuffer, Background), + (OpenBufferForSymbol, Background), + (OpenBufferForSymbolResponse, Background), + (OpenBufferResponse, Background), (PerformRename, Background), (PerformRenameResponse, Background), (PrepareRename, Background), @@ -199,7 +199,7 @@ messages!( (UnregisterProject, Foreground), (UnregisterWorktree, Foreground), (UnshareProject, Foreground), - (UpdateBuffer, Foreground), + (UpdateBuffer, Background), (UpdateBufferFile, Foreground), (UpdateContacts, Foreground), (UpdateDiagnosticSummary, Foreground), diff --git a/crates/server/src/rpc.rs b/crates/server/src/rpc.rs index 2b00bfc7f6..9a8b4ee161 100644 --- a/crates/server/src/rpc.rs +++ b/crates/server/src/rpc.rs @@ -4911,12 +4911,9 @@ mod tests { ); (buffer.version(), buffer.save(cx)) }); - let save = cx.spawn(|cx| async move { + let save = cx.background().spawn(async move { let (saved_version, _) = save.await.expect("save request failed"); - buffer.read_with(&cx, |buffer, _| { - assert!(buffer.version().observed_all(&saved_version)); - assert!(saved_version.observed_all(&requested_version)); - }); + assert!(saved_version.observed_all(&requested_version)); }); if rng.lock().gen_bool(0.3) { log::info!("Guest {}: detaching save request", guest_id); diff --git a/crates/text/src/text.rs b/crates/text/src/text.rs index d92a36dd43..e9e106ea83 100644 --- a/crates/text/src/text.rs +++ b/crates/text/src/text.rs @@ -1307,6 +1307,32 @@ impl Buffer { } } + pub fn wait_for_anchors<'a>( + &mut self, + anchors: impl IntoIterator, + ) -> impl 'static + Future { + let mut futures = Vec::new(); + for anchor in anchors { + if !self.version.observed(anchor.timestamp) + && *anchor != Anchor::max() + && *anchor != Anchor::min() + { + let (tx, rx) = oneshot::channel(); + self.edit_id_resolvers + .entry(anchor.timestamp) + .or_default() + .push(tx); + futures.push(rx); + } + } + + async move { + for mut future in futures { + future.recv().await; + } + } + } + pub fn wait_for_version(&mut self, version: clock::Global) -> impl Future { let (tx, mut rx) = barrier::channel(); if !self.snapshot.version.observed_all(&version) { From d0d69161670cf93351e9e9c3a96308374bc60f50 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 3 Mar 2022 13:54:26 +0100 Subject: [PATCH 11/16] Move code action and completion edit messages to the background --- crates/rpc/src/proto.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/rpc/src/proto.rs b/crates/rpc/src/proto.rs index f60c2d2c0f..0729dbc76a 100644 --- a/crates/rpc/src/proto.rs +++ b/crates/rpc/src/proto.rs @@ -139,10 +139,10 @@ macro_rules! entity_messages { messages!( (Ack, Foreground), (AddProjectCollaborator, Foreground), - (ApplyCodeAction, Foreground), - (ApplyCodeActionResponse, Foreground), - (ApplyCompletionAdditionalEdits, Foreground), - (ApplyCompletionAdditionalEditsResponse, Foreground), + (ApplyCodeAction, Background), + (ApplyCodeActionResponse, Background), + (ApplyCompletionAdditionalEdits, Background), + (ApplyCompletionAdditionalEditsResponse, Background), (BufferReloaded, Foreground), (BufferSaved, Foreground), (ChannelMessageSent, Foreground), From c613b226195f9d1fc9d9ffe6c487036acffb1c6a Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 3 Mar 2022 13:58:55 +0100 Subject: [PATCH 12/16] Ignore diagnostics received for invisible worktrees --- crates/project/src/project.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 616c555151..a9c1b4e16f 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -1218,6 +1218,10 @@ impl Project { let (worktree, relative_path) = self .find_local_worktree(&abs_path, cx) .ok_or_else(|| anyhow!("no worktree found for diagnostics"))?; + if !worktree.read(cx).is_visible() { + return Ok(()); + } + let project_path = ProjectPath { worktree_id: worktree.read(cx).id(), path: relative_path.into(), From 410ee124afe6a7ede0daf14ae750204685567dea Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 3 Mar 2022 16:13:17 +0100 Subject: [PATCH 13/16] Maintain remote worktrees correctly when building updates This accidentally regressed in 53327e2. --- crates/project/src/worktree.rs | 8 +++----- crates/server/src/rpc/store.rs | 24 +++++++++++++++++++++++- 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index a9ce90ce61..99b0a0b298 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -768,15 +768,13 @@ impl LocalWorktree { if self.share.is_some() { let _ = share_tx.try_send(Ok(())); } else { - let snapshot = self.snapshot(); let rpc = self.client.clone(); let worktree_id = cx.model_id() as u64; - let maintain_remote_snapshot = cx.background().spawn({ let rpc = rpc.clone(); let diagnostic_summaries = self.diagnostic_summaries.clone(); async move { - match snapshots_to_send_rx.recv().await { + let mut prev_snapshot = match snapshots_to_send_rx.recv().await { Ok(snapshot) => { if let Err(error) = rpc .request(proto::UpdateWorktree { @@ -797,13 +795,14 @@ impl LocalWorktree { return Err(anyhow!("failed to send initial update worktree")); } else { let _ = share_tx.try_send(Ok(())); + snapshot } } Err(error) => { let _ = share_tx.try_send(Err(error.into())); return Err(anyhow!("failed to send initial update worktree")); } - } + }; for (path, summary) in diagnostic_summaries.iter() { rpc.send(proto::UpdateDiagnosticSummary { @@ -813,7 +812,6 @@ impl LocalWorktree { })?; } - let mut prev_snapshot = snapshot; while let Ok(snapshot) = snapshots_to_send_rx.recv().await { let message = snapshot.build_update(&prev_snapshot, project_id, worktree_id, false); diff --git a/crates/server/src/rpc/store.rs b/crates/server/src/rpc/store.rs index d51b43610e..c18db3b684 100644 --- a/crates/server/src/rpc/store.rs +++ b/crates/server/src/rpc/store.rs @@ -534,7 +534,12 @@ impl Store { for entry in updated_entries { worktree.entries.insert(entry.id, entry.clone()); } - Ok(project.connection_ids()) + let connection_ids = project.connection_ids(); + + #[cfg(test)] + self.check_invariants(); + + Ok(connection_ids) } pub fn project_connection_ids( @@ -619,6 +624,23 @@ impl Store { .guests .contains_key(connection_id)); } + + if let Some(share) = project.share.as_ref() { + for (worktree_id, worktree) in share.worktrees.iter() { + let mut paths = HashMap::default(); + for entry in worktree.entries.values() { + let prev_entry = paths.insert(&entry.path, entry); + assert_eq!( + prev_entry, + None, + "worktree {:?}, duplicate path for entries {:?} and {:?}", + worktree_id, + prev_entry.unwrap(), + entry + ); + } + } + } } for channel_id in &connection.channels { let channel = self.channels.get(channel_id).unwrap(); From 556d9cc53fd98f48ac2b900485ce309903110249 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 3 Mar 2022 18:07:59 +0100 Subject: [PATCH 14/16] Correctly defer undo operations when messages arrive out of order Co-Authored-By: Nathan Sobo Co-Authored-By: Max Brunsfeld --- crates/language/src/proto.rs | 18 ++++++++++++++---- crates/rpc/proto/zed.proto | 7 ++++--- crates/rpc/src/rpc.rs | 2 +- crates/text/src/text.rs | 16 +++++++++------- crates/util/src/test.rs | 19 +++---------------- 5 files changed, 31 insertions(+), 31 deletions(-) diff --git a/crates/language/src/proto.rs b/crates/language/src/proto.rs index 80c990455c..09e1a4e350 100644 --- a/crates/language/src/proto.rs +++ b/crates/language/src/proto.rs @@ -25,7 +25,13 @@ pub fn serialize_operation(operation: &Operation) -> proto::Operation { replica_id: undo.id.replica_id as u32, local_timestamp: undo.id.value, lamport_timestamp: lamport_timestamp.value, - ranges: undo.ranges.iter().map(serialize_range).collect(), + version: From::from(&undo.version), + transaction_ranges: undo + .transaction_ranges + .iter() + .map(serialize_range) + .collect(), + transaction_version: From::from(&undo.transaction_version), counts: undo .counts .iter() @@ -35,7 +41,6 @@ pub fn serialize_operation(operation: &Operation) -> proto::Operation { count: *count, }) .collect(), - version: From::from(&undo.version), }), Operation::UpdateSelections { selections, @@ -183,6 +188,7 @@ pub fn deserialize_operation(message: proto::Operation) -> Result { replica_id: undo.replica_id as ReplicaId, value: undo.local_timestamp, }, + version: undo.version.into(), counts: undo .counts .into_iter() @@ -196,8 +202,12 @@ pub fn deserialize_operation(message: proto::Operation) -> Result { ) }) .collect(), - ranges: undo.ranges.into_iter().map(deserialize_range).collect(), - version: undo.version.into(), + transaction_ranges: undo + .transaction_ranges + .into_iter() + .map(deserialize_range) + .collect(), + transaction_version: undo.transaction_version.into(), }, }), proto::operation::Variant::UpdateSelections(message) => { diff --git a/crates/rpc/proto/zed.proto b/crates/rpc/proto/zed.proto index 801334d3fb..31424762a7 100644 --- a/crates/rpc/proto/zed.proto +++ b/crates/rpc/proto/zed.proto @@ -629,9 +629,10 @@ message Operation { uint32 replica_id = 1; uint32 local_timestamp = 2; uint32 lamport_timestamp = 3; - repeated Range ranges = 4; - repeated VectorClockEntry version = 5; - repeated UndoCount counts = 6; + repeated VectorClockEntry version = 4; + repeated Range transaction_ranges = 5; + repeated VectorClockEntry transaction_version = 6; + repeated UndoCount counts = 7; } message UpdateSelections { diff --git a/crates/rpc/src/rpc.rs b/crates/rpc/src/rpc.rs index ad4a67519d..816c4ce13b 100644 --- a/crates/rpc/src/rpc.rs +++ b/crates/rpc/src/rpc.rs @@ -5,4 +5,4 @@ pub mod proto; pub use conn::Connection; pub use peer::*; -pub const PROTOCOL_VERSION: u32 = 8; +pub const PROTOCOL_VERSION: u32 = 9; diff --git a/crates/text/src/text.rs b/crates/text/src/text.rs index e9e106ea83..95441a1208 100644 --- a/crates/text/src/text.rs +++ b/crates/text/src/text.rs @@ -520,7 +520,8 @@ pub struct EditOperation { pub struct UndoOperation { pub id: clock::Local, pub counts: HashMap, - pub ranges: Vec>, + pub transaction_ranges: Vec>, + pub transaction_version: clock::Global, pub version: clock::Global, } @@ -1039,7 +1040,7 @@ impl Buffer { let mut edits = Patch::default(); self.snapshot.undo_map.insert(undo); - let mut cx = undo.version.clone(); + let mut cx = undo.transaction_version.clone(); for edit_id in undo.counts.keys().copied() { cx.observe(edit_id); } @@ -1047,7 +1048,7 @@ impl Buffer { let mut old_fragments = self.fragments.cursor::<(VersionedFullOffset, usize)>(); let mut new_fragments = old_fragments.slice( - &VersionedFullOffset::Offset(undo.ranges[0].start), + &VersionedFullOffset::Offset(undo.transaction_ranges[0].start), Bias::Right, &cx, ); @@ -1055,7 +1056,7 @@ impl Buffer { RopeBuilder::new(self.visible_text.cursor(0), self.deleted_text.cursor(0)); new_ropes.push_tree(new_fragments.summary().text); - for range in &undo.ranges { + for range in &undo.transaction_ranges { let mut end_offset = old_fragments.end(&cx).0.full_offset(); if end_offset < range.start { @@ -1073,7 +1074,7 @@ impl Buffer { let mut fragment = fragment.clone(); let fragment_was_visible = fragment.visible; - if fragment.was_visible(&undo.version, &self.undo_map) + if fragment.was_visible(&undo.transaction_version, &self.undo_map) || undo .counts .contains_key(&fragment.insertion_timestamp.local()) @@ -1264,9 +1265,10 @@ impl Buffer { let undo = UndoOperation { id: self.local_clock.tick(), + version: self.version(), counts, - ranges: transaction.ranges, - version: transaction.start.clone(), + transaction_ranges: transaction.ranges, + transaction_version: transaction.start.clone(), }; self.apply_undo(&undo)?; let operation = Operation::Undo { diff --git a/crates/util/src/test.rs b/crates/util/src/test.rs index ce81921fff..17bb6e67ce 100644 --- a/crates/util/src/test.rs +++ b/crates/util/src/test.rs @@ -40,23 +40,10 @@ impl Network { for (replica, inbox) in self.inboxes.iter_mut() { if *replica != sender { for message in &messages { - let min_index = inbox - .iter() - .enumerate() - .rev() - .find_map(|(index, envelope)| { - if sender == envelope.sender { - Some(index + 1) - } else { - None - } - }) - .unwrap_or(0); - - // Insert one or more duplicates of this message *after* the previous - // message delivered by this replica. + // Insert one or more duplicates of this message, potentially *before* the previous + // message sent by this peer to simulate out-of-order delivery. for _ in 0..self.rng.gen_range(1..4) { - let insertion_index = self.rng.gen_range(min_index..inbox.len() + 1); + let insertion_index = self.rng.gen_range(0..inbox.len() + 1); inbox.insert( insertion_index, Envelope { From bcd5c2883347d66d249542ba6d0b2603d81ec5b9 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 4 Mar 2022 09:27:49 +0100 Subject: [PATCH 15/16] Allow receiving diagnostic updates out of order --- crates/language/src/buffer.rs | 37 ++++++++++++++++++++++-------- crates/language/src/tests.rs | 43 ++++++++++++++++++++++++++++++----- crates/rpc/proto/zed.proto | 3 ++- crates/util/src/test.rs | 2 -- 4 files changed, 67 insertions(+), 18 deletions(-) diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index b53e02d733..8ade7c86e4 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -68,6 +68,7 @@ pub struct Buffer { remote_selections: TreeMap, selections_update_count: usize, diagnostics_update_count: usize, + diagnostics_timestamp: clock::Lamport, file_update_count: usize, language_server: Option, completion_triggers: Vec, @@ -439,9 +440,14 @@ impl Buffer { let snapshot = this.snapshot(); let entries = proto::deserialize_diagnostics(message.diagnostics); this.apply_diagnostic_update( - DiagnosticSet::from_sorted_entries(entries.into_iter().cloned(), &snapshot), + DiagnosticSet::from_sorted_entries(entries.iter().cloned(), &snapshot), + clock::Lamport { + replica_id: 0, + value: message.diagnostics_timestamp, + }, cx, ); + this.completion_triggers = message.completion_triggers; Ok(this) @@ -470,6 +476,7 @@ impl Buffer { }) .collect(), diagnostics: proto::serialize_diagnostics(self.diagnostics.iter()), + diagnostics_timestamp: self.diagnostics_timestamp.value, completion_triggers: self.completion_triggers.clone(), } } @@ -512,6 +519,7 @@ impl Buffer { selections_update_count: 0, diagnostics: Default::default(), diagnostics_update_count: 0, + diagnostics_timestamp: Default::default(), file_update_count: 0, language_server: None, completion_triggers: Default::default(), @@ -1008,11 +1016,12 @@ impl Buffer { drop(edits_since_save); let set = DiagnosticSet::new(sanitized_diagnostics, content); - self.apply_diagnostic_update(set.clone(), cx); + let lamport_timestamp = self.text.lamport_clock.tick(); + self.apply_diagnostic_update(set.clone(), lamport_timestamp, cx); let op = Operation::UpdateDiagnostics { diagnostics: set.iter().cloned().collect(), - lamport_timestamp: self.text.lamport_clock.tick(), + lamport_timestamp, }; self.send_operation(op, cx); Ok(()) @@ -1682,11 +1691,12 @@ impl Buffer { } Operation::UpdateDiagnostics { diagnostics: diagnostic_set, - .. + lamport_timestamp, } => { let snapshot = self.snapshot(); self.apply_diagnostic_update( DiagnosticSet::from_sorted_entries(diagnostic_set.iter().cloned(), &snapshot), + lamport_timestamp, cx, ); } @@ -1720,11 +1730,20 @@ impl Buffer { } } - fn apply_diagnostic_update(&mut self, diagnostics: DiagnosticSet, cx: &mut ModelContext) { - self.diagnostics = diagnostics; - self.diagnostics_update_count += 1; - cx.notify(); - cx.emit(Event::DiagnosticsUpdated); + fn apply_diagnostic_update( + &mut self, + diagnostics: DiagnosticSet, + lamport_timestamp: clock::Lamport, + cx: &mut ModelContext, + ) { + if lamport_timestamp > self.diagnostics_timestamp { + self.diagnostics = diagnostics; + self.diagnostics_timestamp = lamport_timestamp; + self.diagnostics_update_count += 1; + self.text.lamport_clock.observe(lamport_timestamp); + cx.notify(); + cx.emit(Event::DiagnosticsUpdated); + } } #[cfg(not(test))] diff --git a/crates/language/src/tests.rs b/crates/language/src/tests.rs index 247385a810..27a21639be 100644 --- a/crates/language/src/tests.rs +++ b/crates/language/src/tests.rs @@ -12,7 +12,7 @@ use std::{ time::{Duration, Instant}, }; use unindent::Unindent as _; -use util::test::Network; +use util::{post_inc, test::Network}; #[cfg(test)] #[ctor::ctor] @@ -1173,6 +1173,7 @@ fn test_random_collaboration(cx: &mut MutableAppContext, mut rng: StdRng) { let mut now = Instant::now(); let mut mutation_count = operations; + let mut next_diagnostic_id = 0; let mut active_selections = BTreeMap::default(); loop { let replica_index = rng.gen_range(0..replica_ids.len()); @@ -1213,7 +1214,27 @@ fn test_random_collaboration(cx: &mut MutableAppContext, mut rng: StdRng) { }); mutation_count -= 1; } - 40..=49 if replica_ids.len() < max_peers => { + 40..=49 if mutation_count != 0 && replica_id == 0 => { + let entry_count = rng.gen_range(1..=5); + buffer.update(cx, |buffer, cx| { + let diagnostics = (0..entry_count) + .map(|_| { + let range = buffer.random_byte_range(0, &mut rng); + DiagnosticEntry { + range, + diagnostic: Diagnostic { + message: post_inc(&mut next_diagnostic_id).to_string(), + ..Default::default() + }, + } + }) + .collect(); + log::info!("peer {} setting diagnostics: {:?}", replica_id, diagnostics); + buffer.update_diagnostics(diagnostics, None, cx).unwrap(); + }); + mutation_count -= 1; + } + 50..=59 if replica_ids.len() < max_peers => { let old_buffer = buffer.read(cx).to_proto(); let new_replica_id = replica_ids.len() as ReplicaId; log::info!( @@ -1230,14 +1251,14 @@ fn test_random_collaboration(cx: &mut MutableAppContext, mut rng: StdRng) { replica_ids.push(new_replica_id); network.replicate(replica_id, new_replica_id); } - 50..=69 if mutation_count != 0 => { + 60..=69 if mutation_count != 0 => { buffer.update(cx, |buffer, cx| { buffer.randomly_undo_redo(&mut rng, cx); log::info!("buffer {} text: {:?}", buffer.replica_id(), buffer.text()); }); mutation_count -= 1; } - 70..=99 if network.has_unreceived(replica_id) => { + _ if network.has_unreceived(replica_id) => { let ops = network .receive(replica_id) .into_iter() @@ -1274,15 +1295,25 @@ fn test_random_collaboration(cx: &mut MutableAppContext, mut rng: StdRng) { } } - let first_buffer = buffers[0].read(cx); + let first_buffer = buffers[0].read(cx).snapshot(); for buffer in &buffers[1..] { - let buffer = buffer.read(cx); + let buffer = buffer.read(cx).snapshot(); assert_eq!( buffer.text(), first_buffer.text(), "Replica {} text != Replica 0 text", buffer.replica_id() ); + assert_eq!( + buffer + .diagnostics_in_range::<_, usize>(0..buffer.len()) + .collect::>(), + first_buffer + .diagnostics_in_range::<_, usize>(0..first_buffer.len()) + .collect::>(), + "Replica {} diagnostics != Replica 0 diagnostics", + buffer.replica_id() + ); } for buffer in &buffers { diff --git a/crates/rpc/proto/zed.proto b/crates/rpc/proto/zed.proto index 31424762a7..377aef6946 100644 --- a/crates/rpc/proto/zed.proto +++ b/crates/rpc/proto/zed.proto @@ -542,7 +542,8 @@ message BufferState { repeated Operation operations = 4; repeated SelectionSet selections = 5; repeated Diagnostic diagnostics = 6; - repeated string completion_triggers = 7; + uint32 diagnostics_timestamp = 7; + repeated string completion_triggers = 8; } message BufferFragment { diff --git a/crates/util/src/test.rs b/crates/util/src/test.rs index 17bb6e67ce..73b5461261 100644 --- a/crates/util/src/test.rs +++ b/crates/util/src/test.rs @@ -5,7 +5,6 @@ use tempdir::TempDir; #[derive(Clone)] struct Envelope { message: T, - sender: ReplicaId, } pub struct Network { @@ -48,7 +47,6 @@ impl Network { insertion_index, Envelope { message: message.clone(), - sender, }, ); } From 7c420050c74841de964dad6307bc815ff2f88fb5 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 4 Mar 2022 09:34:30 +0100 Subject: [PATCH 16/16] Observe selection set lamport timestamps when deserializing buffer --- crates/language/src/buffer.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index 8ade7c86e4..7caa4c1f0d 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -426,16 +426,18 @@ impl Buffer { this.apply_ops(ops, cx)?; for selection_set in message.selections { + let lamport_timestamp = clock::Lamport { + replica_id: selection_set.replica_id as ReplicaId, + value: selection_set.lamport_timestamp, + }; this.remote_selections.insert( selection_set.replica_id as ReplicaId, SelectionSet { selections: proto::deserialize_selections(selection_set.selections), - lamport_timestamp: clock::Lamport { - replica_id: selection_set.replica_id as ReplicaId, - value: selection_set.lamport_timestamp, - }, + lamport_timestamp, }, ); + this.text.lamport_clock.observe(lamport_timestamp); } let snapshot = this.snapshot(); let entries = proto::deserialize_diagnostics(message.diagnostics);