1pub mod lsp_status;
2pub mod menu;
3pub mod pane;
4pub mod pane_group;
5pub mod settings;
6pub mod sidebar;
7mod status_bar;
8
9use anyhow::{anyhow, Context, Result};
10use client::{
11 proto, Authenticate, ChannelList, Client, PeerId, Subscription, TypedEnvelope, User, UserStore,
12};
13use clock::ReplicaId;
14use collections::{hash_map, HashMap, HashSet};
15use gpui::{
16 action,
17 color::Color,
18 elements::*,
19 geometry::{vector::vec2f, PathBuilder},
20 json::{self, to_string_pretty, ToJson},
21 keymap::Binding,
22 platform::{CursorStyle, WindowOptions},
23 AnyModelHandle, AnyViewHandle, AppContext, AsyncAppContext, Border, ClipboardItem, Entity,
24 ImageData, ModelHandle, MutableAppContext, PathPromptOptions, PromptLevel, RenderContext, Task,
25 View, ViewContext, ViewHandle, WeakViewHandle,
26};
27use language::LanguageRegistry;
28use log::error;
29pub use pane::*;
30pub use pane_group::*;
31use postage::prelude::Stream;
32use project::{fs, Fs, Project, ProjectEntryId, ProjectPath, Worktree};
33pub use settings::Settings;
34use sidebar::{Side, Sidebar, SidebarItemId, ToggleSidebarItem, ToggleSidebarItemFocus};
35use status_bar::StatusBar;
36pub use status_bar::StatusItemView;
37use std::{
38 any::{Any, TypeId},
39 cell::RefCell,
40 fmt,
41 future::Future,
42 path::{Path, PathBuf},
43 rc::Rc,
44 sync::{
45 atomic::{AtomicBool, Ordering::SeqCst},
46 Arc,
47 },
48};
49use theme::{Theme, ThemeRegistry};
50use util::ResultExt;
51
52type ProjectItemBuilders = HashMap<
53 TypeId,
54 fn(usize, ModelHandle<Project>, AnyModelHandle, &mut MutableAppContext) -> Box<dyn ItemHandle>,
55>;
56
57type FollowableItemBuilder = fn(
58 ViewHandle<Pane>,
59 ModelHandle<Project>,
60 &mut Option<proto::view::Variant>,
61 &mut MutableAppContext,
62) -> Option<Task<Result<Box<dyn FollowableItemHandle>>>>;
63type FollowableItemBuilders = HashMap<
64 TypeId,
65 (
66 FollowableItemBuilder,
67 fn(AnyViewHandle) -> Box<dyn FollowableItemHandle>,
68 ),
69>;
70
71action!(Open, Arc<AppState>);
72action!(OpenNew, Arc<AppState>);
73action!(OpenPaths, OpenParams);
74action!(ToggleShare);
75action!(ToggleFollow, PeerId);
76action!(FollowNextCollaborator);
77action!(Unfollow);
78action!(JoinProject, JoinProjectParams);
79action!(Save);
80action!(DebugElements);
81
82pub fn init(client: &Arc<Client>, cx: &mut MutableAppContext) {
83 pane::init(cx);
84 menu::init(cx);
85
86 cx.add_global_action(open);
87 cx.add_global_action(move |action: &OpenPaths, cx: &mut MutableAppContext| {
88 open_paths(&action.0.paths, &action.0.app_state, cx).detach();
89 });
90 cx.add_global_action(move |action: &OpenNew, cx: &mut MutableAppContext| {
91 open_new(&action.0, cx)
92 });
93 cx.add_global_action(move |action: &JoinProject, cx: &mut MutableAppContext| {
94 join_project(action.0.project_id, &action.0.app_state, cx).detach();
95 });
96
97 cx.add_action(Workspace::toggle_share);
98 cx.add_async_action(Workspace::toggle_follow);
99 cx.add_async_action(Workspace::follow_next_collaborator);
100 cx.add_action(
101 |workspace: &mut Workspace, _: &Unfollow, cx: &mut ViewContext<Workspace>| {
102 let pane = workspace.active_pane().clone();
103 workspace.unfollow(&pane, cx);
104 },
105 );
106 cx.add_action(
107 |workspace: &mut Workspace, _: &Save, cx: &mut ViewContext<Workspace>| {
108 workspace.save_active_item(cx).detach_and_log_err(cx);
109 },
110 );
111 cx.add_action(Workspace::debug_elements);
112 cx.add_action(Workspace::toggle_sidebar_item);
113 cx.add_action(Workspace::toggle_sidebar_item_focus);
114 cx.add_bindings(vec![
115 Binding::new("cmd-alt-shift-F", FollowNextCollaborator, None),
116 Binding::new("cmd-alt-shift-U", Unfollow, None),
117 Binding::new("cmd-s", Save, None),
118 Binding::new("cmd-alt-i", DebugElements, None),
119 Binding::new(
120 "cmd-shift-!",
121 ToggleSidebarItem(SidebarItemId {
122 side: Side::Left,
123 item_index: 0,
124 }),
125 None,
126 ),
127 Binding::new(
128 "cmd-1",
129 ToggleSidebarItemFocus(SidebarItemId {
130 side: Side::Left,
131 item_index: 0,
132 }),
133 None,
134 ),
135 ]);
136
137 client.add_view_request_handler(Workspace::handle_follow);
138 client.add_view_message_handler(Workspace::handle_unfollow);
139 client.add_view_message_handler(Workspace::handle_update_followers);
140}
141
142pub fn register_project_item<I: ProjectItem>(cx: &mut MutableAppContext) {
143 cx.update_default_global(|builders: &mut ProjectItemBuilders, _| {
144 builders.insert(TypeId::of::<I::Item>(), |window_id, project, model, cx| {
145 let item = model.downcast::<I::Item>().unwrap();
146 Box::new(cx.add_view(window_id, |cx| I::for_project_item(project, item, cx)))
147 });
148 });
149}
150
151pub fn register_followable_item<I: FollowableItem>(cx: &mut MutableAppContext) {
152 cx.update_default_global(|builders: &mut FollowableItemBuilders, _| {
153 builders.insert(
154 TypeId::of::<I>(),
155 (
156 |pane, project, state, cx| {
157 I::from_state_proto(pane, project, state, cx).map(|task| {
158 cx.foreground()
159 .spawn(async move { Ok(Box::new(task.await?) as Box<_>) })
160 })
161 },
162 |this| Box::new(this.downcast::<I>().unwrap()),
163 ),
164 );
165 });
166}
167
168pub struct AppState {
169 pub languages: Arc<LanguageRegistry>,
170 pub themes: Arc<ThemeRegistry>,
171 pub client: Arc<client::Client>,
172 pub user_store: ModelHandle<client::UserStore>,
173 pub fs: Arc<dyn fs::Fs>,
174 pub channel_list: ModelHandle<client::ChannelList>,
175 pub build_window_options: &'static dyn Fn() -> WindowOptions<'static>,
176 pub build_workspace: &'static dyn Fn(
177 ModelHandle<Project>,
178 &Arc<AppState>,
179 &mut ViewContext<Workspace>,
180 ) -> Workspace,
181}
182
183#[derive(Clone)]
184pub struct OpenParams {
185 pub paths: Vec<PathBuf>,
186 pub app_state: Arc<AppState>,
187}
188
189#[derive(Clone)]
190pub struct JoinProjectParams {
191 pub project_id: u64,
192 pub app_state: Arc<AppState>,
193}
194
195pub trait Item: View {
196 fn deactivated(&mut self, _: &mut ViewContext<Self>) {}
197 fn navigate(&mut self, _: Box<dyn Any>, _: &mut ViewContext<Self>) {}
198 fn tab_content(&self, style: &theme::Tab, cx: &AppContext) -> ElementBox;
199 fn project_path(&self, cx: &AppContext) -> Option<ProjectPath>;
200 fn project_entry_id(&self, cx: &AppContext) -> Option<ProjectEntryId>;
201 fn set_nav_history(&mut self, _: ItemNavHistory, _: &mut ViewContext<Self>);
202 fn clone_on_split(&self, _: &mut ViewContext<Self>) -> Option<Self>
203 where
204 Self: Sized,
205 {
206 None
207 }
208 fn is_dirty(&self, _: &AppContext) -> bool {
209 false
210 }
211 fn has_conflict(&self, _: &AppContext) -> bool {
212 false
213 }
214 fn can_save(&self, cx: &AppContext) -> bool;
215 fn save(
216 &mut self,
217 project: ModelHandle<Project>,
218 cx: &mut ViewContext<Self>,
219 ) -> Task<Result<()>>;
220 fn can_save_as(&self, cx: &AppContext) -> bool;
221 fn save_as(
222 &mut self,
223 project: ModelHandle<Project>,
224 abs_path: PathBuf,
225 cx: &mut ViewContext<Self>,
226 ) -> Task<Result<()>>;
227 fn should_activate_item_on_event(_: &Self::Event) -> bool {
228 false
229 }
230 fn should_close_item_on_event(_: &Self::Event) -> bool {
231 false
232 }
233 fn should_update_tab_on_event(_: &Self::Event) -> bool {
234 false
235 }
236 fn act_as_type(
237 &self,
238 type_id: TypeId,
239 self_handle: &ViewHandle<Self>,
240 _: &AppContext,
241 ) -> Option<AnyViewHandle> {
242 if TypeId::of::<Self>() == type_id {
243 Some(self_handle.into())
244 } else {
245 None
246 }
247 }
248}
249
250pub trait ProjectItem: Item {
251 type Item: project::Item;
252
253 fn for_project_item(
254 project: ModelHandle<Project>,
255 item: ModelHandle<Self::Item>,
256 cx: &mut ViewContext<Self>,
257 ) -> Self;
258}
259
260pub trait FollowableItem: Item {
261 fn to_state_proto(&self, cx: &AppContext) -> Option<proto::view::Variant>;
262 fn from_state_proto(
263 pane: ViewHandle<Pane>,
264 project: ModelHandle<Project>,
265 state: &mut Option<proto::view::Variant>,
266 cx: &mut MutableAppContext,
267 ) -> Option<Task<Result<ViewHandle<Self>>>>;
268 fn add_event_to_update_proto(
269 &self,
270 event: &Self::Event,
271 update: &mut Option<proto::update_view::Variant>,
272 cx: &AppContext,
273 ) -> bool;
274 fn apply_update_proto(
275 &mut self,
276 message: proto::update_view::Variant,
277 cx: &mut ViewContext<Self>,
278 ) -> Result<()>;
279
280 fn set_leader_replica_id(&mut self, leader_replica_id: Option<u16>, cx: &mut ViewContext<Self>);
281 fn should_unfollow_on_event(event: &Self::Event, cx: &AppContext) -> bool;
282}
283
284pub trait FollowableItemHandle: ItemHandle {
285 fn set_leader_replica_id(&self, leader_replica_id: Option<u16>, cx: &mut MutableAppContext);
286 fn to_state_proto(&self, cx: &AppContext) -> Option<proto::view::Variant>;
287 fn add_event_to_update_proto(
288 &self,
289 event: &dyn Any,
290 update: &mut Option<proto::update_view::Variant>,
291 cx: &AppContext,
292 ) -> bool;
293 fn apply_update_proto(
294 &self,
295 message: proto::update_view::Variant,
296 cx: &mut MutableAppContext,
297 ) -> Result<()>;
298 fn should_unfollow_on_event(&self, event: &dyn Any, cx: &AppContext) -> bool;
299}
300
301impl<T: FollowableItem> FollowableItemHandle for ViewHandle<T> {
302 fn set_leader_replica_id(&self, leader_replica_id: Option<u16>, cx: &mut MutableAppContext) {
303 self.update(cx, |this, cx| {
304 this.set_leader_replica_id(leader_replica_id, cx)
305 })
306 }
307
308 fn to_state_proto(&self, cx: &AppContext) -> Option<proto::view::Variant> {
309 self.read(cx).to_state_proto(cx)
310 }
311
312 fn add_event_to_update_proto(
313 &self,
314 event: &dyn Any,
315 update: &mut Option<proto::update_view::Variant>,
316 cx: &AppContext,
317 ) -> bool {
318 if let Some(event) = event.downcast_ref() {
319 self.read(cx).add_event_to_update_proto(event, update, cx)
320 } else {
321 false
322 }
323 }
324
325 fn apply_update_proto(
326 &self,
327 message: proto::update_view::Variant,
328 cx: &mut MutableAppContext,
329 ) -> Result<()> {
330 self.update(cx, |this, cx| this.apply_update_proto(message, cx))
331 }
332
333 fn should_unfollow_on_event(&self, event: &dyn Any, cx: &AppContext) -> bool {
334 if let Some(event) = event.downcast_ref() {
335 T::should_unfollow_on_event(event, cx)
336 } else {
337 false
338 }
339 }
340}
341
342pub trait ItemHandle: 'static + fmt::Debug {
343 fn tab_content(&self, style: &theme::Tab, cx: &AppContext) -> ElementBox;
344 fn project_path(&self, cx: &AppContext) -> Option<ProjectPath>;
345 fn project_entry_id(&self, cx: &AppContext) -> Option<ProjectEntryId>;
346 fn boxed_clone(&self) -> Box<dyn ItemHandle>;
347 fn set_nav_history(&self, nav_history: Rc<RefCell<NavHistory>>, cx: &mut MutableAppContext);
348 fn clone_on_split(&self, cx: &mut MutableAppContext) -> Option<Box<dyn ItemHandle>>;
349 fn added_to_pane(
350 &self,
351 workspace: &mut Workspace,
352 pane: ViewHandle<Pane>,
353 cx: &mut ViewContext<Workspace>,
354 );
355 fn deactivated(&self, cx: &mut MutableAppContext);
356 fn navigate(&self, data: Box<dyn Any>, cx: &mut MutableAppContext);
357 fn id(&self) -> usize;
358 fn to_any(&self) -> AnyViewHandle;
359 fn is_dirty(&self, cx: &AppContext) -> bool;
360 fn has_conflict(&self, cx: &AppContext) -> bool;
361 fn can_save(&self, cx: &AppContext) -> bool;
362 fn can_save_as(&self, cx: &AppContext) -> bool;
363 fn save(&self, project: ModelHandle<Project>, cx: &mut MutableAppContext) -> Task<Result<()>>;
364 fn save_as(
365 &self,
366 project: ModelHandle<Project>,
367 abs_path: PathBuf,
368 cx: &mut MutableAppContext,
369 ) -> Task<Result<()>>;
370 fn act_as_type(&self, type_id: TypeId, cx: &AppContext) -> Option<AnyViewHandle>;
371 fn to_followable_item_handle(&self, cx: &AppContext) -> Option<Box<dyn FollowableItemHandle>>;
372}
373
374pub trait WeakItemHandle {
375 fn id(&self) -> usize;
376 fn upgrade(&self, cx: &AppContext) -> Option<Box<dyn ItemHandle>>;
377}
378
379impl dyn ItemHandle {
380 pub fn downcast<T: View>(&self) -> Option<ViewHandle<T>> {
381 self.to_any().downcast()
382 }
383
384 pub fn act_as<T: View>(&self, cx: &AppContext) -> Option<ViewHandle<T>> {
385 self.act_as_type(TypeId::of::<T>(), cx)
386 .and_then(|t| t.downcast())
387 }
388}
389
390impl<T: Item> ItemHandle for ViewHandle<T> {
391 fn tab_content(&self, style: &theme::Tab, cx: &AppContext) -> ElementBox {
392 self.read(cx).tab_content(style, cx)
393 }
394
395 fn project_path(&self, cx: &AppContext) -> Option<ProjectPath> {
396 self.read(cx).project_path(cx)
397 }
398
399 fn project_entry_id(&self, cx: &AppContext) -> Option<ProjectEntryId> {
400 self.read(cx).project_entry_id(cx)
401 }
402
403 fn boxed_clone(&self) -> Box<dyn ItemHandle> {
404 Box::new(self.clone())
405 }
406
407 fn clone_on_split(&self, cx: &mut MutableAppContext) -> Option<Box<dyn ItemHandle>> {
408 self.update(cx, |item, cx| {
409 cx.add_option_view(|cx| item.clone_on_split(cx))
410 })
411 .map(|handle| Box::new(handle) as Box<dyn ItemHandle>)
412 }
413
414 fn set_nav_history(&self, nav_history: Rc<RefCell<NavHistory>>, cx: &mut MutableAppContext) {
415 self.update(cx, |item, cx| {
416 item.set_nav_history(ItemNavHistory::new(nav_history, &cx.handle()), cx);
417 })
418 }
419
420 fn added_to_pane(
421 &self,
422 workspace: &mut Workspace,
423 pane: ViewHandle<Pane>,
424 cx: &mut ViewContext<Workspace>,
425 ) {
426 if let Some(followed_item) = self.to_followable_item_handle(cx) {
427 if let Some(message) = followed_item.to_state_proto(cx) {
428 workspace.update_followers(
429 proto::update_followers::Variant::CreateView(proto::View {
430 id: followed_item.id() as u64,
431 variant: Some(message),
432 leader_id: workspace.leader_for_pane(&pane).map(|id| id.0),
433 }),
434 cx,
435 );
436 }
437 }
438
439 let pending_update = Rc::new(RefCell::new(None));
440 let pending_update_scheduled = Rc::new(AtomicBool::new(false));
441 let pane = pane.downgrade();
442 cx.subscribe(self, move |workspace, item, event, cx| {
443 let pane = if let Some(pane) = pane.upgrade(cx) {
444 pane
445 } else {
446 log::error!("unexpected item event after pane was dropped");
447 return;
448 };
449
450 if let Some(item) = item.to_followable_item_handle(cx) {
451 let leader_id = workspace.leader_for_pane(&pane);
452
453 if leader_id.is_some() && item.should_unfollow_on_event(event, cx) {
454 workspace.unfollow(&pane, cx);
455 }
456
457 if item.add_event_to_update_proto(event, &mut *pending_update.borrow_mut(), cx)
458 && !pending_update_scheduled.load(SeqCst)
459 {
460 pending_update_scheduled.store(true, SeqCst);
461 cx.after_window_update({
462 let pending_update = pending_update.clone();
463 let pending_update_scheduled = pending_update_scheduled.clone();
464 move |this, cx| {
465 pending_update_scheduled.store(false, SeqCst);
466 this.update_followers(
467 proto::update_followers::Variant::UpdateView(proto::UpdateView {
468 id: item.id() as u64,
469 variant: pending_update.borrow_mut().take(),
470 leader_id: leader_id.map(|id| id.0),
471 }),
472 cx,
473 );
474 }
475 });
476 }
477 }
478
479 if T::should_close_item_on_event(event) {
480 pane.update(cx, |pane, cx| pane.close_item(item.id(), cx));
481 return;
482 }
483
484 if T::should_activate_item_on_event(event) {
485 pane.update(cx, |pane, cx| {
486 if let Some(ix) = pane.index_for_item(&item) {
487 pane.activate_item(ix, true, cx);
488 pane.activate(cx);
489 }
490 });
491 }
492
493 if T::should_update_tab_on_event(event) {
494 pane.update(cx, |_, cx| cx.notify());
495 }
496 })
497 .detach();
498 }
499
500 fn deactivated(&self, cx: &mut MutableAppContext) {
501 self.update(cx, |this, cx| this.deactivated(cx));
502 }
503
504 fn navigate(&self, data: Box<dyn Any>, cx: &mut MutableAppContext) {
505 self.update(cx, |this, cx| this.navigate(data, cx));
506 }
507
508 fn save(&self, project: ModelHandle<Project>, cx: &mut MutableAppContext) -> Task<Result<()>> {
509 self.update(cx, |item, cx| item.save(project, cx))
510 }
511
512 fn save_as(
513 &self,
514 project: ModelHandle<Project>,
515 abs_path: PathBuf,
516 cx: &mut MutableAppContext,
517 ) -> Task<anyhow::Result<()>> {
518 self.update(cx, |item, cx| item.save_as(project, abs_path, cx))
519 }
520
521 fn is_dirty(&self, cx: &AppContext) -> bool {
522 self.read(cx).is_dirty(cx)
523 }
524
525 fn has_conflict(&self, cx: &AppContext) -> bool {
526 self.read(cx).has_conflict(cx)
527 }
528
529 fn id(&self) -> usize {
530 self.id()
531 }
532
533 fn to_any(&self) -> AnyViewHandle {
534 self.into()
535 }
536
537 fn can_save(&self, cx: &AppContext) -> bool {
538 self.read(cx).can_save(cx)
539 }
540
541 fn can_save_as(&self, cx: &AppContext) -> bool {
542 self.read(cx).can_save_as(cx)
543 }
544
545 fn act_as_type(&self, type_id: TypeId, cx: &AppContext) -> Option<AnyViewHandle> {
546 self.read(cx).act_as_type(type_id, self, cx)
547 }
548
549 fn to_followable_item_handle(&self, cx: &AppContext) -> Option<Box<dyn FollowableItemHandle>> {
550 if cx.has_global::<FollowableItemBuilders>() {
551 let builders = cx.global::<FollowableItemBuilders>();
552 let item = self.to_any();
553 Some(builders.get(&item.view_type())?.1(item))
554 } else {
555 None
556 }
557 }
558}
559
560impl Into<AnyViewHandle> for Box<dyn ItemHandle> {
561 fn into(self) -> AnyViewHandle {
562 self.to_any()
563 }
564}
565
566impl Clone for Box<dyn ItemHandle> {
567 fn clone(&self) -> Box<dyn ItemHandle> {
568 self.boxed_clone()
569 }
570}
571
572impl<T: Item> WeakItemHandle for WeakViewHandle<T> {
573 fn id(&self) -> usize {
574 self.id()
575 }
576
577 fn upgrade(&self, cx: &AppContext) -> Option<Box<dyn ItemHandle>> {
578 self.upgrade(cx).map(|v| Box::new(v) as Box<dyn ItemHandle>)
579 }
580}
581
582#[derive(Clone)]
583pub struct WorkspaceParams {
584 pub project: ModelHandle<Project>,
585 pub client: Arc<Client>,
586 pub fs: Arc<dyn Fs>,
587 pub languages: Arc<LanguageRegistry>,
588 pub user_store: ModelHandle<UserStore>,
589 pub channel_list: ModelHandle<ChannelList>,
590}
591
592impl WorkspaceParams {
593 #[cfg(any(test, feature = "test-support"))]
594 pub fn test(cx: &mut MutableAppContext) -> Self {
595 let settings = Settings::test(cx);
596 cx.set_global(settings);
597
598 let fs = project::FakeFs::new(cx.background().clone());
599 let languages = Arc::new(LanguageRegistry::test());
600 let http_client = client::test::FakeHttpClient::new(|_| async move {
601 Ok(client::http::ServerResponse::new(404))
602 });
603 let client = Client::new(http_client.clone());
604 let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx));
605 let project = Project::local(
606 client.clone(),
607 user_store.clone(),
608 languages.clone(),
609 fs.clone(),
610 cx,
611 );
612 Self {
613 project,
614 channel_list: cx
615 .add_model(|cx| ChannelList::new(user_store.clone(), client.clone(), cx)),
616 client,
617 fs,
618 languages,
619 user_store,
620 }
621 }
622
623 #[cfg(any(test, feature = "test-support"))]
624 pub fn local(app_state: &Arc<AppState>, cx: &mut MutableAppContext) -> Self {
625 Self {
626 project: Project::local(
627 app_state.client.clone(),
628 app_state.user_store.clone(),
629 app_state.languages.clone(),
630 app_state.fs.clone(),
631 cx,
632 ),
633 client: app_state.client.clone(),
634 fs: app_state.fs.clone(),
635 languages: app_state.languages.clone(),
636 user_store: app_state.user_store.clone(),
637 channel_list: app_state.channel_list.clone(),
638 }
639 }
640}
641
642pub struct Workspace {
643 weak_self: WeakViewHandle<Self>,
644 client: Arc<Client>,
645 user_store: ModelHandle<client::UserStore>,
646 remote_entity_subscription: Option<Subscription>,
647 fs: Arc<dyn Fs>,
648 modal: Option<AnyViewHandle>,
649 center: PaneGroup,
650 left_sidebar: Sidebar,
651 right_sidebar: Sidebar,
652 panes: Vec<ViewHandle<Pane>>,
653 active_pane: ViewHandle<Pane>,
654 status_bar: ViewHandle<StatusBar>,
655 project: ModelHandle<Project>,
656 leader_state: LeaderState,
657 follower_states_by_leader: FollowerStatesByLeader,
658 last_leaders_by_pane: HashMap<WeakViewHandle<Pane>, PeerId>,
659 _observe_current_user: Task<()>,
660}
661
662#[derive(Default)]
663struct LeaderState {
664 followers: HashSet<PeerId>,
665}
666
667type FollowerStatesByLeader = HashMap<PeerId, HashMap<ViewHandle<Pane>, FollowerState>>;
668
669#[derive(Default)]
670struct FollowerState {
671 active_view_id: Option<u64>,
672 items_by_leader_view_id: HashMap<u64, FollowerItem>,
673}
674
675#[derive(Debug)]
676enum FollowerItem {
677 Loading(Vec<proto::update_view::Variant>),
678 Loaded(Box<dyn FollowableItemHandle>),
679}
680
681impl Workspace {
682 pub fn new(params: &WorkspaceParams, cx: &mut ViewContext<Self>) -> Self {
683 cx.observe(¶ms.project, |_, project, cx| {
684 if project.read(cx).is_read_only() {
685 cx.blur();
686 }
687 cx.notify()
688 })
689 .detach();
690
691 cx.subscribe(¶ms.project, move |this, project, event, cx| {
692 match event {
693 project::Event::RemoteIdChanged(remote_id) => {
694 this.project_remote_id_changed(*remote_id, cx);
695 }
696 project::Event::CollaboratorLeft(peer_id) => {
697 this.collaborator_left(*peer_id, cx);
698 }
699 _ => {}
700 }
701 if project.read(cx).is_read_only() {
702 cx.blur();
703 }
704 cx.notify()
705 })
706 .detach();
707
708 let pane = cx.add_view(|_| Pane::new());
709 let pane_id = pane.id();
710 cx.observe(&pane, move |me, _, cx| {
711 let active_entry = me.active_project_path(cx);
712 me.project
713 .update(cx, |project, cx| project.set_active_path(active_entry, cx));
714 })
715 .detach();
716 cx.subscribe(&pane, move |me, _, event, cx| {
717 me.handle_pane_event(pane_id, event, cx)
718 })
719 .detach();
720 cx.focus(&pane);
721
722 let status_bar = cx.add_view(|cx| StatusBar::new(&pane, cx));
723 let mut current_user = params.user_store.read(cx).watch_current_user().clone();
724 let mut connection_status = params.client.status().clone();
725 let _observe_current_user = cx.spawn_weak(|this, mut cx| async move {
726 current_user.recv().await;
727 connection_status.recv().await;
728 let mut stream =
729 Stream::map(current_user, drop).merge(Stream::map(connection_status, drop));
730
731 while stream.recv().await.is_some() {
732 cx.update(|cx| {
733 if let Some(this) = this.upgrade(cx) {
734 this.update(cx, |_, cx| cx.notify());
735 }
736 })
737 }
738 });
739
740 let weak_self = cx.weak_handle();
741
742 cx.emit_global(WorkspaceCreated(weak_self.clone()));
743
744 let mut this = Workspace {
745 modal: None,
746 weak_self,
747 center: PaneGroup::new(pane.clone()),
748 panes: vec![pane.clone()],
749 active_pane: pane.clone(),
750 status_bar,
751 client: params.client.clone(),
752 remote_entity_subscription: None,
753 user_store: params.user_store.clone(),
754 fs: params.fs.clone(),
755 left_sidebar: Sidebar::new(Side::Left),
756 right_sidebar: Sidebar::new(Side::Right),
757 project: params.project.clone(),
758 leader_state: Default::default(),
759 follower_states_by_leader: Default::default(),
760 last_leaders_by_pane: Default::default(),
761 _observe_current_user,
762 };
763 this.project_remote_id_changed(this.project.read(cx).remote_id(), cx);
764 this
765 }
766
767 pub fn weak_handle(&self) -> WeakViewHandle<Self> {
768 self.weak_self.clone()
769 }
770
771 pub fn left_sidebar_mut(&mut self) -> &mut Sidebar {
772 &mut self.left_sidebar
773 }
774
775 pub fn right_sidebar_mut(&mut self) -> &mut Sidebar {
776 &mut self.right_sidebar
777 }
778
779 pub fn status_bar(&self) -> &ViewHandle<StatusBar> {
780 &self.status_bar
781 }
782
783 pub fn project(&self) -> &ModelHandle<Project> {
784 &self.project
785 }
786
787 pub fn worktrees<'a>(
788 &self,
789 cx: &'a AppContext,
790 ) -> impl 'a + Iterator<Item = ModelHandle<Worktree>> {
791 self.project.read(cx).worktrees(cx)
792 }
793
794 pub fn contains_paths(&self, paths: &[PathBuf], cx: &AppContext) -> bool {
795 paths.iter().all(|path| self.contains_path(&path, cx))
796 }
797
798 pub fn contains_path(&self, path: &Path, cx: &AppContext) -> bool {
799 for worktree in self.worktrees(cx) {
800 let worktree = worktree.read(cx).as_local();
801 if worktree.map_or(false, |w| w.contains_abs_path(path)) {
802 return true;
803 }
804 }
805 false
806 }
807
808 pub fn worktree_scans_complete(&self, cx: &AppContext) -> impl Future<Output = ()> + 'static {
809 let futures = self
810 .worktrees(cx)
811 .filter_map(|worktree| worktree.read(cx).as_local())
812 .map(|worktree| worktree.scan_complete())
813 .collect::<Vec<_>>();
814 async move {
815 for future in futures {
816 future.await;
817 }
818 }
819 }
820
821 pub fn open_paths(
822 &mut self,
823 abs_paths: &[PathBuf],
824 cx: &mut ViewContext<Self>,
825 ) -> Task<Vec<Option<Result<Box<dyn ItemHandle>, Arc<anyhow::Error>>>>> {
826 let entries = abs_paths
827 .iter()
828 .cloned()
829 .map(|path| self.project_path_for_path(&path, cx))
830 .collect::<Vec<_>>();
831
832 let fs = self.fs.clone();
833 let tasks = abs_paths
834 .iter()
835 .cloned()
836 .zip(entries.into_iter())
837 .map(|(abs_path, project_path)| {
838 cx.spawn(|this, mut cx| {
839 let fs = fs.clone();
840 async move {
841 let project_path = project_path.await.ok()?;
842 if fs.is_file(&abs_path).await {
843 Some(
844 this.update(&mut cx, |this, cx| this.open_path(project_path, cx))
845 .await,
846 )
847 } else {
848 None
849 }
850 }
851 })
852 })
853 .collect::<Vec<_>>();
854
855 cx.foreground().spawn(async move {
856 let mut items = Vec::new();
857 for task in tasks {
858 items.push(task.await);
859 }
860 items
861 })
862 }
863
864 fn project_path_for_path(
865 &self,
866 abs_path: &Path,
867 cx: &mut ViewContext<Self>,
868 ) -> Task<Result<ProjectPath>> {
869 let entry = self.project().update(cx, |project, cx| {
870 project.find_or_create_local_worktree(abs_path, true, cx)
871 });
872 cx.spawn(|_, cx| async move {
873 let (worktree, path) = entry.await?;
874 Ok(ProjectPath {
875 worktree_id: worktree.read_with(&cx, |t, _| t.id()),
876 path: path.into(),
877 })
878 })
879 }
880
881 // Returns the model that was toggled closed if it was open
882 pub fn toggle_modal<V, F>(
883 &mut self,
884 cx: &mut ViewContext<Self>,
885 add_view: F,
886 ) -> Option<ViewHandle<V>>
887 where
888 V: 'static + View,
889 F: FnOnce(&mut ViewContext<Self>, &mut Self) -> ViewHandle<V>,
890 {
891 cx.notify();
892 // Whatever modal was visible is getting clobbered. If its the same type as V, then return
893 // it. Otherwise, create a new modal and set it as active.
894 let already_open_modal = self.modal.take().and_then(|modal| modal.downcast::<V>());
895 if let Some(already_open_modal) = already_open_modal {
896 cx.focus_self();
897 Some(already_open_modal)
898 } else {
899 let modal = add_view(cx, self);
900 cx.focus(&modal);
901 self.modal = Some(modal.into());
902 None
903 }
904 }
905
906 pub fn modal(&self) -> Option<&AnyViewHandle> {
907 self.modal.as_ref()
908 }
909
910 pub fn dismiss_modal(&mut self, cx: &mut ViewContext<Self>) {
911 if self.modal.take().is_some() {
912 cx.focus(&self.active_pane);
913 cx.notify();
914 }
915 }
916
917 pub fn items<'a>(
918 &'a self,
919 cx: &'a AppContext,
920 ) -> impl 'a + Iterator<Item = &Box<dyn ItemHandle>> {
921 self.panes.iter().flat_map(|pane| pane.read(cx).items())
922 }
923
924 pub fn item_of_type<T: Item>(&self, cx: &AppContext) -> Option<ViewHandle<T>> {
925 self.items_of_type(cx).max_by_key(|item| item.id())
926 }
927
928 pub fn items_of_type<'a, T: Item>(
929 &'a self,
930 cx: &'a AppContext,
931 ) -> impl 'a + Iterator<Item = ViewHandle<T>> {
932 self.panes
933 .iter()
934 .flat_map(|pane| pane.read(cx).items_of_type())
935 }
936
937 pub fn active_item(&self, cx: &AppContext) -> Option<Box<dyn ItemHandle>> {
938 self.active_pane().read(cx).active_item()
939 }
940
941 fn active_project_path(&self, cx: &ViewContext<Self>) -> Option<ProjectPath> {
942 self.active_item(cx).and_then(|item| item.project_path(cx))
943 }
944
945 pub fn save_active_item(&mut self, cx: &mut ViewContext<Self>) -> Task<Result<()>> {
946 let project = self.project.clone();
947 if let Some(item) = self.active_item(cx) {
948 if item.can_save(cx) {
949 if item.has_conflict(cx.as_ref()) {
950 const CONFLICT_MESSAGE: &'static str = "This file has changed on disk since you started editing it. Do you want to overwrite it?";
951
952 let mut answer = cx.prompt(
953 PromptLevel::Warning,
954 CONFLICT_MESSAGE,
955 &["Overwrite", "Cancel"],
956 );
957 cx.spawn(|_, mut cx| async move {
958 let answer = answer.recv().await;
959 if answer == Some(0) {
960 cx.update(|cx| item.save(project, cx)).await?;
961 }
962 Ok(())
963 })
964 } else {
965 item.save(project, cx)
966 }
967 } else if item.can_save_as(cx) {
968 let worktree = self.worktrees(cx).next();
969 let start_abs_path = worktree
970 .and_then(|w| w.read(cx).as_local())
971 .map_or(Path::new(""), |w| w.abs_path())
972 .to_path_buf();
973 let mut abs_path = cx.prompt_for_new_path(&start_abs_path);
974 cx.spawn(|_, mut cx| async move {
975 if let Some(abs_path) = abs_path.recv().await.flatten() {
976 cx.update(|cx| item.save_as(project, abs_path, cx)).await?;
977 }
978 Ok(())
979 })
980 } else {
981 Task::ready(Ok(()))
982 }
983 } else {
984 Task::ready(Ok(()))
985 }
986 }
987
988 pub fn toggle_sidebar_item(&mut self, action: &ToggleSidebarItem, cx: &mut ViewContext<Self>) {
989 let sidebar = match action.0.side {
990 Side::Left => &mut self.left_sidebar,
991 Side::Right => &mut self.right_sidebar,
992 };
993 sidebar.toggle_item(action.0.item_index);
994 if let Some(active_item) = sidebar.active_item() {
995 cx.focus(active_item);
996 } else {
997 cx.focus_self();
998 }
999 cx.notify();
1000 }
1001
1002 pub fn toggle_sidebar_item_focus(
1003 &mut self,
1004 action: &ToggleSidebarItemFocus,
1005 cx: &mut ViewContext<Self>,
1006 ) {
1007 let sidebar = match action.0.side {
1008 Side::Left => &mut self.left_sidebar,
1009 Side::Right => &mut self.right_sidebar,
1010 };
1011 sidebar.activate_item(action.0.item_index);
1012 if let Some(active_item) = sidebar.active_item() {
1013 if active_item.is_focused(cx) {
1014 cx.focus_self();
1015 } else {
1016 cx.focus(active_item);
1017 }
1018 }
1019 cx.notify();
1020 }
1021
1022 pub fn debug_elements(&mut self, _: &DebugElements, cx: &mut ViewContext<Self>) {
1023 match to_string_pretty(&cx.debug_elements()) {
1024 Ok(json) => {
1025 let kib = json.len() as f32 / 1024.;
1026 cx.as_mut().write_to_clipboard(ClipboardItem::new(json));
1027 log::info!(
1028 "copied {:.1} KiB of element debug JSON to the clipboard",
1029 kib
1030 );
1031 }
1032 Err(error) => {
1033 log::error!("error debugging elements: {}", error);
1034 }
1035 };
1036 }
1037
1038 fn add_pane(&mut self, cx: &mut ViewContext<Self>) -> ViewHandle<Pane> {
1039 let pane = cx.add_view(|_| Pane::new());
1040 let pane_id = pane.id();
1041 cx.observe(&pane, move |me, _, cx| {
1042 let active_entry = me.active_project_path(cx);
1043 me.project
1044 .update(cx, |project, cx| project.set_active_path(active_entry, cx));
1045 })
1046 .detach();
1047 cx.subscribe(&pane, move |me, _, event, cx| {
1048 me.handle_pane_event(pane_id, event, cx)
1049 })
1050 .detach();
1051 self.panes.push(pane.clone());
1052 self.activate_pane(pane.clone(), cx);
1053 pane
1054 }
1055
1056 pub fn add_item(&mut self, item: Box<dyn ItemHandle>, cx: &mut ViewContext<Self>) {
1057 let pane = self.active_pane().clone();
1058 Pane::add_item(self, pane, item, true, cx);
1059 }
1060
1061 pub fn open_path(
1062 &mut self,
1063 path: impl Into<ProjectPath>,
1064 cx: &mut ViewContext<Self>,
1065 ) -> Task<Result<Box<dyn ItemHandle>, Arc<anyhow::Error>>> {
1066 let pane = self.active_pane().downgrade();
1067 let task = self.load_path(path.into(), cx);
1068 cx.spawn(|this, mut cx| async move {
1069 let (project_entry_id, build_item) = task.await?;
1070 let pane = pane
1071 .upgrade(&cx)
1072 .ok_or_else(|| anyhow!("pane was closed"))?;
1073 this.update(&mut cx, |this, cx| {
1074 Ok(Pane::open_item(
1075 this,
1076 pane,
1077 project_entry_id,
1078 cx,
1079 build_item,
1080 ))
1081 })
1082 })
1083 }
1084
1085 pub(crate) fn load_path(
1086 &mut self,
1087 path: ProjectPath,
1088 cx: &mut ViewContext<Self>,
1089 ) -> Task<
1090 Result<(
1091 ProjectEntryId,
1092 impl 'static + FnOnce(&mut MutableAppContext) -> Box<dyn ItemHandle>,
1093 )>,
1094 > {
1095 let project = self.project().clone();
1096 let project_item = project.update(cx, |project, cx| project.open_path(path, cx));
1097 let window_id = cx.window_id();
1098 cx.as_mut().spawn(|mut cx| async move {
1099 let (project_entry_id, project_item) = project_item.await?;
1100 let build_item = cx.update(|cx| {
1101 cx.default_global::<ProjectItemBuilders>()
1102 .get(&project_item.model_type())
1103 .ok_or_else(|| anyhow!("no item builder for project item"))
1104 .cloned()
1105 })?;
1106 let build_item =
1107 move |cx: &mut MutableAppContext| build_item(window_id, project, project_item, cx);
1108 Ok((project_entry_id, build_item))
1109 })
1110 }
1111
1112 pub fn open_project_item<T>(
1113 &mut self,
1114 project_item: ModelHandle<T::Item>,
1115 cx: &mut ViewContext<Self>,
1116 ) -> ViewHandle<T>
1117 where
1118 T: ProjectItem,
1119 {
1120 use project::Item as _;
1121
1122 let entry_id = project_item.read(cx).entry_id(cx);
1123 if let Some(item) = entry_id
1124 .and_then(|entry_id| self.active_pane().read(cx).item_for_entry(entry_id, cx))
1125 .and_then(|item| item.downcast())
1126 {
1127 self.activate_item(&item, cx);
1128 return item;
1129 }
1130
1131 let item = cx.add_view(|cx| T::for_project_item(self.project().clone(), project_item, cx));
1132 self.add_item(Box::new(item.clone()), cx);
1133 item
1134 }
1135
1136 pub fn activate_item(&mut self, item: &dyn ItemHandle, cx: &mut ViewContext<Self>) -> bool {
1137 let result = self.panes.iter().find_map(|pane| {
1138 if let Some(ix) = pane.read(cx).index_for_item(item) {
1139 Some((pane.clone(), ix))
1140 } else {
1141 None
1142 }
1143 });
1144 if let Some((pane, ix)) = result {
1145 self.activate_pane(pane.clone(), cx);
1146 pane.update(cx, |pane, cx| pane.activate_item(ix, true, cx));
1147 true
1148 } else {
1149 false
1150 }
1151 }
1152
1153 pub fn activate_next_pane(&mut self, cx: &mut ViewContext<Self>) {
1154 let ix = self
1155 .panes
1156 .iter()
1157 .position(|pane| pane == &self.active_pane)
1158 .unwrap();
1159 let next_ix = (ix + 1) % self.panes.len();
1160 self.activate_pane(self.panes[next_ix].clone(), cx);
1161 }
1162
1163 fn activate_pane(&mut self, pane: ViewHandle<Pane>, cx: &mut ViewContext<Self>) {
1164 if self.active_pane != pane {
1165 self.active_pane = pane.clone();
1166 self.status_bar.update(cx, |status_bar, cx| {
1167 status_bar.set_active_pane(&self.active_pane, cx);
1168 });
1169 cx.focus(&self.active_pane);
1170 cx.notify();
1171 }
1172
1173 self.update_followers(
1174 proto::update_followers::Variant::UpdateActiveView(proto::UpdateActiveView {
1175 id: self.active_item(cx).map(|item| item.id() as u64),
1176 leader_id: self.leader_for_pane(&pane).map(|id| id.0),
1177 }),
1178 cx,
1179 );
1180 }
1181
1182 fn handle_pane_event(
1183 &mut self,
1184 pane_id: usize,
1185 event: &pane::Event,
1186 cx: &mut ViewContext<Self>,
1187 ) {
1188 if let Some(pane) = self.pane(pane_id) {
1189 match event {
1190 pane::Event::Split(direction) => {
1191 self.split_pane(pane, *direction, cx);
1192 }
1193 pane::Event::Remove => {
1194 self.remove_pane(pane, cx);
1195 }
1196 pane::Event::Activate => {
1197 self.activate_pane(pane, cx);
1198 }
1199 pane::Event::ActivateItem { local } => {
1200 if *local {
1201 self.unfollow(&pane, cx);
1202 }
1203 }
1204 }
1205 } else {
1206 error!("pane {} not found", pane_id);
1207 }
1208 }
1209
1210 pub fn split_pane(
1211 &mut self,
1212 pane: ViewHandle<Pane>,
1213 direction: SplitDirection,
1214 cx: &mut ViewContext<Self>,
1215 ) -> ViewHandle<Pane> {
1216 let new_pane = self.add_pane(cx);
1217 self.activate_pane(new_pane.clone(), cx);
1218 if let Some(item) = pane.read(cx).active_item() {
1219 if let Some(clone) = item.clone_on_split(cx.as_mut()) {
1220 Pane::add_item(self, new_pane.clone(), clone, true, cx);
1221 }
1222 }
1223 self.center.split(&pane, &new_pane, direction).unwrap();
1224 cx.notify();
1225 new_pane
1226 }
1227
1228 fn remove_pane(&mut self, pane: ViewHandle<Pane>, cx: &mut ViewContext<Self>) {
1229 if self.center.remove(&pane).unwrap() {
1230 self.panes.retain(|p| p != &pane);
1231 self.activate_pane(self.panes.last().unwrap().clone(), cx);
1232 self.unfollow(&pane, cx);
1233 self.last_leaders_by_pane.remove(&pane.downgrade());
1234 cx.notify();
1235 }
1236 }
1237
1238 pub fn panes(&self) -> &[ViewHandle<Pane>] {
1239 &self.panes
1240 }
1241
1242 fn pane(&self, pane_id: usize) -> Option<ViewHandle<Pane>> {
1243 self.panes.iter().find(|pane| pane.id() == pane_id).cloned()
1244 }
1245
1246 pub fn active_pane(&self) -> &ViewHandle<Pane> {
1247 &self.active_pane
1248 }
1249
1250 fn toggle_share(&mut self, _: &ToggleShare, cx: &mut ViewContext<Self>) {
1251 self.project.update(cx, |project, cx| {
1252 if project.is_local() {
1253 if project.is_shared() {
1254 project.unshare(cx).detach();
1255 } else {
1256 project.share(cx).detach();
1257 }
1258 }
1259 });
1260 }
1261
1262 fn project_remote_id_changed(&mut self, remote_id: Option<u64>, cx: &mut ViewContext<Self>) {
1263 if let Some(remote_id) = remote_id {
1264 self.remote_entity_subscription =
1265 Some(self.client.add_view_for_remote_entity(remote_id, cx));
1266 } else {
1267 self.remote_entity_subscription.take();
1268 }
1269 }
1270
1271 fn collaborator_left(&mut self, peer_id: PeerId, cx: &mut ViewContext<Self>) {
1272 self.leader_state.followers.remove(&peer_id);
1273 if let Some(states_by_pane) = self.follower_states_by_leader.remove(&peer_id) {
1274 for state in states_by_pane.into_values() {
1275 for item in state.items_by_leader_view_id.into_values() {
1276 if let FollowerItem::Loaded(item) = item {
1277 item.set_leader_replica_id(None, cx);
1278 }
1279 }
1280 }
1281 }
1282 cx.notify();
1283 }
1284
1285 pub fn toggle_follow(
1286 &mut self,
1287 ToggleFollow(leader_id): &ToggleFollow,
1288 cx: &mut ViewContext<Self>,
1289 ) -> Option<Task<Result<()>>> {
1290 let leader_id = *leader_id;
1291 let pane = self.active_pane().clone();
1292
1293 if let Some(prev_leader_id) = self.unfollow(&pane, cx) {
1294 if leader_id == prev_leader_id {
1295 return None;
1296 }
1297 }
1298
1299 self.last_leaders_by_pane
1300 .insert(pane.downgrade(), leader_id);
1301 self.follower_states_by_leader
1302 .entry(leader_id)
1303 .or_default()
1304 .insert(pane.clone(), Default::default());
1305 cx.notify();
1306
1307 let project_id = self.project.read(cx).remote_id()?;
1308 let request = self.client.request(proto::Follow {
1309 project_id,
1310 leader_id: leader_id.0,
1311 });
1312 Some(cx.spawn_weak(|this, mut cx| async move {
1313 let response = request.await?;
1314 if let Some(this) = this.upgrade(&cx) {
1315 this.update(&mut cx, |this, _| {
1316 let state = this
1317 .follower_states_by_leader
1318 .get_mut(&leader_id)
1319 .and_then(|states_by_pane| states_by_pane.get_mut(&pane))
1320 .ok_or_else(|| anyhow!("following interrupted"))?;
1321 state.active_view_id = response.active_view_id;
1322 Ok::<_, anyhow::Error>(())
1323 })?;
1324 Self::add_views_from_leader(this, leader_id, vec![pane], response.views, &mut cx)
1325 .await?;
1326 }
1327 Ok(())
1328 }))
1329 }
1330
1331 pub fn follow_next_collaborator(
1332 &mut self,
1333 _: &FollowNextCollaborator,
1334 cx: &mut ViewContext<Self>,
1335 ) -> Option<Task<Result<()>>> {
1336 let collaborators = self.project.read(cx).collaborators();
1337 let next_leader_id = if let Some(leader_id) = self.leader_for_pane(&self.active_pane) {
1338 let mut collaborators = collaborators.keys().copied();
1339 while let Some(peer_id) = collaborators.next() {
1340 if peer_id == leader_id {
1341 break;
1342 }
1343 }
1344 collaborators.next()
1345 } else if let Some(last_leader_id) =
1346 self.last_leaders_by_pane.get(&self.active_pane.downgrade())
1347 {
1348 if collaborators.contains_key(last_leader_id) {
1349 Some(*last_leader_id)
1350 } else {
1351 None
1352 }
1353 } else {
1354 None
1355 };
1356
1357 next_leader_id
1358 .or_else(|| collaborators.keys().copied().next())
1359 .and_then(|leader_id| self.toggle_follow(&ToggleFollow(leader_id), cx))
1360 }
1361
1362 pub fn unfollow(
1363 &mut self,
1364 pane: &ViewHandle<Pane>,
1365 cx: &mut ViewContext<Self>,
1366 ) -> Option<PeerId> {
1367 for (leader_id, states_by_pane) in &mut self.follower_states_by_leader {
1368 let leader_id = *leader_id;
1369 if let Some(state) = states_by_pane.remove(&pane) {
1370 for (_, item) in state.items_by_leader_view_id {
1371 if let FollowerItem::Loaded(item) = item {
1372 item.set_leader_replica_id(None, cx);
1373 }
1374 }
1375
1376 if states_by_pane.is_empty() {
1377 self.follower_states_by_leader.remove(&leader_id);
1378 if let Some(project_id) = self.project.read(cx).remote_id() {
1379 self.client
1380 .send(proto::Unfollow {
1381 project_id,
1382 leader_id: leader_id.0,
1383 })
1384 .log_err();
1385 }
1386 }
1387
1388 cx.notify();
1389 return Some(leader_id);
1390 }
1391 }
1392 None
1393 }
1394
1395 fn render_connection_status(&self, cx: &mut RenderContext<Self>) -> Option<ElementBox> {
1396 let theme = &cx.global::<Settings>().theme;
1397 match &*self.client.status().borrow() {
1398 client::Status::ConnectionError
1399 | client::Status::ConnectionLost
1400 | client::Status::Reauthenticating
1401 | client::Status::Reconnecting { .. }
1402 | client::Status::ReconnectionError { .. } => Some(
1403 Container::new(
1404 Align::new(
1405 ConstrainedBox::new(
1406 Svg::new("icons/offline-14.svg")
1407 .with_color(theme.workspace.titlebar.offline_icon.color)
1408 .boxed(),
1409 )
1410 .with_width(theme.workspace.titlebar.offline_icon.width)
1411 .boxed(),
1412 )
1413 .boxed(),
1414 )
1415 .with_style(theme.workspace.titlebar.offline_icon.container)
1416 .boxed(),
1417 ),
1418 client::Status::UpgradeRequired => Some(
1419 Label::new(
1420 "Please update Zed to collaborate".to_string(),
1421 theme.workspace.titlebar.outdated_warning.text.clone(),
1422 )
1423 .contained()
1424 .with_style(theme.workspace.titlebar.outdated_warning.container)
1425 .aligned()
1426 .boxed(),
1427 ),
1428 _ => None,
1429 }
1430 }
1431
1432 fn render_titlebar(&self, theme: &Theme, cx: &mut RenderContext<Self>) -> ElementBox {
1433 ConstrainedBox::new(
1434 Container::new(
1435 Stack::new()
1436 .with_child(
1437 Align::new(
1438 Label::new("zed".into(), theme.workspace.titlebar.title.clone())
1439 .boxed(),
1440 )
1441 .boxed(),
1442 )
1443 .with_child(
1444 Align::new(
1445 Flex::row()
1446 .with_children(self.render_share_icon(theme, cx))
1447 .with_children(self.render_collaborators(theme, cx))
1448 .with_child(self.render_current_user(
1449 self.user_store.read(cx).current_user().as_ref(),
1450 self.project.read(cx).replica_id(),
1451 theme,
1452 cx,
1453 ))
1454 .with_children(self.render_connection_status(cx))
1455 .boxed(),
1456 )
1457 .right()
1458 .boxed(),
1459 )
1460 .boxed(),
1461 )
1462 .with_style(theme.workspace.titlebar.container)
1463 .boxed(),
1464 )
1465 .with_height(theme.workspace.titlebar.height)
1466 .named("titlebar")
1467 }
1468
1469 fn render_collaborators(&self, theme: &Theme, cx: &mut RenderContext<Self>) -> Vec<ElementBox> {
1470 let mut collaborators = self
1471 .project
1472 .read(cx)
1473 .collaborators()
1474 .values()
1475 .cloned()
1476 .collect::<Vec<_>>();
1477 collaborators.sort_unstable_by_key(|collaborator| collaborator.replica_id);
1478 collaborators
1479 .into_iter()
1480 .filter_map(|collaborator| {
1481 Some(self.render_avatar(
1482 collaborator.user.avatar.clone()?,
1483 collaborator.replica_id,
1484 Some(collaborator.peer_id),
1485 theme,
1486 cx,
1487 ))
1488 })
1489 .collect()
1490 }
1491
1492 fn render_current_user(
1493 &self,
1494 user: Option<&Arc<User>>,
1495 replica_id: ReplicaId,
1496 theme: &Theme,
1497 cx: &mut RenderContext<Self>,
1498 ) -> ElementBox {
1499 if let Some(avatar) = user.and_then(|user| user.avatar.clone()) {
1500 self.render_avatar(avatar, replica_id, None, theme, cx)
1501 } else {
1502 MouseEventHandler::new::<Authenticate, _, _>(0, cx, |state, _| {
1503 let style = if state.hovered {
1504 &theme.workspace.titlebar.hovered_sign_in_prompt
1505 } else {
1506 &theme.workspace.titlebar.sign_in_prompt
1507 };
1508 Label::new("Sign in".to_string(), style.text.clone())
1509 .contained()
1510 .with_style(style.container)
1511 .boxed()
1512 })
1513 .on_click(|cx| cx.dispatch_action(Authenticate))
1514 .with_cursor_style(CursorStyle::PointingHand)
1515 .aligned()
1516 .boxed()
1517 }
1518 }
1519
1520 fn render_avatar(
1521 &self,
1522 avatar: Arc<ImageData>,
1523 replica_id: ReplicaId,
1524 peer_id: Option<PeerId>,
1525 theme: &Theme,
1526 cx: &mut RenderContext<Self>,
1527 ) -> ElementBox {
1528 let replica_color = theme.editor.replica_selection_style(replica_id).cursor;
1529 let is_followed = peer_id.map_or(false, |peer_id| {
1530 self.follower_states_by_leader.contains_key(&peer_id)
1531 });
1532 let mut avatar_style = theme.workspace.titlebar.avatar;
1533 if is_followed {
1534 avatar_style.border = Border::all(1.0, replica_color);
1535 }
1536 let content = Stack::new()
1537 .with_child(
1538 Image::new(avatar)
1539 .with_style(avatar_style)
1540 .constrained()
1541 .with_width(theme.workspace.titlebar.avatar_width)
1542 .aligned()
1543 .boxed(),
1544 )
1545 .with_child(
1546 AvatarRibbon::new(replica_color)
1547 .constrained()
1548 .with_width(theme.workspace.titlebar.avatar_ribbon.width)
1549 .with_height(theme.workspace.titlebar.avatar_ribbon.height)
1550 .aligned()
1551 .bottom()
1552 .boxed(),
1553 )
1554 .constrained()
1555 .with_width(theme.workspace.right_sidebar.width)
1556 .boxed();
1557
1558 if let Some(peer_id) = peer_id {
1559 MouseEventHandler::new::<ToggleFollow, _, _>(replica_id.into(), cx, move |_, _| content)
1560 .with_cursor_style(CursorStyle::PointingHand)
1561 .on_click(move |cx| cx.dispatch_action(ToggleFollow(peer_id)))
1562 .boxed()
1563 } else {
1564 content
1565 }
1566 }
1567
1568 fn render_share_icon(&self, theme: &Theme, cx: &mut RenderContext<Self>) -> Option<ElementBox> {
1569 if self.project().read(cx).is_local() && self.client.user_id().is_some() {
1570 let color = if self.project().read(cx).is_shared() {
1571 theme.workspace.titlebar.share_icon_active_color
1572 } else {
1573 theme.workspace.titlebar.share_icon_color
1574 };
1575 Some(
1576 MouseEventHandler::new::<ToggleShare, _, _>(0, cx, |_, _| {
1577 Align::new(
1578 Svg::new("icons/broadcast-24.svg")
1579 .with_color(color)
1580 .constrained()
1581 .with_width(24.)
1582 .boxed(),
1583 )
1584 .boxed()
1585 })
1586 .with_cursor_style(CursorStyle::PointingHand)
1587 .on_click(|cx| cx.dispatch_action(ToggleShare))
1588 .boxed(),
1589 )
1590 } else {
1591 None
1592 }
1593 }
1594
1595 fn render_disconnected_overlay(&self, cx: &AppContext) -> Option<ElementBox> {
1596 if self.project.read(cx).is_read_only() {
1597 let theme = &cx.global::<Settings>().theme;
1598 Some(
1599 EventHandler::new(
1600 Label::new(
1601 "Your connection to the remote project has been lost.".to_string(),
1602 theme.workspace.disconnected_overlay.text.clone(),
1603 )
1604 .aligned()
1605 .contained()
1606 .with_style(theme.workspace.disconnected_overlay.container)
1607 .boxed(),
1608 )
1609 .capture(|_, _, _| true)
1610 .boxed(),
1611 )
1612 } else {
1613 None
1614 }
1615 }
1616
1617 // RPC handlers
1618
1619 async fn handle_follow(
1620 this: ViewHandle<Self>,
1621 envelope: TypedEnvelope<proto::Follow>,
1622 _: Arc<Client>,
1623 mut cx: AsyncAppContext,
1624 ) -> Result<proto::FollowResponse> {
1625 this.update(&mut cx, |this, cx| {
1626 this.leader_state
1627 .followers
1628 .insert(envelope.original_sender_id()?);
1629
1630 let active_view_id = this
1631 .active_item(cx)
1632 .and_then(|i| i.to_followable_item_handle(cx))
1633 .map(|i| i.id() as u64);
1634 Ok(proto::FollowResponse {
1635 active_view_id,
1636 views: this
1637 .panes()
1638 .iter()
1639 .flat_map(|pane| {
1640 let leader_id = this.leader_for_pane(pane).map(|id| id.0);
1641 pane.read(cx).items().filter_map({
1642 let cx = &cx;
1643 move |item| {
1644 let id = item.id() as u64;
1645 let item = item.to_followable_item_handle(cx)?;
1646 let variant = item.to_state_proto(cx)?;
1647 Some(proto::View {
1648 id,
1649 leader_id,
1650 variant: Some(variant),
1651 })
1652 }
1653 })
1654 })
1655 .collect(),
1656 })
1657 })
1658 }
1659
1660 async fn handle_unfollow(
1661 this: ViewHandle<Self>,
1662 envelope: TypedEnvelope<proto::Unfollow>,
1663 _: Arc<Client>,
1664 mut cx: AsyncAppContext,
1665 ) -> Result<()> {
1666 this.update(&mut cx, |this, _| {
1667 this.leader_state
1668 .followers
1669 .remove(&envelope.original_sender_id()?);
1670 Ok(())
1671 })
1672 }
1673
1674 async fn handle_update_followers(
1675 this: ViewHandle<Self>,
1676 envelope: TypedEnvelope<proto::UpdateFollowers>,
1677 _: Arc<Client>,
1678 mut cx: AsyncAppContext,
1679 ) -> Result<()> {
1680 let leader_id = envelope.original_sender_id()?;
1681 match envelope
1682 .payload
1683 .variant
1684 .ok_or_else(|| anyhow!("invalid update"))?
1685 {
1686 proto::update_followers::Variant::UpdateActiveView(update_active_view) => {
1687 this.update(&mut cx, |this, cx| {
1688 this.update_leader_state(leader_id, cx, |state, _| {
1689 state.active_view_id = update_active_view.id;
1690 });
1691 Ok::<_, anyhow::Error>(())
1692 })
1693 }
1694 proto::update_followers::Variant::UpdateView(update_view) => {
1695 this.update(&mut cx, |this, cx| {
1696 let variant = update_view
1697 .variant
1698 .ok_or_else(|| anyhow!("missing update view variant"))?;
1699 this.update_leader_state(leader_id, cx, |state, cx| {
1700 let variant = variant.clone();
1701 match state
1702 .items_by_leader_view_id
1703 .entry(update_view.id)
1704 .or_insert(FollowerItem::Loading(Vec::new()))
1705 {
1706 FollowerItem::Loaded(item) => {
1707 item.apply_update_proto(variant, cx).log_err();
1708 }
1709 FollowerItem::Loading(updates) => updates.push(variant),
1710 }
1711 });
1712 Ok(())
1713 })
1714 }
1715 proto::update_followers::Variant::CreateView(view) => {
1716 let panes = this.read_with(&cx, |this, _| {
1717 this.follower_states_by_leader
1718 .get(&leader_id)
1719 .into_iter()
1720 .flat_map(|states_by_pane| states_by_pane.keys())
1721 .cloned()
1722 .collect()
1723 });
1724 Self::add_views_from_leader(this.clone(), leader_id, panes, vec![view], &mut cx)
1725 .await?;
1726 Ok(())
1727 }
1728 }
1729 .log_err();
1730
1731 Ok(())
1732 }
1733
1734 async fn add_views_from_leader(
1735 this: ViewHandle<Self>,
1736 leader_id: PeerId,
1737 panes: Vec<ViewHandle<Pane>>,
1738 views: Vec<proto::View>,
1739 cx: &mut AsyncAppContext,
1740 ) -> Result<()> {
1741 let project = this.read_with(cx, |this, _| this.project.clone());
1742 let replica_id = project
1743 .read_with(cx, |project, _| {
1744 project
1745 .collaborators()
1746 .get(&leader_id)
1747 .map(|c| c.replica_id)
1748 })
1749 .ok_or_else(|| anyhow!("no such collaborator {}", leader_id))?;
1750
1751 let item_builders = cx.update(|cx| {
1752 cx.default_global::<FollowableItemBuilders>()
1753 .values()
1754 .map(|b| b.0)
1755 .collect::<Vec<_>>()
1756 .clone()
1757 });
1758
1759 let mut item_tasks_by_pane = HashMap::default();
1760 for pane in panes {
1761 let mut item_tasks = Vec::new();
1762 let mut leader_view_ids = Vec::new();
1763 for view in &views {
1764 let mut variant = view.variant.clone();
1765 if variant.is_none() {
1766 Err(anyhow!("missing variant"))?;
1767 }
1768 for build_item in &item_builders {
1769 let task =
1770 cx.update(|cx| build_item(pane.clone(), project.clone(), &mut variant, cx));
1771 if let Some(task) = task {
1772 item_tasks.push(task);
1773 leader_view_ids.push(view.id);
1774 break;
1775 } else {
1776 assert!(variant.is_some());
1777 }
1778 }
1779 }
1780
1781 item_tasks_by_pane.insert(pane, (item_tasks, leader_view_ids));
1782 }
1783
1784 for (pane, (item_tasks, leader_view_ids)) in item_tasks_by_pane {
1785 let items = futures::future::try_join_all(item_tasks).await?;
1786 this.update(cx, |this, cx| {
1787 let state = this
1788 .follower_states_by_leader
1789 .get_mut(&leader_id)?
1790 .get_mut(&pane)?;
1791
1792 for (id, item) in leader_view_ids.into_iter().zip(items) {
1793 item.set_leader_replica_id(Some(replica_id), cx);
1794 match state.items_by_leader_view_id.entry(id) {
1795 hash_map::Entry::Occupied(e) => {
1796 let e = e.into_mut();
1797 if let FollowerItem::Loading(updates) = e {
1798 for update in updates.drain(..) {
1799 item.apply_update_proto(update, cx)
1800 .context("failed to apply view update")
1801 .log_err();
1802 }
1803 }
1804 *e = FollowerItem::Loaded(item);
1805 }
1806 hash_map::Entry::Vacant(e) => {
1807 e.insert(FollowerItem::Loaded(item));
1808 }
1809 }
1810 }
1811
1812 Some(())
1813 });
1814 }
1815 this.update(cx, |this, cx| this.leader_updated(leader_id, cx));
1816
1817 Ok(())
1818 }
1819
1820 fn update_followers(
1821 &self,
1822 update: proto::update_followers::Variant,
1823 cx: &AppContext,
1824 ) -> Option<()> {
1825 let project_id = self.project.read(cx).remote_id()?;
1826 if !self.leader_state.followers.is_empty() {
1827 self.client
1828 .send(proto::UpdateFollowers {
1829 project_id,
1830 follower_ids: self.leader_state.followers.iter().map(|f| f.0).collect(),
1831 variant: Some(update),
1832 })
1833 .log_err();
1834 }
1835 None
1836 }
1837
1838 pub fn leader_for_pane(&self, pane: &ViewHandle<Pane>) -> Option<PeerId> {
1839 self.follower_states_by_leader
1840 .iter()
1841 .find_map(|(leader_id, state)| {
1842 if state.contains_key(pane) {
1843 Some(*leader_id)
1844 } else {
1845 None
1846 }
1847 })
1848 }
1849
1850 fn update_leader_state(
1851 &mut self,
1852 leader_id: PeerId,
1853 cx: &mut ViewContext<Self>,
1854 mut update_fn: impl FnMut(&mut FollowerState, &mut ViewContext<Self>),
1855 ) {
1856 for (_, state) in self
1857 .follower_states_by_leader
1858 .get_mut(&leader_id)
1859 .into_iter()
1860 .flatten()
1861 {
1862 update_fn(state, cx);
1863 }
1864 self.leader_updated(leader_id, cx);
1865 }
1866
1867 fn leader_updated(&mut self, leader_id: PeerId, cx: &mut ViewContext<Self>) -> Option<()> {
1868 let mut items_to_add = Vec::new();
1869 for (pane, state) in self.follower_states_by_leader.get(&leader_id)? {
1870 if let Some(active_item) = state
1871 .active_view_id
1872 .and_then(|id| state.items_by_leader_view_id.get(&id))
1873 {
1874 if let FollowerItem::Loaded(item) = active_item {
1875 items_to_add.push((pane.clone(), item.boxed_clone()));
1876 }
1877 }
1878 }
1879
1880 for (pane, item) in items_to_add {
1881 Pane::add_item(self, pane.clone(), item.boxed_clone(), false, cx);
1882 cx.notify();
1883 }
1884 None
1885 }
1886}
1887
1888impl Entity for Workspace {
1889 type Event = ();
1890}
1891
1892impl View for Workspace {
1893 fn ui_name() -> &'static str {
1894 "Workspace"
1895 }
1896
1897 fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
1898 let theme = cx.global::<Settings>().theme.clone();
1899 Stack::new()
1900 .with_child(
1901 Flex::column()
1902 .with_child(self.render_titlebar(&theme, cx))
1903 .with_child(
1904 Stack::new()
1905 .with_child({
1906 let mut content = Flex::row();
1907 content.add_child(self.left_sidebar.render(&theme, cx));
1908 if let Some(element) =
1909 self.left_sidebar.render_active_item(&theme, cx)
1910 {
1911 content.add_child(Flexible::new(0.8, false, element).boxed());
1912 }
1913 content.add_child(
1914 Flex::column()
1915 .with_child(
1916 Flexible::new(
1917 1.,
1918 true,
1919 self.center.render(
1920 &theme,
1921 &self.follower_states_by_leader,
1922 self.project.read(cx).collaborators(),
1923 ),
1924 )
1925 .boxed(),
1926 )
1927 .with_child(ChildView::new(&self.status_bar).boxed())
1928 .flexible(1., true)
1929 .boxed(),
1930 );
1931 if let Some(element) =
1932 self.right_sidebar.render_active_item(&theme, cx)
1933 {
1934 content.add_child(Flexible::new(0.8, false, element).boxed());
1935 }
1936 content.add_child(self.right_sidebar.render(&theme, cx));
1937 content.boxed()
1938 })
1939 .with_children(self.modal.as_ref().map(|m| ChildView::new(m).boxed()))
1940 .flexible(1.0, true)
1941 .boxed(),
1942 )
1943 .contained()
1944 .with_background_color(theme.workspace.background)
1945 .boxed(),
1946 )
1947 .with_children(self.render_disconnected_overlay(cx))
1948 .named("workspace")
1949 }
1950
1951 fn on_focus(&mut self, cx: &mut ViewContext<Self>) {
1952 cx.focus(&self.active_pane);
1953 }
1954}
1955
1956pub trait WorkspaceHandle {
1957 fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath>;
1958}
1959
1960impl WorkspaceHandle for ViewHandle<Workspace> {
1961 fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath> {
1962 self.read(cx)
1963 .worktrees(cx)
1964 .flat_map(|worktree| {
1965 let worktree_id = worktree.read(cx).id();
1966 worktree.read(cx).files(true, 0).map(move |f| ProjectPath {
1967 worktree_id,
1968 path: f.path.clone(),
1969 })
1970 })
1971 .collect::<Vec<_>>()
1972 }
1973}
1974
1975pub struct AvatarRibbon {
1976 color: Color,
1977}
1978
1979impl AvatarRibbon {
1980 pub fn new(color: Color) -> AvatarRibbon {
1981 AvatarRibbon { color }
1982 }
1983}
1984
1985impl Element for AvatarRibbon {
1986 type LayoutState = ();
1987
1988 type PaintState = ();
1989
1990 fn layout(
1991 &mut self,
1992 constraint: gpui::SizeConstraint,
1993 _: &mut gpui::LayoutContext,
1994 ) -> (gpui::geometry::vector::Vector2F, Self::LayoutState) {
1995 (constraint.max, ())
1996 }
1997
1998 fn paint(
1999 &mut self,
2000 bounds: gpui::geometry::rect::RectF,
2001 _: gpui::geometry::rect::RectF,
2002 _: &mut Self::LayoutState,
2003 cx: &mut gpui::PaintContext,
2004 ) -> Self::PaintState {
2005 let mut path = PathBuilder::new();
2006 path.reset(bounds.lower_left());
2007 path.curve_to(
2008 bounds.origin() + vec2f(bounds.height(), 0.),
2009 bounds.origin(),
2010 );
2011 path.line_to(bounds.upper_right() - vec2f(bounds.height(), 0.));
2012 path.curve_to(bounds.lower_right(), bounds.upper_right());
2013 path.line_to(bounds.lower_left());
2014 cx.scene.push_path(path.build(self.color, None));
2015 }
2016
2017 fn dispatch_event(
2018 &mut self,
2019 _: &gpui::Event,
2020 _: gpui::geometry::rect::RectF,
2021 _: &mut Self::LayoutState,
2022 _: &mut Self::PaintState,
2023 _: &mut gpui::EventContext,
2024 ) -> bool {
2025 false
2026 }
2027
2028 fn debug(
2029 &self,
2030 bounds: gpui::geometry::rect::RectF,
2031 _: &Self::LayoutState,
2032 _: &Self::PaintState,
2033 _: &gpui::DebugContext,
2034 ) -> gpui::json::Value {
2035 json::json!({
2036 "type": "AvatarRibbon",
2037 "bounds": bounds.to_json(),
2038 "color": self.color.to_json(),
2039 })
2040 }
2041}
2042
2043impl std::fmt::Debug for OpenParams {
2044 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2045 f.debug_struct("OpenParams")
2046 .field("paths", &self.paths)
2047 .finish()
2048 }
2049}
2050
2051fn open(action: &Open, cx: &mut MutableAppContext) {
2052 let app_state = action.0.clone();
2053 let mut paths = cx.prompt_for_paths(PathPromptOptions {
2054 files: true,
2055 directories: true,
2056 multiple: true,
2057 });
2058 cx.spawn(|mut cx| async move {
2059 if let Some(paths) = paths.recv().await.flatten() {
2060 cx.update(|cx| cx.dispatch_global_action(OpenPaths(OpenParams { paths, app_state })));
2061 }
2062 })
2063 .detach();
2064}
2065
2066pub struct WorkspaceCreated(WeakViewHandle<Workspace>);
2067
2068pub fn open_paths(
2069 abs_paths: &[PathBuf],
2070 app_state: &Arc<AppState>,
2071 cx: &mut MutableAppContext,
2072) -> Task<ViewHandle<Workspace>> {
2073 log::info!("open paths {:?}", abs_paths);
2074
2075 // Open paths in existing workspace if possible
2076 let mut existing = None;
2077 for window_id in cx.window_ids().collect::<Vec<_>>() {
2078 if let Some(workspace_handle) = cx.root_view::<Workspace>(window_id) {
2079 if workspace_handle.update(cx, |workspace, cx| {
2080 if workspace.contains_paths(abs_paths, cx.as_ref()) {
2081 cx.activate_window(window_id);
2082 existing = Some(workspace_handle.clone());
2083 true
2084 } else {
2085 false
2086 }
2087 }) {
2088 break;
2089 }
2090 }
2091 }
2092
2093 let workspace = existing.unwrap_or_else(|| {
2094 cx.add_window((app_state.build_window_options)(), |cx| {
2095 let project = Project::local(
2096 app_state.client.clone(),
2097 app_state.user_store.clone(),
2098 app_state.languages.clone(),
2099 app_state.fs.clone(),
2100 cx,
2101 );
2102 (app_state.build_workspace)(project, &app_state, cx)
2103 })
2104 .1
2105 });
2106
2107 let task = workspace.update(cx, |workspace, cx| workspace.open_paths(abs_paths, cx));
2108 cx.spawn(|_| async move {
2109 task.await;
2110 workspace
2111 })
2112}
2113
2114pub fn join_project(
2115 project_id: u64,
2116 app_state: &Arc<AppState>,
2117 cx: &mut MutableAppContext,
2118) -> Task<Result<ViewHandle<Workspace>>> {
2119 for window_id in cx.window_ids().collect::<Vec<_>>() {
2120 if let Some(workspace) = cx.root_view::<Workspace>(window_id) {
2121 if workspace.read(cx).project().read(cx).remote_id() == Some(project_id) {
2122 return Task::ready(Ok(workspace));
2123 }
2124 }
2125 }
2126
2127 let app_state = app_state.clone();
2128 cx.spawn(|mut cx| async move {
2129 let project = Project::remote(
2130 project_id,
2131 app_state.client.clone(),
2132 app_state.user_store.clone(),
2133 app_state.languages.clone(),
2134 app_state.fs.clone(),
2135 &mut cx,
2136 )
2137 .await?;
2138 Ok(cx.update(|cx| {
2139 cx.add_window((app_state.build_window_options)(), |cx| {
2140 (app_state.build_workspace)(project, &app_state, cx)
2141 })
2142 .1
2143 }))
2144 })
2145}
2146
2147fn open_new(app_state: &Arc<AppState>, cx: &mut MutableAppContext) {
2148 let (window_id, workspace) = cx.add_window((app_state.build_window_options)(), |cx| {
2149 let project = Project::local(
2150 app_state.client.clone(),
2151 app_state.user_store.clone(),
2152 app_state.languages.clone(),
2153 app_state.fs.clone(),
2154 cx,
2155 );
2156 (app_state.build_workspace)(project, &app_state, cx)
2157 });
2158 cx.dispatch_action(window_id, vec![workspace.id()], &OpenNew(app_state.clone()));
2159}