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