diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index 779f7f9981..358cc0f392 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -764,9 +764,9 @@ mod tests { ) .await; - let worktree = project + let (worktree, _) = project .update(&mut cx, |project, cx| { - project.add_local_worktree("/test", cx) + project.find_or_create_worktree_for_abs_path("/test", false, cx) }) .await .unwrap(); diff --git a/crates/file_finder/src/file_finder.rs b/crates/file_finder/src/file_finder.rs index 00f253bc75..99a60cf28d 100644 --- a/crates/file_finder/src/file_finder.rs +++ b/crates/file_finder/src/file_finder.rs @@ -454,9 +454,10 @@ mod tests { .await; let (window_id, workspace) = cx.add_window(|cx| Workspace::new(¶ms, cx)); - workspace - .update(&mut cx, |workspace, cx| { - workspace.add_worktree(Path::new("/root"), cx) + params + .project + .update(&mut cx, |project, cx| { + project.find_or_create_worktree_for_abs_path(Path::new("/root"), false, cx) }) .await .unwrap(); @@ -514,9 +515,10 @@ mod tests { .await; let (_, workspace) = cx.add_window(|cx| Workspace::new(¶ms, cx)); - workspace - .update(&mut cx, |workspace, cx| { - workspace.add_worktree("/dir".as_ref(), cx) + params + .project + .update(&mut cx, |project, cx| { + project.find_or_create_worktree_for_abs_path(Path::new("/dir"), false, cx) }) .await .unwrap(); @@ -579,9 +581,14 @@ mod tests { .await; let (_, workspace) = cx.add_window(|cx| Workspace::new(¶ms, cx)); - workspace - .update(&mut cx, |workspace, cx| { - workspace.add_worktree(Path::new("/root/the-parent-dir/the-file"), cx) + params + .project + .update(&mut cx, |project, cx| { + project.find_or_create_worktree_for_abs_path( + Path::new("/root/the-parent-dir/the-file"), + false, + cx, + ) }) .await .unwrap(); diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index b12e90d9bc..70f1f3eb1f 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -10,6 +10,7 @@ use futures::Future; use fuzzy::{PathMatch, PathMatchCandidate, PathMatchCandidateSet}; use gpui::{ AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, MutableAppContext, Task, + WeakModelHandle, }; use language::{ Bias, Buffer, DiagnosticEntry, File as _, Language, LanguageRegistry, ToOffset, ToPointUtf16, @@ -28,7 +29,7 @@ pub use fs::*; pub use worktree::*; pub struct Project { - worktrees: Vec>, + worktrees: Vec, active_entry: Option, languages: Arc, language_servers: HashMap<(WorktreeId, String), Arc>, @@ -41,6 +42,11 @@ pub struct Project { language_servers_with_diagnostics_running: isize, } +enum WorktreeHandle { + Strong(ModelHandle), + Weak(WeakModelHandle), +} + enum ProjectClientState { Local { is_shared: bool, @@ -161,7 +167,7 @@ impl Project { if let Some(project_id) = remote_id { let mut registrations = Vec::new(); this.update(&mut cx, |this, cx| { - for worktree in &this.worktrees { + for worktree in this.worktrees(cx).collect::>() { registrations.push(worktree.update( cx, |worktree, cx| { @@ -295,7 +301,7 @@ impl Project { language_servers: Default::default(), }; for worktree in worktrees { - this.add_worktree(worktree, cx); + this.add_worktree(&worktree, false, cx); } this })) @@ -364,8 +370,13 @@ impl Project { &self.collaborators } - pub fn worktrees(&self) -> &[ModelHandle] { - &self.worktrees + pub fn worktrees<'a>( + &'a self, + cx: &'a AppContext, + ) -> impl 'a + Iterator> { + self.worktrees + .iter() + .filter_map(move |worktree| worktree.upgrade(cx)) } pub fn worktree_for_id( @@ -373,10 +384,8 @@ impl Project { id: WorktreeId, cx: &AppContext, ) -> Option> { - self.worktrees - .iter() + self.worktrees(cx) .find(|worktree| worktree.read(cx).id() == id) - .cloned() } pub fn share(&self, cx: &mut ModelContext) -> Task> { @@ -401,7 +410,7 @@ impl Project { rpc.request(proto::ShareProject { project_id }).await?; let mut tasks = Vec::new(); this.update(&mut cx, |this, cx| { - for worktree in &this.worktrees { + for worktree in this.worktrees(cx).collect::>() { worktree.update(cx, |worktree, cx| { let worktree = worktree.as_local_mut().unwrap(); tasks.push(worktree.share(cx)); @@ -438,7 +447,7 @@ impl Project { rpc.send(proto::UnshareProject { project_id }).await?; this.update(&mut cx, |this, cx| { this.collaborators.clear(); - for worktree in &this.worktrees { + for worktree in this.worktrees(cx).collect::>() { worktree.update(cx, |worktree, _| { worktree.as_local_mut().unwrap().unshare(); }); @@ -494,7 +503,7 @@ impl Project { abs_path: PathBuf, cx: &mut ModelContext, ) -> Task> { - let worktree_task = self.find_or_create_worktree_for_abs_path(&abs_path, cx); + let worktree_task = self.find_or_create_worktree_for_abs_path(&abs_path, false, cx); cx.spawn(|this, mut cx| async move { let (worktree, path) = worktree_task.await?; worktree @@ -777,7 +786,7 @@ impl Project { } else { let (worktree, relative_path) = this .update(&mut cx, |this, cx| { - this.create_worktree_for_abs_path(&abs_path, cx) + this.create_worktree_for_abs_path(&abs_path, true, cx) }) .await?; this.update(&mut cx, |this, cx| { @@ -829,22 +838,25 @@ impl Project { pub fn find_or_create_worktree_for_abs_path( &self, - abs_path: &Path, + abs_path: impl AsRef, + weak: bool, cx: &mut ModelContext, ) -> Task, PathBuf)>> { + let abs_path = abs_path.as_ref(); if let Some((tree, relative_path)) = self.find_worktree_for_abs_path(abs_path, cx) { Task::ready(Ok((tree.clone(), relative_path.into()))) } else { - self.create_worktree_for_abs_path(abs_path, cx) + self.create_worktree_for_abs_path(abs_path, weak, cx) } } fn create_worktree_for_abs_path( &self, abs_path: &Path, + weak: bool, cx: &mut ModelContext, ) -> Task, PathBuf)>> { - let worktree = self.add_local_worktree(abs_path, cx); + let worktree = self.add_local_worktree(abs_path, weak, cx); cx.background().spawn(async move { let worktree = worktree.await?; Ok((worktree, PathBuf::new())) @@ -856,7 +868,7 @@ impl Project { abs_path: &Path, cx: &AppContext, ) -> Option<(ModelHandle, PathBuf)> { - for tree in &self.worktrees { + for tree in self.worktrees(cx) { if let Some(relative_path) = tree .read(cx) .as_local() @@ -875,9 +887,10 @@ impl Project { } } - pub fn add_local_worktree( + fn add_local_worktree( &self, abs_path: impl AsRef, + weak: bool, cx: &mut ModelContext, ) -> Task>> { let fs = self.fs.clone(); @@ -886,10 +899,10 @@ impl Project { let path = Arc::from(abs_path.as_ref()); cx.spawn(|project, mut cx| async move { let worktree = - Worktree::open_local(client.clone(), user_store, path, fs, &mut cx).await?; + Worktree::open_local(client.clone(), user_store, path, weak, fs, &mut cx).await?; let (remote_project_id, is_shared) = project.update(&mut cx, |project, cx| { - project.add_worktree(worktree.clone(), cx); + project.add_worktree(&worktree, weak, cx); (project.remote_id(), project.is_shared()) }); @@ -913,14 +926,28 @@ impl Project { } pub fn remove_worktree(&mut self, id: WorktreeId, cx: &mut ModelContext) { - self.worktrees - .retain(|worktree| worktree.read(cx).id() != id); + self.worktrees.retain(|worktree| { + worktree + .upgrade(cx) + .map_or(false, |w| w.read(cx).id() != id) + }); cx.notify(); } - fn add_worktree(&mut self, worktree: ModelHandle, cx: &mut ModelContext) { + fn add_worktree( + &mut self, + worktree: &ModelHandle, + weak: bool, + cx: &mut ModelContext, + ) { cx.observe(&worktree, |_, _, cx| cx.notify()).detach(); - self.worktrees.push(worktree); + if weak { + self.worktrees + .push(WorktreeHandle::Weak(worktree.downgrade())); + } else { + self.worktrees + .push(WorktreeHandle::Strong(worktree.clone())); + } cx.notify(); } @@ -966,7 +993,7 @@ impl Project { &'a self, cx: &'a AppContext, ) -> impl Iterator + 'a { - self.worktrees.iter().flat_map(move |worktree| { + self.worktrees(cx).flat_map(move |worktree| { let worktree = worktree.read(cx); let worktree_id = worktree.id(); worktree @@ -1059,7 +1086,7 @@ impl Project { .remove(&peer_id) .ok_or_else(|| anyhow!("unknown peer {:?}", peer_id))? .replica_id; - for worktree in &self.worktrees { + for worktree in self.worktrees(cx).collect::>() { worktree.update(cx, |worktree, cx| { worktree.remove_collaborator(peer_id, replica_id, cx); }) @@ -1085,7 +1112,7 @@ impl Project { let worktree = Worktree::remote(remote_id, replica_id, worktree, client, user_store, &mut cx) .await?; - this.update(&mut cx, |this, cx| this.add_worktree(worktree, cx)); + this.update(&mut cx, |this, cx| this.add_worktree(&worktree, false, cx)); Ok(()) } .log_err() @@ -1293,8 +1320,7 @@ impl Project { ) -> impl 'a + Future> { let include_root_name = self.worktrees.len() > 1; let candidate_sets = self - .worktrees - .iter() + .worktrees(cx) .map(|worktree| CandidateSet { snapshot: worktree.read(cx).snapshot(), include_ignored, @@ -1317,6 +1343,15 @@ impl Project { } } +impl WorktreeHandle { + pub fn upgrade(&self, cx: &AppContext) -> Option> { + match self { + WorktreeHandle::Strong(handle) => Some(handle.clone()), + WorktreeHandle::Weak(handle) => handle.upgrade(cx), + } + } +} + struct CandidateSet { snapshot: Snapshot, include_ignored: bool, @@ -1489,7 +1524,7 @@ mod tests { let tree = project .update(&mut cx, |project, cx| { - project.add_local_worktree(&root_link_path, cx) + project.add_local_worktree(&root_link_path, false, cx) }) .await .unwrap(); @@ -1564,7 +1599,7 @@ mod tests { let tree = project .update(&mut cx, |project, cx| { - project.add_local_worktree(dir.path(), cx) + project.add_local_worktree(dir.path(), false, cx) }) .await .unwrap(); @@ -1670,7 +1705,7 @@ mod tests { let project = build_project(&mut cx); let tree = project .update(&mut cx, |project, cx| { - project.add_local_worktree(&dir.path(), cx) + project.add_local_worktree(&dir.path(), false, cx) }) .await .unwrap(); diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index ea245cacdc..eb42b95bc5 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -91,11 +91,12 @@ impl Worktree { client: Arc, user_store: ModelHandle, path: impl Into>, + weak: bool, fs: Arc, cx: &mut AsyncAppContext, ) -> Result> { let (tree, scan_states_tx) = - LocalWorktree::new(client, user_store, path, fs.clone(), cx).await?; + LocalWorktree::new(client, user_store, path, weak, fs.clone(), cx).await?; tree.update(cx, |tree, cx| { let tree = tree.as_local_mut().unwrap(); let abs_path = tree.snapshot.abs_path.clone(); @@ -126,6 +127,7 @@ impl Worktree { .map(|c| c.to_ascii_lowercase()) .collect(); let root_name = worktree.root_name.clone(); + let weak = worktree.weak; let (entries_by_path, entries_by_id, diagnostic_summaries) = cx .background() .spawn(async move { @@ -225,6 +227,7 @@ impl Worktree { queued_operations: Default::default(), user_store, diagnostic_summaries, + weak, }) }) }); @@ -271,6 +274,13 @@ impl Worktree { } } + pub fn is_weak(&self) -> bool { + match self { + Worktree::Local(worktree) => worktree.weak, + Worktree::Remote(worktree) => worktree.weak, + } + } + pub fn replica_id(&self) -> ReplicaId { match self { Worktree::Local(_) => 0, @@ -776,6 +786,7 @@ pub struct LocalWorktree { client: Arc, user_store: ModelHandle, fs: Arc, + weak: bool, } #[derive(Debug, Eq, PartialEq)] @@ -803,6 +814,7 @@ pub struct RemoteWorktree { user_store: ModelHandle, queued_operations: Vec<(u64, Operation)>, diagnostic_summaries: TreeMap, + weak: bool, } type LoadingBuffers = HashMap< @@ -822,6 +834,7 @@ impl LocalWorktree { client: Arc, user_store: ModelHandle, path: impl Into>, + weak: bool, fs: Arc, cx: &mut AsyncAppContext, ) -> Result<(ModelHandle, Sender)> { @@ -889,6 +902,7 @@ impl LocalWorktree { client, user_store, fs, + weak, }; cx.spawn_weak(|this, mut cx| async move { @@ -1415,10 +1429,11 @@ impl LocalWorktree { }); let diagnostic_summaries = self.diagnostic_summaries.clone(); + let weak = self.weak; let share_message = cx.background().spawn(async move { proto::ShareWorktree { project_id, - worktree: Some(snapshot.to_proto(&diagnostic_summaries)), + worktree: Some(snapshot.to_proto(&diagnostic_summaries, weak)), } }); @@ -1636,6 +1651,7 @@ impl Snapshot { pub fn to_proto( &self, diagnostic_summaries: &TreeMap, + weak: bool, ) -> proto::Worktree { let root_name = self.root_name.clone(); proto::Worktree { @@ -1651,6 +1667,7 @@ impl Snapshot { .iter() .map(|(path, summary)| summary.to_proto(path.0.clone())) .collect(), + weak, } } @@ -3110,6 +3127,7 @@ mod tests { client, user_store, Arc::from(Path::new("/root")), + false, Arc::new(fs), &mut cx.to_async(), ) @@ -3147,6 +3165,7 @@ mod tests { client, user_store, dir.path(), + false, Arc::new(RealFs), &mut cx.to_async(), ) @@ -3181,6 +3200,7 @@ mod tests { client, user_store, file_path.clone(), + false, Arc::new(RealFs), &mut cx.to_async(), ) @@ -3229,6 +3249,7 @@ mod tests { client, user_store.clone(), dir.path(), + false, Arc::new(RealFs), &mut cx.to_async(), ) @@ -3265,7 +3286,7 @@ mod tests { let remote = Worktree::remote( 1, 1, - initial_snapshot.to_proto(&Default::default()), + initial_snapshot.to_proto(&Default::default(), Default::default()), Client::new(http_client.clone()), user_store, &mut cx.to_async(), @@ -3379,6 +3400,7 @@ mod tests { client, user_store, dir.path(), + false, Arc::new(RealFs), &mut cx.to_async(), ) @@ -3431,6 +3453,7 @@ mod tests { client.clone(), user_store, "/the-dir".as_ref(), + false, fs, &mut cx.to_async(), ) @@ -3485,6 +3508,7 @@ mod tests { client, user_store, dir.path(), + false, Arc::new(RealFs), &mut cx.to_async(), ) @@ -3621,6 +3645,7 @@ mod tests { client, user_store, dir.path(), + false, Arc::new(RealFs), &mut cx.to_async(), ) @@ -3735,6 +3760,7 @@ mod tests { client.clone(), user_store, "/the-dir".as_ref(), + false, fs, &mut cx.to_async(), ) diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 382a942849..781c4cd586 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -6,8 +6,8 @@ use gpui::{ }, keymap::{self, Binding}, platform::CursorStyle, - AppContext, Element, ElementBox, Entity, ModelHandle, MutableAppContext, ReadModel, View, - ViewContext, ViewHandle, WeakViewHandle, + AppContext, Element, ElementBox, Entity, ModelHandle, MutableAppContext, View, ViewContext, + ViewHandle, WeakViewHandle, }; use postage::watch; use project::{Project, ProjectEntry, ProjectPath, Worktree, WorktreeId}; @@ -24,7 +24,7 @@ use workspace::{ pub struct ProjectPanel { project: ModelHandle, list: UniformListState, - visible_entries: Vec>, + visible_entries: Vec<(WorktreeId, Vec)>, expanded_dir_ids: HashMap>, selection: Option, settings: watch::Receiver, @@ -260,7 +260,11 @@ impl ProjectPanel { } fn select_first(&mut self, cx: &mut ViewContext) { - if let Some(worktree) = self.project.read(cx).worktrees().first() { + let worktree = self + .visible_entries + .first() + .and_then(|(worktree_id, _)| self.project.read(cx).worktree_for_id(*worktree_id, cx)); + if let Some(worktree) = worktree { let worktree = worktree.read(cx); let worktree_id = worktree.id(); if let Some(root_entry) = worktree.root_entry() { @@ -289,10 +293,11 @@ impl ProjectPanel { let project = self.project.read(cx); let mut offset = None; let mut ix = 0; - for (worktree_ix, visible_entries) in self.visible_entries.iter().enumerate() { + for (worktree_id, visible_entries) in &self.visible_entries { if target_ix < ix + visible_entries.len() { - let worktree = project.worktrees()[worktree_ix].read(cx); - offset = Some((worktree, visible_entries[target_ix - ix])); + offset = project + .worktree_for_id(*worktree_id, cx) + .map(|w| (w.read(cx), visible_entries[target_ix - ix])); break; } else { ix += visible_entries.len(); @@ -318,7 +323,11 @@ impl ProjectPanel { new_selected_entry: Option<(WorktreeId, usize)>, cx: &mut ViewContext, ) { - let worktrees = self.project.read(cx).worktrees(); + let worktrees = self + .project + .read(cx) + .worktrees(cx) + .filter(|worktree| !worktree.read(cx).is_weak()); self.visible_entries.clear(); let mut entry_ix = 0; @@ -369,7 +378,8 @@ impl ProjectPanel { } entry_iter.advance(); } - self.visible_entries.push(visible_worktree_entries); + self.visible_entries + .push((worktree_id, visible_worktree_entries)); } } @@ -404,16 +414,14 @@ impl ProjectPanel { } } - fn for_each_visible_entry( + fn for_each_visible_entry( &self, range: Range, - cx: &mut C, - mut callback: impl FnMut(ProjectEntry, EntryDetails, &mut C), + cx: &mut ViewContext, + mut callback: impl FnMut(ProjectEntry, EntryDetails, &mut ViewContext), ) { - let project = self.project.read(cx); - let worktrees = project.worktrees().to_vec(); let mut ix = 0; - for (worktree_ix, visible_worktree_entries) in self.visible_entries.iter().enumerate() { + for (worktree_id, visible_worktree_entries) in &self.visible_entries { if ix >= range.end { return; } @@ -423,37 +431,38 @@ impl ProjectPanel { } let end_ix = range.end.min(ix + visible_worktree_entries.len()); - let worktree = &worktrees[worktree_ix]; - let snapshot = worktree.read(cx).snapshot(); - let expanded_entry_ids = self - .expanded_dir_ids - .get(&snapshot.id()) - .map(Vec::as_slice) - .unwrap_or(&[]); - let root_name = OsStr::new(snapshot.root_name()); - let mut cursor = snapshot.entries(false); + if let Some(worktree) = self.project.read(cx).worktree_for_id(*worktree_id, cx) { + let snapshot = worktree.read(cx).snapshot(); + let expanded_entry_ids = self + .expanded_dir_ids + .get(&snapshot.id()) + .map(Vec::as_slice) + .unwrap_or(&[]); + let root_name = OsStr::new(snapshot.root_name()); + let mut cursor = snapshot.entries(false); - for ix in visible_worktree_entries[range.start.saturating_sub(ix)..end_ix - ix] - .iter() - .copied() - { - cursor.advance_to_offset(ix); - if let Some(entry) = cursor.entry() { - let filename = entry.path.file_name().unwrap_or(root_name); - let details = EntryDetails { - filename: filename.to_string_lossy().to_string(), - depth: entry.path.components().count(), - is_dir: entry.is_dir(), - is_expanded: expanded_entry_ids.binary_search(&entry.id).is_ok(), - is_selected: self.selection.map_or(false, |e| { - e.worktree_id == snapshot.id() && e.entry_id == entry.id - }), - }; - let entry = ProjectEntry { - worktree_id: snapshot.id(), - entry_id: entry.id, - }; - callback(entry, details, cx); + for ix in visible_worktree_entries[range.start.saturating_sub(ix)..end_ix - ix] + .iter() + .copied() + { + cursor.advance_to_offset(ix); + if let Some(entry) = cursor.entry() { + let filename = entry.path.file_name().unwrap_or(root_name); + let details = EntryDetails { + filename: filename.to_string_lossy().to_string(), + depth: entry.path.components().count(), + is_dir: entry.is_dir(), + is_expanded: expanded_entry_ids.binary_search(&entry.id).is_ok(), + is_selected: self.selection.map_or(false, |e| { + e.worktree_id == snapshot.id() && e.entry_id == entry.id + }), + }; + let entry = ProjectEntry { + worktree_id: snapshot.id(), + entry_id: entry.id, + }; + callback(entry, details, cx); + } } } ix = end_ix; @@ -545,7 +554,7 @@ impl View for ProjectPanel { self.list.clone(), self.visible_entries .iter() - .map(|worktree_entries| worktree_entries.len()) + .map(|(_, worktree_entries)| worktree_entries.len()) .sum(), move |range, items, cx| { let theme = &settings.borrow().theme.project_panel; @@ -633,18 +642,18 @@ mod tests { cx, ) }); - let root1 = project + let (root1, _) = project .update(&mut cx, |project, cx| { - project.add_local_worktree("/root1", cx) + project.find_or_create_worktree_for_abs_path("/root1", false, cx) }) .await .unwrap(); root1 .read_with(&cx, |t, _| t.as_local().unwrap().scan_complete()) .await; - let root2 = project + let (root2, _) = project .update(&mut cx, |project, cx| { - project.add_local_worktree("/root2", cx) + project.find_or_create_worktree_for_abs_path("/root2", false, cx) }) .await .unwrap(); @@ -827,7 +836,7 @@ mod tests { ) { let path = path.as_ref(); panel.update(cx, |panel, cx| { - for worktree in panel.project.read(cx).worktrees() { + for worktree in panel.project.read(cx).worktrees(cx).collect::>() { let worktree = worktree.read(cx); if let Ok(relative_path) = path.strip_prefix(worktree.root_name()) { let entry_id = worktree.entry_for_path(relative_path).unwrap().id; diff --git a/crates/rpc/proto/zed.proto b/crates/rpc/proto/zed.proto index 973d459310..9891ce21cf 100644 --- a/crates/rpc/proto/zed.proto +++ b/crates/rpc/proto/zed.proto @@ -272,6 +272,7 @@ message Worktree { string root_name = 2; repeated Entry entries = 3; repeated DiagnosticSummary diagnostic_summaries = 4; + bool weak = 5; } message Entry { diff --git a/crates/server/src/rpc.rs b/crates/server/src/rpc.rs index adfbeacd13..3ad1042136 100644 --- a/crates/server/src/rpc.rs +++ b/crates/server/src/rpc.rs @@ -309,6 +309,7 @@ impl Server { .values() .cloned() .collect(), + weak: worktree.weak, }) }) .collect(); @@ -421,6 +422,7 @@ impl Server { authorized_user_ids: contact_user_ids.clone(), root_name: request.payload.root_name, share: None, + weak: false, }, ); @@ -1158,8 +1160,10 @@ mod tests { cx, ) }); - let worktree_a = project_a - .update(&mut cx_a, |p, cx| p.add_local_worktree("/a", cx)) + let (worktree_a, _) = project_a + .update(&mut cx_a, |p, cx| { + p.find_or_create_worktree_for_abs_path("/a", false, cx) + }) .await .unwrap(); worktree_a @@ -1184,7 +1188,7 @@ mod tests { ) .await .unwrap(); - let worktree_b = project_b.update(&mut cx_b, |p, _| p.worktrees()[0].clone()); + let worktree_b = project_b.update(&mut cx_b, |p, cx| p.worktrees(cx).next().unwrap()); let replica_id_b = project_b.read_with(&cx_b, |project, _| { assert_eq!( @@ -1293,8 +1297,10 @@ mod tests { cx, ) }); - let worktree_a = project_a - .update(&mut cx_a, |p, cx| p.add_local_worktree("/a", cx)) + let (worktree_a, _) = project_a + .update(&mut cx_a, |p, cx| { + p.find_or_create_worktree_for_abs_path("/a", false, cx) + }) .await .unwrap(); worktree_a @@ -1321,7 +1327,7 @@ mod tests { .await .unwrap(); - let worktree_b = project_b.read_with(&cx_b, |p, _| p.worktrees()[0].clone()); + let worktree_b = project_b.read_with(&cx_b, |p, cx| p.worktrees(cx).next().unwrap()); worktree_b .update(&mut cx_b, |tree, cx| tree.open_buffer("a.txt", cx)) .await @@ -1353,7 +1359,7 @@ mod tests { ) .await .unwrap(); - let worktree_c = project_c.read_with(&cx_b, |p, _| p.worktrees()[0].clone()); + let worktree_c = project_c.read_with(&cx_b, |p, cx| p.worktrees(cx).next().unwrap()); worktree_c .update(&mut cx_b, |tree, cx| tree.open_buffer("a.txt", cx)) .await @@ -1395,8 +1401,10 @@ mod tests { cx, ) }); - let worktree_a = project_a - .update(&mut cx_a, |p, cx| p.add_local_worktree("/a", cx)) + let (worktree_a, _) = project_a + .update(&mut cx_a, |p, cx| { + p.find_or_create_worktree_for_abs_path("/a", false, cx) + }) .await .unwrap(); worktree_a @@ -1433,8 +1441,8 @@ mod tests { .unwrap(); // Open and edit a buffer as both guests B and C. - let worktree_b = project_b.read_with(&cx_b, |p, _| p.worktrees()[0].clone()); - let worktree_c = project_c.read_with(&cx_c, |p, _| p.worktrees()[0].clone()); + let worktree_b = project_b.read_with(&cx_b, |p, cx| p.worktrees(cx).next().unwrap()); + let worktree_c = project_c.read_with(&cx_c, |p, cx| p.worktrees(cx).next().unwrap()); let buffer_b = worktree_b .update(&mut cx_b, |tree, cx| tree.open_buffer("file1", cx)) .await @@ -1547,8 +1555,10 @@ mod tests { cx, ) }); - let worktree_a = project_a - .update(&mut cx_a, |p, cx| p.add_local_worktree("/dir", cx)) + let (worktree_a, _) = project_a + .update(&mut cx_a, |p, cx| { + p.find_or_create_worktree_for_abs_path("/dir", false, cx) + }) .await .unwrap(); worktree_a @@ -1573,7 +1583,7 @@ mod tests { ) .await .unwrap(); - let worktree_b = project_b.update(&mut cx_b, |p, _| p.worktrees()[0].clone()); + let worktree_b = project_b.update(&mut cx_b, |p, cx| p.worktrees(cx).next().unwrap()); // Open a buffer as client B let buffer_b = worktree_b @@ -1642,8 +1652,10 @@ mod tests { cx, ) }); - let worktree_a = project_a - .update(&mut cx_a, |p, cx| p.add_local_worktree("/dir", cx)) + let (worktree_a, _) = project_a + .update(&mut cx_a, |p, cx| { + p.find_or_create_worktree_for_abs_path("/dir", false, cx) + }) .await .unwrap(); worktree_a @@ -1668,7 +1680,7 @@ mod tests { ) .await .unwrap(); - let worktree_b = project_b.update(&mut cx_b, |p, _| p.worktrees()[0].clone()); + let worktree_b = project_b.update(&mut cx_b, |p, cx| p.worktrees(cx).next().unwrap()); // Open a buffer as client A let buffer_a = worktree_a @@ -1723,8 +1735,10 @@ mod tests { cx, ) }); - let worktree_a = project_a - .update(&mut cx_a, |p, cx| p.add_local_worktree("/dir", cx)) + let (worktree_a, _) = project_a + .update(&mut cx_a, |p, cx| { + p.find_or_create_worktree_for_abs_path("/dir", false, cx) + }) .await .unwrap(); worktree_a @@ -1749,7 +1763,7 @@ mod tests { ) .await .unwrap(); - let worktree_b = project_b.update(&mut cx_b, |p, _| p.worktrees()[0].clone()); + let worktree_b = project_b.update(&mut cx_b, |p, cx| p.worktrees(cx).next().unwrap()); // See that a guest has joined as client A. project_a @@ -1799,8 +1813,10 @@ mod tests { cx, ) }); - let worktree_a = project_a - .update(&mut cx_a, |p, cx| p.add_local_worktree("/a", cx)) + let (worktree_a, _) = project_a + .update(&mut cx_a, |p, cx| { + p.find_or_create_worktree_for_abs_path("/a", false, cx) + }) .await .unwrap(); worktree_a @@ -1886,8 +1902,10 @@ mod tests { cx, ) }); - let worktree_a = project_a - .update(&mut cx_a, |p, cx| p.add_local_worktree("/a", cx)) + let (worktree_a, _) = project_a + .update(&mut cx_a, |p, cx| { + p.find_or_create_worktree_for_abs_path("/a", false, cx) + }) .await .unwrap(); worktree_a @@ -2023,7 +2041,7 @@ mod tests { .await; // Open the file with the errors on client B. They should be present. - let worktree_b = project_b.update(&mut cx_b, |p, _| p.worktrees()[0].clone()); + let worktree_b = project_b.update(&mut cx_b, |p, cx| p.worktrees(cx).next().unwrap()); let buffer_b = cx_b .background() .spawn(worktree_b.update(&mut cx_b, |worktree, cx| worktree.open_buffer("a.rs", cx))) @@ -2108,8 +2126,10 @@ mod tests { cx, ) }); - let worktree_a = project_a - .update(&mut cx_a, |p, cx| p.add_local_worktree("/a", cx)) + let (worktree_a, _) = project_a + .update(&mut cx_a, |p, cx| { + p.find_or_create_worktree_for_abs_path("/a", false, cx) + }) .await .unwrap(); worktree_a @@ -2136,7 +2156,7 @@ mod tests { .unwrap(); // Open the file to be formatted on client B. - let worktree_b = project_b.update(&mut cx_b, |p, _| p.worktrees()[0].clone()); + let worktree_b = project_b.update(&mut cx_b, |p, cx| p.worktrees(cx).next().unwrap()); let buffer_b = cx_b .background() .spawn(worktree_b.update(&mut cx_b, |worktree, cx| worktree.open_buffer("a.rs", cx))) @@ -2616,8 +2636,10 @@ mod tests { cx, ) }); - let worktree_a = project_a - .update(&mut cx_a, |p, cx| p.add_local_worktree("/a", cx)) + let (worktree_a, _) = project_a + .update(&mut cx_a, |p, cx| { + p.find_or_create_worktree_for_abs_path("/a", false, cx) + }) .await .unwrap(); worktree_a diff --git a/crates/server/src/rpc/store.rs b/crates/server/src/rpc/store.rs index a115b27db6..b7aec2689b 100644 --- a/crates/server/src/rpc/store.rs +++ b/crates/server/src/rpc/store.rs @@ -31,6 +31,7 @@ pub struct Worktree { pub authorized_user_ids: Vec, pub root_name: String, pub share: Option, + pub weak: bool, } #[derive(Default)] @@ -202,6 +203,7 @@ impl Store { let mut worktree_root_names = project .worktrees .values() + .filter(|worktree| !worktree.weak) .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 51709749a6..ebd29e69e7 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -639,8 +639,11 @@ impl Workspace { &self.project } - pub fn worktrees<'a>(&self, cx: &'a AppContext) -> &'a [ModelHandle] { - &self.project.read(cx).worktrees() + pub fn worktrees<'a>( + &self, + cx: &'a AppContext, + ) -> impl 'a + Iterator> { + self.project.read(cx).worktrees(cx) } pub fn contains_paths(&self, paths: &[PathBuf], cx: &AppContext) -> bool { @@ -660,7 +663,6 @@ impl Workspace { pub fn worktree_scans_complete(&self, cx: &AppContext) -> impl Future + 'static { let futures = self .worktrees(cx) - .iter() .filter_map(|worktree| worktree.read(cx).as_local()) .map(|worktree| worktree.scan_complete()) .collect::>(); @@ -720,7 +722,7 @@ impl Workspace { cx: &mut ViewContext, ) -> Task> { let entry = self.project().update(cx, |project, cx| { - project.find_or_create_worktree_for_abs_path(abs_path, cx) + project.find_or_create_worktree_for_abs_path(abs_path, false, cx) }); cx.spawn(|_, cx| async move { let (worktree, path) = entry.await?; @@ -731,15 +733,6 @@ impl Workspace { }) } - pub fn add_worktree( - &self, - path: &Path, - cx: &mut ViewContext, - ) -> Task>> { - self.project - .update(cx, |project, cx| project.add_local_worktree(path, cx)) - } - pub fn toggle_modal(&mut self, cx: &mut ViewContext, add_view: F) where V: 'static + View, @@ -866,7 +859,7 @@ impl Workspace { item.save(cx) } } else if item.can_save_as(cx) { - let worktree = self.worktrees(cx).first(); + let worktree = self.worktrees(cx).next(); let start_abs_path = worktree .and_then(|w| w.read(cx).as_local()) .map_or(Path::new(""), |w| w.abs_path()) @@ -1343,7 +1336,6 @@ impl WorkspaceHandle for ViewHandle { fn file_project_paths(&self, cx: &AppContext) -> Vec { self.read(cx) .worktrees(cx) - .iter() .flat_map(|worktree| { let worktree_id = worktree.read(cx).id(); worktree.read(cx).files(true, 0).map(move |f| ProjectPath { diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 22eef7fe40..e636cced93 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -175,7 +175,7 @@ mod tests { assert_eq!(cx.window_ids().len(), 1); let workspace_1 = cx.root_view::(cx.window_ids()[0]).unwrap(); workspace_1.read_with(&cx, |workspace, cx| { - assert_eq!(workspace.worktrees(cx).len(), 2) + assert_eq!(workspace.worktrees(cx).count(), 2) }); cx.update(|cx| { @@ -242,9 +242,10 @@ mod tests { .await; let params = cx.update(|cx| WorkspaceParams::local(&app_state, cx)); let (_, workspace) = cx.add_window(|cx| Workspace::new(¶ms, cx)); - workspace - .update(&mut cx, |workspace, cx| { - workspace.add_worktree(Path::new("/root"), cx) + params + .project + .update(&mut cx, |project, cx| { + project.find_or_create_worktree_for_abs_path(Path::new("/root"), false, cx) }) .await .unwrap(); @@ -366,9 +367,10 @@ mod tests { let params = cx.update(|cx| WorkspaceParams::local(&app_state, cx)); let (_, workspace) = cx.add_window(|cx| Workspace::new(¶ms, cx)); - workspace - .update(&mut cx, |workspace, cx| { - workspace.add_worktree("/dir1".as_ref(), cx) + params + .project + .update(&mut cx, |project, cx| { + project.find_or_create_worktree_for_abs_path(Path::new("/dir1"), false, cx) }) .await .unwrap(); @@ -402,7 +404,6 @@ mod tests { let worktree_roots = workspace .read(cx) .worktrees(cx) - .iter() .map(|w| w.read(cx).as_local().unwrap().abs_path().as_ref()) .collect::>(); assert_eq!( @@ -433,9 +434,10 @@ mod tests { let params = cx.update(|cx| WorkspaceParams::local(&app_state, cx)); let (window_id, workspace) = cx.add_window(|cx| Workspace::new(¶ms, cx)); - workspace - .update(&mut cx, |workspace, cx| { - workspace.add_worktree(Path::new("/root"), cx) + params + .project + .update(&mut cx, |project, cx| { + project.find_or_create_worktree_for_abs_path(Path::new("/root"), false, cx) }) .await .unwrap(); @@ -481,21 +483,14 @@ mod tests { app_state.fs.as_fake().insert_dir("/root").await.unwrap(); let params = cx.update(|cx| WorkspaceParams::local(&app_state, cx)); let (window_id, workspace) = cx.add_window(|cx| Workspace::new(¶ms, cx)); - workspace - .update(&mut cx, |workspace, cx| { - workspace.add_worktree(Path::new("/root"), cx) + params + .project + .update(&mut cx, |project, cx| { + project.find_or_create_worktree_for_abs_path(Path::new("/root"), false, cx) }) .await .unwrap(); - let worktree = cx.read(|cx| { - workspace - .read(cx) - .worktrees(cx) - .iter() - .next() - .unwrap() - .clone() - }); + let worktree = cx.read(|cx| workspace.read(cx).worktrees(cx).next().unwrap()); // Create a new untitled buffer cx.dispatch_action(window_id, vec![workspace.id()], OpenNew(app_state.clone())); @@ -640,9 +635,10 @@ mod tests { let params = cx.update(|cx| WorkspaceParams::local(&app_state, cx)); let (window_id, workspace) = cx.add_window(|cx| Workspace::new(¶ms, cx)); - workspace - .update(&mut cx, |workspace, cx| { - workspace.add_worktree(Path::new("/root"), cx) + params + .project + .update(&mut cx, |project, cx| { + project.find_or_create_worktree_for_abs_path(Path::new("/root"), false, cx) }) .await .unwrap(); @@ -717,9 +713,10 @@ mod tests { .await; let params = cx.update(|cx| WorkspaceParams::local(&app_state, cx)); let (_, workspace) = cx.add_window(|cx| Workspace::new(¶ms, cx)); - workspace - .update(&mut cx, |workspace, cx| { - workspace.add_worktree(Path::new("/root"), cx) + params + .project + .update(&mut cx, |project, cx| { + project.find_or_create_worktree_for_abs_path(Path::new("/root"), false, cx) }) .await .unwrap();