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