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