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