Merge pull request #525 from zed-industries/preserve-worktrees

Grow worktrees monotonically when sharing and move most messages to the background
This commit is contained in:
Antonio Scandurra
2022-03-04 09:48:18 +01:00
committed by GitHub
21 changed files with 662 additions and 380 deletions
+101 -56
View File
@@ -35,10 +35,11 @@ pub(crate) trait LspCommand: 'static + Sized {
) -> Result<Self::Response>;
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<Project>,
buffer: ModelHandle<Buffer>,
cx: AsyncAppContext,
) -> Result<Self>;
fn response_to_proto(
response: Self::Response,
@@ -125,19 +126,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<Self> {
async fn from_proto(
message: proto::PrepareRename,
_: ModelHandle<Project>,
buffer: ModelHandle<Buffer>,
mut cx: AsyncAppContext,
) -> Result<Self> {
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)),
})
}
@@ -245,19 +255,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<Self> {
async fn from_proto(
message: proto::PerformRename,
_: ModelHandle<Project>,
buffer: ModelHandle<Buffer>,
mut cx: AsyncAppContext,
) -> Result<Self> {
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,
})
@@ -389,19 +407,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<Self> {
async fn from_proto(
message: proto::GetDefinition,
_: ModelHandle<Project>,
buffer: ModelHandle<Buffer>,
mut cx: AsyncAppContext,
) -> Result<Self> {
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)),
})
}
@@ -447,6 +473,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,
@@ -537,19 +566,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<Self> {
async fn from_proto(
message: proto::GetReferences,
_: ModelHandle<Project>,
buffer: ModelHandle<Buffer>,
mut cx: AsyncAppContext,
) -> Result<Self> {
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)),
})
}
@@ -595,6 +632,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,
@@ -666,23 +706,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<Project>,
buffer: ModelHandle<Buffer>,
mut cx: AsyncAppContext,
) -> Result<Self> {
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)),
})
}
@@ -713,33 +757,34 @@ impl LspCommand for GetDocumentHighlights {
self,
message: proto::GetDocumentHighlightsResponse,
_: ModelHandle<Project>,
_: ModelHandle<Buffer>,
_: AsyncAppContext,
buffer: ModelHandle<Buffer>,
mut cx: AsyncAppContext,
) -> Result<Vec<DocumentHighlight>> {
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::<Result<Vec<_>>>()?)
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 {
+208 -124
View File
@@ -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<Option<Result<ModelHandle<Buffer>, Arc<anyhow::Error>>>>,
>,
loading_local_worktrees:
HashMap<Arc<Path>, Shared<Task<Result<ModelHandle<Worktree>, Arc<anyhow::Error>>>>>,
opened_buffers: HashMap<u64, OpenBuffer>,
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,
@@ -398,19 +402,61 @@ impl Project {
}
#[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,
})
pub fn languages(&self) -> &Arc<LanguageRegistry> {
&self.languages
}
#[cfg(any(test, feature = "test-support"))]
pub fn languages(&self) -> &Arc<LanguageRegistry> {
&self.languages
pub fn check_invariants(&self, cx: &AppContext) {
if self.is_local() {
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 has_open_buffer(&self, path: impl Into<ProjectPath>, 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<dyn Fs> {
@@ -479,16 +525,16 @@ 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<Item = ModelHandle<Worktree>> {
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
}
})
})
@@ -514,6 +560,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 +572,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 +614,7 @@ impl Project {
pub fn unshare(&self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
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 +622,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_visible() {
*worktree_handle = WorktreeHandle::Weak(worktree.downgrade());
}
}
_ => {}
}
}
remote_id_rx
.borrow()
.ok_or_else(|| anyhow!("no project id"))
@@ -743,7 +814,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| {
@@ -763,12 +834,12 @@ impl Project {
}
pub fn save_buffer_as(
&self,
&mut self,
buffer: ModelHandle<Buffer>,
abs_path: PathBuf,
cx: &mut ModelContext<Project>,
) -> Task<Result<()>> {
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
@@ -786,25 +857,6 @@ impl Project {
})
}
#[cfg(any(test, feature = "test-support"))]
pub fn has_open_buffer(&self, path: impl Into<ProjectPath>, 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,
@@ -1166,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(),
@@ -1774,6 +1830,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 {
@@ -1781,6 +1838,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?;
@@ -2051,7 +2109,7 @@ impl Project {
) -> Task<Result<HashMap<ModelHandle<Buffer>, Vec<Range<Anchor>>>>> {
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())
@@ -2295,16 +2353,16 @@ impl Project {
}
pub fn find_or_create_local_worktree(
&self,
&mut self,
abs_path: impl AsRef<Path>,
weak: bool,
visible: bool,
cx: &mut ModelContext<Self>,
) -> Task<Result<(ModelHandle<Worktree>, 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())) })
}
@@ -2335,38 +2393,62 @@ impl Project {
}
fn create_local_worktree(
&self,
&mut self,
abs_path: impl AsRef<Path>,
weak: bool,
visible: bool,
cx: &mut ModelContext<Self>,
) -> Task<Result<ModelHandle<Worktree>>> {
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 path: Arc<Path> = 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 {
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?;
}
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)
})
}
@@ -2388,11 +2470,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_visible() || 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());
@@ -2401,9 +2486,6 @@ impl Project {
.detach();
self.worktrees
.push(WorktreeHandle::Weak(worktree.downgrade()));
} else {
self.worktrees
.push(WorktreeHandle::Strong(worktree.clone()));
}
cx.notify();
}
@@ -2623,7 +2705,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);
@@ -2731,7 +2813,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));
@@ -2785,13 +2867,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 {
@@ -2849,12 +2929,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))
@@ -2924,10 +3001,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))
})?;
@@ -2983,19 +3063,26 @@ impl Project {
<T::LspRequest as lsp::request::Request>::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,
@@ -3357,7 +3444,7 @@ impl Project {
) -> impl 'a + Future<Output = Vec<PathMatch>> {
let worktrees = self
.worktrees(cx)
.filter(|worktree| !worktree.read(cx).is_weak())
.filter(|worktree| worktree.read(cx).is_visible())
.collect::<Vec<_>>();
let include_root_name = worktrees.len() > 1;
let candidate_sets = worktrees
@@ -3652,7 +3739,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();
@@ -3721,7 +3808,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();
@@ -3819,7 +3906,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();
@@ -3867,7 +3954,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();
@@ -3924,16 +4011,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>(
@@ -3947,7 +4031,7 @@ mod tests {
let worktree = worktree.read(cx);
(
worktree.as_local().unwrap().abs_path().as_ref(),
worktree.is_weak(),
worktree.is_visible(),
)
})
.collect::<Vec<_>>()
@@ -3968,7 +4052,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()
@@ -4006,7 +4090,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()
@@ -4050,7 +4134,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();
@@ -4087,7 +4171,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,
)
@@ -4196,7 +4280,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()
@@ -4246,7 +4330,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();
@@ -4380,7 +4464,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();
@@ -4489,7 +4573,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();
@@ -4757,7 +4841,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();
@@ -4885,7 +4969,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();
+66 -50
View File
@@ -71,7 +71,7 @@ pub struct LocalWorktree {
queued_operations: Vec<(u64, Operation)>,
client: Arc<Client>,
fs: Arc<dyn Fs>,
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<PathKey, DiagnosticSummary>,
weak: bool,
visible: bool,
}
#[derive(Clone)]
@@ -169,11 +169,12 @@ impl Worktree {
pub async fn local(
client: Arc<Client>,
path: impl Into<Arc<Path>>,
weak: bool,
visible: bool,
fs: Arc<dyn Fs>,
cx: &mut AsyncAppContext,
) -> Result<ModelHandle<Self>> {
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,
})
});
@@ -345,6 +346,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,
@@ -352,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,
}
}
@@ -454,7 +459,7 @@ impl LocalWorktree {
async fn new(
client: Arc<Client>,
path: impl Into<Arc<Path>>,
weak: bool,
visible: bool,
fs: Arc<dyn Fs>,
cx: &mut AsyncAppContext,
) -> Result<(ModelHandle<Worktree>, UnboundedSender<ScanState>)> {
@@ -521,7 +526,7 @@ impl LocalWorktree {
queued_operations: Default::default(),
client,
fs,
weak,
visible,
};
cx.spawn_weak(|this, mut cx| async move {
@@ -734,10 +739,11 @@ 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,
};
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 {
@@ -754,45 +760,49 @@ impl LocalWorktree {
})
}
pub fn share(
&mut self,
project_id: u64,
cx: &mut ModelContext<Worktree>,
) -> impl Future<Output = Result<()>> {
pub fn share(&mut self, project_id: u64, cx: &mut ModelContext<Worktree>) -> Task<Result<()>> {
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::<LocalSnapshot>();
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::<LocalSnapshot>();
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(()));
}
let mut prev_snapshot = 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(()));
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 {
@@ -802,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);
@@ -816,17 +825,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) {
@@ -1024,7 +1040,7 @@ impl LocalSnapshot {
pub(crate) fn to_proto(
&self,
diagnostic_summaries: &TreeMap<PathKey, DiagnosticSummary>,
weak: bool,
visible: bool,
) -> proto::Worktree {
let root_name = self.root_name.clone();
proto::Worktree {
@@ -1040,7 +1056,7 @@ impl LocalSnapshot {
.iter()
.map(|(path, summary)| summary.to_proto(&path.0))
.collect(),
weak,
visible,
}
}
@@ -2464,7 +2480,7 @@ mod tests {
let tree = Worktree::local(
client,
Arc::from(Path::new("/root")),
false,
true,
fs,
&mut cx.to_async(),
)
@@ -2507,7 +2523,7 @@ mod tests {
let tree = Worktree::local(
client,
dir.path(),
false,
true,
Arc::new(RealFs),
&mut cx.to_async(),
)