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 is_singleton(&self, cx: &AppContext) -> bool;
227 fn set_nav_history(&mut self, _: ItemNavHistory, _: &mut ViewContext<Self>);
228 fn clone_on_split(&self, _: &mut ViewContext<Self>) -> Option<Self>
229 where
230 Self: Sized,
231 {
232 None
233 }
234 fn is_dirty(&self, _: &AppContext) -> bool {
235 false
236 }
237 fn has_conflict(&self, _: &AppContext) -> bool {
238 false
239 }
240 fn can_save(&self, cx: &AppContext) -> bool;
241 fn save(
242 &mut self,
243 project: ModelHandle<Project>,
244 cx: &mut ViewContext<Self>,
245 ) -> Task<Result<()>>;
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 is_singleton(&self, cx: &AppContext) -> bool;
377 fn boxed_clone(&self) -> Box<dyn ItemHandle>;
378 fn set_nav_history(&self, nav_history: Rc<RefCell<NavHistory>>, cx: &mut MutableAppContext);
379 fn clone_on_split(&self, cx: &mut MutableAppContext) -> Option<Box<dyn ItemHandle>>;
380 fn added_to_pane(
381 &self,
382 workspace: &mut Workspace,
383 pane: ViewHandle<Pane>,
384 cx: &mut ViewContext<Workspace>,
385 );
386 fn deactivated(&self, cx: &mut MutableAppContext);
387 fn navigate(&self, data: Box<dyn Any>, cx: &mut MutableAppContext) -> bool;
388 fn id(&self) -> usize;
389 fn to_any(&self) -> AnyViewHandle;
390 fn is_dirty(&self, cx: &AppContext) -> bool;
391 fn has_conflict(&self, cx: &AppContext) -> bool;
392 fn can_save(&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 is_singleton(&self, cx: &AppContext) -> bool {
441 self.read(cx).is_singleton(cx)
442 }
443
444 fn boxed_clone(&self) -> Box<dyn ItemHandle> {
445 Box::new(self.clone())
446 }
447
448 fn set_nav_history(&self, nav_history: Rc<RefCell<NavHistory>>, cx: &mut MutableAppContext) {
449 self.update(cx, |item, cx| {
450 item.set_nav_history(ItemNavHistory::new(nav_history, &cx.handle()), cx);
451 })
452 }
453
454 fn clone_on_split(&self, cx: &mut MutableAppContext) -> Option<Box<dyn ItemHandle>> {
455 self.update(cx, |item, cx| {
456 cx.add_option_view(|cx| item.clone_on_split(cx))
457 })
458 .map(|handle| Box::new(handle) as Box<dyn ItemHandle>)
459 }
460
461 fn added_to_pane(
462 &self,
463 workspace: &mut Workspace,
464 pane: ViewHandle<Pane>,
465 cx: &mut ViewContext<Workspace>,
466 ) {
467 if let Some(followed_item) = self.to_followable_item_handle(cx) {
468 if let Some(message) = followed_item.to_state_proto(cx) {
469 workspace.update_followers(
470 proto::update_followers::Variant::CreateView(proto::View {
471 id: followed_item.id() as u64,
472 variant: Some(message),
473 leader_id: workspace.leader_for_pane(&pane).map(|id| id.0),
474 }),
475 cx,
476 );
477 }
478 }
479
480 let pending_update = Rc::new(RefCell::new(None));
481 let pending_update_scheduled = Rc::new(AtomicBool::new(false));
482 let pane = pane.downgrade();
483 cx.subscribe(self, move |workspace, item, event, cx| {
484 let pane = if let Some(pane) = pane.upgrade(cx) {
485 pane
486 } else {
487 log::error!("unexpected item event after pane was dropped");
488 return;
489 };
490
491 if let Some(item) = item.to_followable_item_handle(cx) {
492 let leader_id = workspace.leader_for_pane(&pane);
493
494 if leader_id.is_some() && item.should_unfollow_on_event(event, cx) {
495 workspace.unfollow(&pane, cx);
496 }
497
498 if item.add_event_to_update_proto(event, &mut *pending_update.borrow_mut(), cx)
499 && !pending_update_scheduled.load(SeqCst)
500 {
501 pending_update_scheduled.store(true, SeqCst);
502 cx.after_window_update({
503 let pending_update = pending_update.clone();
504 let pending_update_scheduled = pending_update_scheduled.clone();
505 move |this, cx| {
506 pending_update_scheduled.store(false, SeqCst);
507 this.update_followers(
508 proto::update_followers::Variant::UpdateView(proto::UpdateView {
509 id: item.id() as u64,
510 variant: pending_update.borrow_mut().take(),
511 leader_id: leader_id.map(|id| id.0),
512 }),
513 cx,
514 );
515 }
516 });
517 }
518 }
519
520 if T::should_close_item_on_event(event) {
521 Pane::close_item(workspace, pane, item.id(), cx).detach_and_log_err(cx);
522 return;
523 }
524
525 if T::should_activate_item_on_event(event) {
526 pane.update(cx, |pane, cx| {
527 if let Some(ix) = pane.index_for_item(&item) {
528 pane.activate_item(ix, true, true, cx);
529 pane.activate(cx);
530 }
531 });
532 }
533
534 if T::should_update_tab_on_event(event) {
535 pane.update(cx, |_, cx| cx.notify());
536 }
537 })
538 .detach();
539 }
540
541 fn deactivated(&self, cx: &mut MutableAppContext) {
542 self.update(cx, |this, cx| this.deactivated(cx));
543 }
544
545 fn navigate(&self, data: Box<dyn Any>, cx: &mut MutableAppContext) -> bool {
546 self.update(cx, |this, cx| this.navigate(data, cx))
547 }
548
549 fn id(&self) -> usize {
550 self.id()
551 }
552
553 fn to_any(&self) -> AnyViewHandle {
554 self.into()
555 }
556
557 fn is_dirty(&self, cx: &AppContext) -> bool {
558 self.read(cx).is_dirty(cx)
559 }
560
561 fn has_conflict(&self, cx: &AppContext) -> bool {
562 self.read(cx).has_conflict(cx)
563 }
564
565 fn can_save(&self, cx: &AppContext) -> bool {
566 self.read(cx).can_save(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 prepare = self.prepare_to_close(cx);
891 Some(cx.spawn(|this, mut cx| async move {
892 if prepare.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 prepare_to_close(&mut self, cx: &mut ViewContext<Self>) -> Task<Result<bool>> {
903 self.save_all_internal(true, cx)
904 }
905
906 fn save_all(&mut self, _: &SaveAll, cx: &mut ViewContext<Self>) -> Option<Task<Result<()>>> {
907 let save_all = self.save_all_internal(false, cx);
908 Some(cx.foreground().spawn(async move {
909 save_all.await?;
910 Ok(())
911 }))
912 }
913
914 fn save_all_internal(
915 &mut self,
916 should_prompt_to_save: bool,
917 cx: &mut ViewContext<Self>,
918 ) -> Task<Result<bool>> {
919 let dirty_items = self
920 .panes
921 .iter()
922 .flat_map(|pane| {
923 pane.read(cx).items().filter_map(|item| {
924 if item.is_dirty(cx) {
925 Some((pane.clone(), item.boxed_clone()))
926 } else {
927 None
928 }
929 })
930 })
931 .collect::<Vec<_>>();
932
933 let project = self.project.clone();
934 cx.spawn_weak(|_, mut cx| async move {
935 // let mut saved_project_entry_ids = HashSet::default();
936 for (pane, item) in dirty_items {
937 let (is_singl, project_entry_ids) =
938 cx.read(|cx| (item.is_singleton(cx), item.project_entry_ids(cx)));
939 if is_singl || !project_entry_ids.is_empty() {
940 if let Some(ix) =
941 pane.read_with(&cx, |pane, _| pane.index_for_item(item.as_ref()))
942 {
943 if !Pane::save_item(
944 project.clone(),
945 &pane,
946 ix,
947 &item,
948 should_prompt_to_save,
949 &mut cx,
950 )
951 .await?
952 {
953 return Ok(false);
954 }
955 }
956 }
957 }
958 Ok(true)
959 })
960 }
961
962 pub fn open_paths(
963 &mut self,
964 mut abs_paths: Vec<PathBuf>,
965 cx: &mut ViewContext<Self>,
966 ) -> Task<Vec<Option<Result<Box<dyn ItemHandle>, Arc<anyhow::Error>>>>> {
967 let fs = self.fs.clone();
968
969 // Sort the paths to ensure we add worktrees for parents before their children.
970 abs_paths.sort_unstable();
971 cx.spawn(|this, mut cx| async move {
972 let mut entries = Vec::new();
973 for path in &abs_paths {
974 entries.push(
975 this.update(&mut cx, |this, cx| this.project_path_for_path(path, cx))
976 .await
977 .ok(),
978 );
979 }
980
981 let tasks = abs_paths
982 .iter()
983 .cloned()
984 .zip(entries.into_iter())
985 .map(|(abs_path, project_path)| {
986 let this = this.clone();
987 cx.spawn(|mut cx| {
988 let fs = fs.clone();
989 async move {
990 let project_path = project_path?;
991 if fs.is_file(&abs_path).await {
992 Some(
993 this.update(&mut cx, |this, cx| {
994 this.open_path(project_path, true, cx)
995 })
996 .await,
997 )
998 } else {
999 None
1000 }
1001 }
1002 })
1003 })
1004 .collect::<Vec<_>>();
1005
1006 futures::future::join_all(tasks).await
1007 })
1008 }
1009
1010 fn add_folder_to_project(&mut self, _: &AddFolderToProject, cx: &mut ViewContext<Self>) {
1011 let mut paths = cx.prompt_for_paths(PathPromptOptions {
1012 files: false,
1013 directories: true,
1014 multiple: true,
1015 });
1016 cx.spawn(|this, mut cx| async move {
1017 if let Some(paths) = paths.recv().await.flatten() {
1018 let results = this
1019 .update(&mut cx, |this, cx| this.open_paths(paths, cx))
1020 .await;
1021 for result in results {
1022 if let Some(result) = result {
1023 result.log_err();
1024 }
1025 }
1026 }
1027 })
1028 .detach();
1029 }
1030
1031 fn project_path_for_path(
1032 &self,
1033 abs_path: &Path,
1034 cx: &mut ViewContext<Self>,
1035 ) -> Task<Result<ProjectPath>> {
1036 let entry = self.project().update(cx, |project, cx| {
1037 project.find_or_create_local_worktree(abs_path, true, cx)
1038 });
1039 cx.spawn(|_, cx| async move {
1040 let (worktree, path) = entry.await?;
1041 Ok(ProjectPath {
1042 worktree_id: worktree.read_with(&cx, |t, _| t.id()),
1043 path: path.into(),
1044 })
1045 })
1046 }
1047
1048 /// Returns the modal that was toggled closed if it was open.
1049 pub fn toggle_modal<V, F>(
1050 &mut self,
1051 cx: &mut ViewContext<Self>,
1052 add_view: F,
1053 ) -> Option<ViewHandle<V>>
1054 where
1055 V: 'static + View,
1056 F: FnOnce(&mut Self, &mut ViewContext<Self>) -> ViewHandle<V>,
1057 {
1058 cx.notify();
1059 // Whatever modal was visible is getting clobbered. If its the same type as V, then return
1060 // it. Otherwise, create a new modal and set it as active.
1061 let already_open_modal = self.modal.take().and_then(|modal| modal.downcast::<V>());
1062 if let Some(already_open_modal) = already_open_modal {
1063 cx.focus_self();
1064 Some(already_open_modal)
1065 } else {
1066 let modal = add_view(self, cx);
1067 cx.focus(&modal);
1068 self.modal = Some(modal.into());
1069 None
1070 }
1071 }
1072
1073 pub fn modal(&self) -> Option<&AnyViewHandle> {
1074 self.modal.as_ref()
1075 }
1076
1077 pub fn dismiss_modal(&mut self, cx: &mut ViewContext<Self>) {
1078 if self.modal.take().is_some() {
1079 cx.focus(&self.active_pane);
1080 cx.notify();
1081 }
1082 }
1083
1084 pub fn show_notification<V: Notification>(
1085 &mut self,
1086 id: usize,
1087 cx: &mut ViewContext<Self>,
1088 build_notification: impl FnOnce(&mut ViewContext<Self>) -> ViewHandle<V>,
1089 ) {
1090 let type_id = TypeId::of::<V>();
1091 if self
1092 .notifications
1093 .iter()
1094 .all(|(existing_type_id, existing_id, _)| {
1095 (*existing_type_id, *existing_id) != (type_id, id)
1096 })
1097 {
1098 let notification = build_notification(cx);
1099 cx.subscribe(¬ification, move |this, handle, event, cx| {
1100 if handle.read(cx).should_dismiss_notification_on_event(event) {
1101 this.dismiss_notification(type_id, id, cx);
1102 }
1103 })
1104 .detach();
1105 self.notifications
1106 .push((type_id, id, Box::new(notification)));
1107 cx.notify();
1108 }
1109 }
1110
1111 fn dismiss_notification(&mut self, type_id: TypeId, id: usize, cx: &mut ViewContext<Self>) {
1112 self.notifications
1113 .retain(|(existing_type_id, existing_id, _)| {
1114 if (*existing_type_id, *existing_id) == (type_id, id) {
1115 cx.notify();
1116 false
1117 } else {
1118 true
1119 }
1120 });
1121 }
1122
1123 pub fn items<'a>(
1124 &'a self,
1125 cx: &'a AppContext,
1126 ) -> impl 'a + Iterator<Item = &Box<dyn ItemHandle>> {
1127 self.panes.iter().flat_map(|pane| pane.read(cx).items())
1128 }
1129
1130 pub fn item_of_type<T: Item>(&self, cx: &AppContext) -> Option<ViewHandle<T>> {
1131 self.items_of_type(cx).max_by_key(|item| item.id())
1132 }
1133
1134 pub fn items_of_type<'a, T: Item>(
1135 &'a self,
1136 cx: &'a AppContext,
1137 ) -> impl 'a + Iterator<Item = ViewHandle<T>> {
1138 self.panes
1139 .iter()
1140 .flat_map(|pane| pane.read(cx).items_of_type())
1141 }
1142
1143 pub fn active_item(&self, cx: &AppContext) -> Option<Box<dyn ItemHandle>> {
1144 self.active_pane().read(cx).active_item()
1145 }
1146
1147 fn active_project_path(&self, cx: &ViewContext<Self>) -> Option<ProjectPath> {
1148 self.active_item(cx).and_then(|item| item.project_path(cx))
1149 }
1150
1151 pub fn save_active_item(
1152 &mut self,
1153 force_name_change: bool,
1154 cx: &mut ViewContext<Self>,
1155 ) -> Task<Result<()>> {
1156 let project = self.project.clone();
1157 if let Some(item) = self.active_item(cx) {
1158 if !force_name_change && item.can_save(cx) {
1159 if item.has_conflict(cx.as_ref()) {
1160 const CONFLICT_MESSAGE: &'static str = "This file has changed on disk since you started editing it. Do you want to overwrite it?";
1161
1162 let mut answer = cx.prompt(
1163 PromptLevel::Warning,
1164 CONFLICT_MESSAGE,
1165 &["Overwrite", "Cancel"],
1166 );
1167 cx.spawn(|_, mut cx| async move {
1168 let answer = answer.recv().await;
1169 if answer == Some(0) {
1170 cx.update(|cx| item.save(project, cx)).await?;
1171 }
1172 Ok(())
1173 })
1174 } else {
1175 item.save(project, cx)
1176 }
1177 } else if item.is_singleton(cx) {
1178 let worktree = self.worktrees(cx).next();
1179 let start_abs_path = worktree
1180 .and_then(|w| w.read(cx).as_local())
1181 .map_or(Path::new(""), |w| w.abs_path())
1182 .to_path_buf();
1183 let mut abs_path = cx.prompt_for_new_path(&start_abs_path);
1184 cx.spawn(|_, mut cx| async move {
1185 if let Some(abs_path) = abs_path.recv().await.flatten() {
1186 cx.update(|cx| item.save_as(project, abs_path, cx)).await?;
1187 }
1188 Ok(())
1189 })
1190 } else {
1191 Task::ready(Ok(()))
1192 }
1193 } else {
1194 Task::ready(Ok(()))
1195 }
1196 }
1197
1198 pub fn toggle_sidebar_item(&mut self, action: &ToggleSidebarItem, cx: &mut ViewContext<Self>) {
1199 let sidebar = match action.side {
1200 Side::Left => &mut self.left_sidebar,
1201 Side::Right => &mut self.right_sidebar,
1202 };
1203 let active_item = sidebar.update(cx, |sidebar, cx| {
1204 sidebar.toggle_item(action.item_index, cx);
1205 sidebar.active_item().map(|item| item.to_any())
1206 });
1207 if let Some(active_item) = active_item {
1208 cx.focus(active_item);
1209 } else {
1210 cx.focus_self();
1211 }
1212 cx.notify();
1213 }
1214
1215 pub fn toggle_sidebar_item_focus(
1216 &mut self,
1217 action: &ToggleSidebarItemFocus,
1218 cx: &mut ViewContext<Self>,
1219 ) {
1220 let sidebar = match action.side {
1221 Side::Left => &mut self.left_sidebar,
1222 Side::Right => &mut self.right_sidebar,
1223 };
1224 let active_item = sidebar.update(cx, |sidebar, cx| {
1225 sidebar.activate_item(action.item_index, cx);
1226 sidebar.active_item().cloned()
1227 });
1228 if let Some(active_item) = active_item {
1229 if active_item.is_focused(cx) {
1230 cx.focus_self();
1231 } else {
1232 cx.focus(active_item.to_any());
1233 }
1234 }
1235 cx.notify();
1236 }
1237
1238 fn add_pane(&mut self, cx: &mut ViewContext<Self>) -> ViewHandle<Pane> {
1239 let pane = cx.add_view(|cx| Pane::new(cx));
1240 let pane_id = pane.id();
1241 cx.observe(&pane, move |me, _, cx| {
1242 let active_entry = me.active_project_path(cx);
1243 me.project
1244 .update(cx, |project, cx| project.set_active_path(active_entry, cx));
1245 })
1246 .detach();
1247 cx.subscribe(&pane, move |me, _, event, cx| {
1248 me.handle_pane_event(pane_id, event, cx)
1249 })
1250 .detach();
1251 self.panes.push(pane.clone());
1252 self.activate_pane(pane.clone(), cx);
1253 cx.emit(Event::PaneAdded(pane.clone()));
1254 pane
1255 }
1256
1257 pub fn add_item(&mut self, item: Box<dyn ItemHandle>, cx: &mut ViewContext<Self>) {
1258 let pane = self.active_pane().clone();
1259 Pane::add_item(self, pane, item, true, true, cx);
1260 }
1261
1262 pub fn open_path(
1263 &mut self,
1264 path: impl Into<ProjectPath>,
1265 focus_item: bool,
1266 cx: &mut ViewContext<Self>,
1267 ) -> Task<Result<Box<dyn ItemHandle>, Arc<anyhow::Error>>> {
1268 let pane = self.active_pane().downgrade();
1269 let task = self.load_path(path.into(), cx);
1270 cx.spawn(|this, mut cx| async move {
1271 let (project_entry_id, build_item) = task.await?;
1272 let pane = pane
1273 .upgrade(&cx)
1274 .ok_or_else(|| anyhow!("pane was closed"))?;
1275 this.update(&mut cx, |this, cx| {
1276 Ok(Pane::open_item(
1277 this,
1278 pane,
1279 project_entry_id,
1280 focus_item,
1281 cx,
1282 build_item,
1283 ))
1284 })
1285 })
1286 }
1287
1288 pub(crate) fn load_path(
1289 &mut self,
1290 path: ProjectPath,
1291 cx: &mut ViewContext<Self>,
1292 ) -> Task<
1293 Result<(
1294 ProjectEntryId,
1295 impl 'static + FnOnce(&mut MutableAppContext) -> Box<dyn ItemHandle>,
1296 )>,
1297 > {
1298 let project = self.project().clone();
1299 let project_item = project.update(cx, |project, cx| project.open_path(path, cx));
1300 let window_id = cx.window_id();
1301 cx.as_mut().spawn(|mut cx| async move {
1302 let (project_entry_id, project_item) = project_item.await?;
1303 let build_item = cx.update(|cx| {
1304 cx.default_global::<ProjectItemBuilders>()
1305 .get(&project_item.model_type())
1306 .ok_or_else(|| anyhow!("no item builder for project item"))
1307 .cloned()
1308 })?;
1309 let build_item =
1310 move |cx: &mut MutableAppContext| build_item(window_id, project, project_item, cx);
1311 Ok((project_entry_id, build_item))
1312 })
1313 }
1314
1315 pub fn open_project_item<T>(
1316 &mut self,
1317 project_item: ModelHandle<T::Item>,
1318 cx: &mut ViewContext<Self>,
1319 ) -> ViewHandle<T>
1320 where
1321 T: ProjectItem,
1322 {
1323 use project::Item as _;
1324
1325 let entry_id = project_item.read(cx).entry_id(cx);
1326 if let Some(item) = entry_id
1327 .and_then(|entry_id| self.active_pane().read(cx).item_for_entry(entry_id, cx))
1328 .and_then(|item| item.downcast())
1329 {
1330 self.activate_item(&item, cx);
1331 return item;
1332 }
1333
1334 let item = cx.add_view(|cx| T::for_project_item(self.project().clone(), project_item, cx));
1335 self.add_item(Box::new(item.clone()), cx);
1336 item
1337 }
1338
1339 pub fn activate_item(&mut self, item: &dyn ItemHandle, cx: &mut ViewContext<Self>) -> bool {
1340 let result = self.panes.iter().find_map(|pane| {
1341 if let Some(ix) = pane.read(cx).index_for_item(item) {
1342 Some((pane.clone(), ix))
1343 } else {
1344 None
1345 }
1346 });
1347 if let Some((pane, ix)) = result {
1348 self.activate_pane(pane.clone(), cx);
1349 pane.update(cx, |pane, cx| pane.activate_item(ix, true, true, cx));
1350 true
1351 } else {
1352 false
1353 }
1354 }
1355
1356 pub fn activate_next_pane(&mut self, cx: &mut ViewContext<Self>) {
1357 let next_pane = {
1358 let panes = self.center.panes();
1359 let ix = panes
1360 .iter()
1361 .position(|pane| **pane == self.active_pane)
1362 .unwrap();
1363 let next_ix = (ix + 1) % panes.len();
1364 panes[next_ix].clone()
1365 };
1366 self.activate_pane(next_pane, cx);
1367 }
1368
1369 pub fn activate_previous_pane(&mut self, cx: &mut ViewContext<Self>) {
1370 let prev_pane = {
1371 let panes = self.center.panes();
1372 let ix = panes
1373 .iter()
1374 .position(|pane| **pane == self.active_pane)
1375 .unwrap();
1376 let prev_ix = if ix == 0 { panes.len() - 1 } else { ix - 1 };
1377 panes[prev_ix].clone()
1378 };
1379 self.activate_pane(prev_pane, cx);
1380 }
1381
1382 fn activate_pane(&mut self, pane: ViewHandle<Pane>, cx: &mut ViewContext<Self>) {
1383 if self.active_pane != pane {
1384 self.active_pane = pane.clone();
1385 self.status_bar.update(cx, |status_bar, cx| {
1386 status_bar.set_active_pane(&self.active_pane, cx);
1387 });
1388 cx.focus(&self.active_pane);
1389 cx.notify();
1390 }
1391
1392 self.update_followers(
1393 proto::update_followers::Variant::UpdateActiveView(proto::UpdateActiveView {
1394 id: self.active_item(cx).map(|item| item.id() as u64),
1395 leader_id: self.leader_for_pane(&pane).map(|id| id.0),
1396 }),
1397 cx,
1398 );
1399 }
1400
1401 fn handle_pane_event(
1402 &mut self,
1403 pane_id: usize,
1404 event: &pane::Event,
1405 cx: &mut ViewContext<Self>,
1406 ) {
1407 if let Some(pane) = self.pane(pane_id) {
1408 match event {
1409 pane::Event::Split(direction) => {
1410 self.split_pane(pane, *direction, cx);
1411 }
1412 pane::Event::Remove => {
1413 self.remove_pane(pane, cx);
1414 }
1415 pane::Event::Activate => {
1416 self.activate_pane(pane, cx);
1417 }
1418 pane::Event::ActivateItem { local } => {
1419 if *local {
1420 self.unfollow(&pane, cx);
1421 }
1422 }
1423 }
1424 } else {
1425 error!("pane {} not found", pane_id);
1426 }
1427 }
1428
1429 pub fn split_pane(
1430 &mut self,
1431 pane: ViewHandle<Pane>,
1432 direction: SplitDirection,
1433 cx: &mut ViewContext<Self>,
1434 ) -> ViewHandle<Pane> {
1435 let new_pane = self.add_pane(cx);
1436 self.activate_pane(new_pane.clone(), cx);
1437 if let Some(item) = pane.read(cx).active_item() {
1438 if let Some(clone) = item.clone_on_split(cx.as_mut()) {
1439 Pane::add_item(self, new_pane.clone(), clone, true, true, cx);
1440 }
1441 }
1442 self.center.split(&pane, &new_pane, direction).unwrap();
1443 cx.notify();
1444 new_pane
1445 }
1446
1447 fn remove_pane(&mut self, pane: ViewHandle<Pane>, cx: &mut ViewContext<Self>) {
1448 if self.center.remove(&pane).unwrap() {
1449 self.panes.retain(|p| p != &pane);
1450 self.activate_pane(self.panes.last().unwrap().clone(), cx);
1451 self.unfollow(&pane, cx);
1452 self.last_leaders_by_pane.remove(&pane.downgrade());
1453 cx.notify();
1454 }
1455 }
1456
1457 pub fn panes(&self) -> &[ViewHandle<Pane>] {
1458 &self.panes
1459 }
1460
1461 fn pane(&self, pane_id: usize) -> Option<ViewHandle<Pane>> {
1462 self.panes.iter().find(|pane| pane.id() == pane_id).cloned()
1463 }
1464
1465 pub fn active_pane(&self) -> &ViewHandle<Pane> {
1466 &self.active_pane
1467 }
1468
1469 fn project_remote_id_changed(&mut self, remote_id: Option<u64>, cx: &mut ViewContext<Self>) {
1470 if let Some(remote_id) = remote_id {
1471 self.remote_entity_subscription =
1472 Some(self.client.add_view_for_remote_entity(remote_id, cx));
1473 } else {
1474 self.remote_entity_subscription.take();
1475 }
1476 }
1477
1478 fn collaborator_left(&mut self, peer_id: PeerId, cx: &mut ViewContext<Self>) {
1479 self.leader_state.followers.remove(&peer_id);
1480 if let Some(states_by_pane) = self.follower_states_by_leader.remove(&peer_id) {
1481 for state in states_by_pane.into_values() {
1482 for item in state.items_by_leader_view_id.into_values() {
1483 if let FollowerItem::Loaded(item) = item {
1484 item.set_leader_replica_id(None, cx);
1485 }
1486 }
1487 }
1488 }
1489 cx.notify();
1490 }
1491
1492 pub fn toggle_follow(
1493 &mut self,
1494 ToggleFollow(leader_id): &ToggleFollow,
1495 cx: &mut ViewContext<Self>,
1496 ) -> Option<Task<Result<()>>> {
1497 let leader_id = *leader_id;
1498 let pane = self.active_pane().clone();
1499
1500 if let Some(prev_leader_id) = self.unfollow(&pane, cx) {
1501 if leader_id == prev_leader_id {
1502 return None;
1503 }
1504 }
1505
1506 self.last_leaders_by_pane
1507 .insert(pane.downgrade(), leader_id);
1508 self.follower_states_by_leader
1509 .entry(leader_id)
1510 .or_default()
1511 .insert(pane.clone(), Default::default());
1512 cx.notify();
1513
1514 let project_id = self.project.read(cx).remote_id()?;
1515 let request = self.client.request(proto::Follow {
1516 project_id,
1517 leader_id: leader_id.0,
1518 });
1519 Some(cx.spawn_weak(|this, mut cx| async move {
1520 let response = request.await?;
1521 if let Some(this) = this.upgrade(&cx) {
1522 this.update(&mut cx, |this, _| {
1523 let state = this
1524 .follower_states_by_leader
1525 .get_mut(&leader_id)
1526 .and_then(|states_by_pane| states_by_pane.get_mut(&pane))
1527 .ok_or_else(|| anyhow!("following interrupted"))?;
1528 state.active_view_id = response.active_view_id;
1529 Ok::<_, anyhow::Error>(())
1530 })?;
1531 Self::add_views_from_leader(this, leader_id, vec![pane], response.views, &mut cx)
1532 .await?;
1533 }
1534 Ok(())
1535 }))
1536 }
1537
1538 pub fn follow_next_collaborator(
1539 &mut self,
1540 _: &FollowNextCollaborator,
1541 cx: &mut ViewContext<Self>,
1542 ) -> Option<Task<Result<()>>> {
1543 let collaborators = self.project.read(cx).collaborators();
1544 let next_leader_id = if let Some(leader_id) = self.leader_for_pane(&self.active_pane) {
1545 let mut collaborators = collaborators.keys().copied();
1546 while let Some(peer_id) = collaborators.next() {
1547 if peer_id == leader_id {
1548 break;
1549 }
1550 }
1551 collaborators.next()
1552 } else if let Some(last_leader_id) =
1553 self.last_leaders_by_pane.get(&self.active_pane.downgrade())
1554 {
1555 if collaborators.contains_key(last_leader_id) {
1556 Some(*last_leader_id)
1557 } else {
1558 None
1559 }
1560 } else {
1561 None
1562 };
1563
1564 next_leader_id
1565 .or_else(|| collaborators.keys().copied().next())
1566 .and_then(|leader_id| self.toggle_follow(&ToggleFollow(leader_id), cx))
1567 }
1568
1569 pub fn unfollow(
1570 &mut self,
1571 pane: &ViewHandle<Pane>,
1572 cx: &mut ViewContext<Self>,
1573 ) -> Option<PeerId> {
1574 for (leader_id, states_by_pane) in &mut self.follower_states_by_leader {
1575 let leader_id = *leader_id;
1576 if let Some(state) = states_by_pane.remove(&pane) {
1577 for (_, item) in state.items_by_leader_view_id {
1578 if let FollowerItem::Loaded(item) = item {
1579 item.set_leader_replica_id(None, cx);
1580 }
1581 }
1582
1583 if states_by_pane.is_empty() {
1584 self.follower_states_by_leader.remove(&leader_id);
1585 if let Some(project_id) = self.project.read(cx).remote_id() {
1586 self.client
1587 .send(proto::Unfollow {
1588 project_id,
1589 leader_id: leader_id.0,
1590 })
1591 .log_err();
1592 }
1593 }
1594
1595 cx.notify();
1596 return Some(leader_id);
1597 }
1598 }
1599 None
1600 }
1601
1602 fn render_connection_status(&self, cx: &mut RenderContext<Self>) -> Option<ElementBox> {
1603 let theme = &cx.global::<Settings>().theme;
1604 match &*self.client.status().borrow() {
1605 client::Status::ConnectionError
1606 | client::Status::ConnectionLost
1607 | client::Status::Reauthenticating
1608 | client::Status::Reconnecting { .. }
1609 | client::Status::ReconnectionError { .. } => Some(
1610 Container::new(
1611 Align::new(
1612 ConstrainedBox::new(
1613 Svg::new("icons/offline-14.svg")
1614 .with_color(theme.workspace.titlebar.offline_icon.color)
1615 .boxed(),
1616 )
1617 .with_width(theme.workspace.titlebar.offline_icon.width)
1618 .boxed(),
1619 )
1620 .boxed(),
1621 )
1622 .with_style(theme.workspace.titlebar.offline_icon.container)
1623 .boxed(),
1624 ),
1625 client::Status::UpgradeRequired => Some(
1626 Label::new(
1627 "Please update Zed to collaborate".to_string(),
1628 theme.workspace.titlebar.outdated_warning.text.clone(),
1629 )
1630 .contained()
1631 .with_style(theme.workspace.titlebar.outdated_warning.container)
1632 .aligned()
1633 .boxed(),
1634 ),
1635 _ => None,
1636 }
1637 }
1638
1639 fn render_titlebar(&self, theme: &Theme, cx: &mut RenderContext<Self>) -> ElementBox {
1640 let mut worktree_root_names = String::new();
1641 {
1642 let mut worktrees = self.project.read(cx).visible_worktrees(cx).peekable();
1643 while let Some(worktree) = worktrees.next() {
1644 worktree_root_names.push_str(worktree.read(cx).root_name());
1645 if worktrees.peek().is_some() {
1646 worktree_root_names.push_str(", ");
1647 }
1648 }
1649 }
1650
1651 ConstrainedBox::new(
1652 Container::new(
1653 Stack::new()
1654 .with_child(
1655 Label::new(worktree_root_names, theme.workspace.titlebar.title.clone())
1656 .aligned()
1657 .left()
1658 .boxed(),
1659 )
1660 .with_child(
1661 Align::new(
1662 Flex::row()
1663 .with_children(self.render_collaborators(theme, cx))
1664 .with_children(self.render_current_user(
1665 self.user_store.read(cx).current_user().as_ref(),
1666 self.project.read(cx).replica_id(),
1667 theme,
1668 cx,
1669 ))
1670 .with_children(self.render_connection_status(cx))
1671 .boxed(),
1672 )
1673 .right()
1674 .boxed(),
1675 )
1676 .boxed(),
1677 )
1678 .with_style(theme.workspace.titlebar.container)
1679 .boxed(),
1680 )
1681 .with_height(theme.workspace.titlebar.height)
1682 .named("titlebar")
1683 }
1684
1685 fn render_collaborators(&self, theme: &Theme, cx: &mut RenderContext<Self>) -> Vec<ElementBox> {
1686 let mut collaborators = self
1687 .project
1688 .read(cx)
1689 .collaborators()
1690 .values()
1691 .cloned()
1692 .collect::<Vec<_>>();
1693 collaborators.sort_unstable_by_key(|collaborator| collaborator.replica_id);
1694 collaborators
1695 .into_iter()
1696 .filter_map(|collaborator| {
1697 Some(self.render_avatar(
1698 collaborator.user.avatar.clone()?,
1699 collaborator.replica_id,
1700 Some(collaborator.peer_id),
1701 theme,
1702 cx,
1703 ))
1704 })
1705 .collect()
1706 }
1707
1708 fn render_current_user(
1709 &self,
1710 user: Option<&Arc<User>>,
1711 replica_id: ReplicaId,
1712 theme: &Theme,
1713 cx: &mut RenderContext<Self>,
1714 ) -> Option<ElementBox> {
1715 let status = *self.client.status().borrow();
1716 if let Some(avatar) = user.and_then(|user| user.avatar.clone()) {
1717 Some(self.render_avatar(avatar, replica_id, None, theme, cx))
1718 } else if matches!(status, client::Status::UpgradeRequired) {
1719 None
1720 } else {
1721 Some(
1722 MouseEventHandler::new::<Authenticate, _, _>(0, cx, |state, _| {
1723 let style = theme
1724 .workspace
1725 .titlebar
1726 .sign_in_prompt
1727 .style_for(state, false);
1728 Label::new("Sign in".to_string(), style.text.clone())
1729 .contained()
1730 .with_style(style.container)
1731 .boxed()
1732 })
1733 .on_click(|_, cx| cx.dispatch_action(Authenticate))
1734 .with_cursor_style(CursorStyle::PointingHand)
1735 .aligned()
1736 .boxed(),
1737 )
1738 }
1739 }
1740
1741 fn render_avatar(
1742 &self,
1743 avatar: Arc<ImageData>,
1744 replica_id: ReplicaId,
1745 peer_id: Option<PeerId>,
1746 theme: &Theme,
1747 cx: &mut RenderContext<Self>,
1748 ) -> ElementBox {
1749 let replica_color = theme.editor.replica_selection_style(replica_id).cursor;
1750 let is_followed = peer_id.map_or(false, |peer_id| {
1751 self.follower_states_by_leader.contains_key(&peer_id)
1752 });
1753 let mut avatar_style = theme.workspace.titlebar.avatar;
1754 if is_followed {
1755 avatar_style.border = Border::all(1.0, replica_color);
1756 }
1757 let content = Stack::new()
1758 .with_child(
1759 Image::new(avatar)
1760 .with_style(avatar_style)
1761 .constrained()
1762 .with_width(theme.workspace.titlebar.avatar_width)
1763 .aligned()
1764 .boxed(),
1765 )
1766 .with_child(
1767 AvatarRibbon::new(replica_color)
1768 .constrained()
1769 .with_width(theme.workspace.titlebar.avatar_ribbon.width)
1770 .with_height(theme.workspace.titlebar.avatar_ribbon.height)
1771 .aligned()
1772 .bottom()
1773 .boxed(),
1774 )
1775 .constrained()
1776 .with_width(theme.workspace.titlebar.avatar_width)
1777 .contained()
1778 .with_margin_left(theme.workspace.titlebar.avatar_margin)
1779 .boxed();
1780
1781 if let Some(peer_id) = peer_id {
1782 MouseEventHandler::new::<ToggleFollow, _, _>(replica_id.into(), cx, move |_, _| content)
1783 .with_cursor_style(CursorStyle::PointingHand)
1784 .on_click(move |_, cx| cx.dispatch_action(ToggleFollow(peer_id)))
1785 .boxed()
1786 } else {
1787 content
1788 }
1789 }
1790
1791 fn render_disconnected_overlay(&self, cx: &AppContext) -> Option<ElementBox> {
1792 if self.project.read(cx).is_read_only() {
1793 let theme = &cx.global::<Settings>().theme;
1794 Some(
1795 EventHandler::new(
1796 Label::new(
1797 "Your connection to the remote project has been lost.".to_string(),
1798 theme.workspace.disconnected_overlay.text.clone(),
1799 )
1800 .aligned()
1801 .contained()
1802 .with_style(theme.workspace.disconnected_overlay.container)
1803 .boxed(),
1804 )
1805 .capture(|_, _, _| true)
1806 .boxed(),
1807 )
1808 } else {
1809 None
1810 }
1811 }
1812
1813 fn render_notifications(&self, theme: &theme::Workspace) -> Option<ElementBox> {
1814 if self.notifications.is_empty() {
1815 None
1816 } else {
1817 Some(
1818 Flex::column()
1819 .with_children(self.notifications.iter().map(|(_, _, notification)| {
1820 ChildView::new(notification.as_ref())
1821 .contained()
1822 .with_style(theme.notification)
1823 .boxed()
1824 }))
1825 .constrained()
1826 .with_width(theme.notifications.width)
1827 .contained()
1828 .with_style(theme.notifications.container)
1829 .aligned()
1830 .bottom()
1831 .right()
1832 .boxed(),
1833 )
1834 }
1835 }
1836
1837 // RPC handlers
1838
1839 async fn handle_follow(
1840 this: ViewHandle<Self>,
1841 envelope: TypedEnvelope<proto::Follow>,
1842 _: Arc<Client>,
1843 mut cx: AsyncAppContext,
1844 ) -> Result<proto::FollowResponse> {
1845 this.update(&mut cx, |this, cx| {
1846 this.leader_state
1847 .followers
1848 .insert(envelope.original_sender_id()?);
1849
1850 let active_view_id = this
1851 .active_item(cx)
1852 .and_then(|i| i.to_followable_item_handle(cx))
1853 .map(|i| i.id() as u64);
1854 Ok(proto::FollowResponse {
1855 active_view_id,
1856 views: this
1857 .panes()
1858 .iter()
1859 .flat_map(|pane| {
1860 let leader_id = this.leader_for_pane(pane).map(|id| id.0);
1861 pane.read(cx).items().filter_map({
1862 let cx = &cx;
1863 move |item| {
1864 let id = item.id() as u64;
1865 let item = item.to_followable_item_handle(cx)?;
1866 let variant = item.to_state_proto(cx)?;
1867 Some(proto::View {
1868 id,
1869 leader_id,
1870 variant: Some(variant),
1871 })
1872 }
1873 })
1874 })
1875 .collect(),
1876 })
1877 })
1878 }
1879
1880 async fn handle_unfollow(
1881 this: ViewHandle<Self>,
1882 envelope: TypedEnvelope<proto::Unfollow>,
1883 _: Arc<Client>,
1884 mut cx: AsyncAppContext,
1885 ) -> Result<()> {
1886 this.update(&mut cx, |this, _| {
1887 this.leader_state
1888 .followers
1889 .remove(&envelope.original_sender_id()?);
1890 Ok(())
1891 })
1892 }
1893
1894 async fn handle_update_followers(
1895 this: ViewHandle<Self>,
1896 envelope: TypedEnvelope<proto::UpdateFollowers>,
1897 _: Arc<Client>,
1898 mut cx: AsyncAppContext,
1899 ) -> Result<()> {
1900 let leader_id = envelope.original_sender_id()?;
1901 match envelope
1902 .payload
1903 .variant
1904 .ok_or_else(|| anyhow!("invalid update"))?
1905 {
1906 proto::update_followers::Variant::UpdateActiveView(update_active_view) => {
1907 this.update(&mut cx, |this, cx| {
1908 this.update_leader_state(leader_id, cx, |state, _| {
1909 state.active_view_id = update_active_view.id;
1910 });
1911 Ok::<_, anyhow::Error>(())
1912 })
1913 }
1914 proto::update_followers::Variant::UpdateView(update_view) => {
1915 this.update(&mut cx, |this, cx| {
1916 let variant = update_view
1917 .variant
1918 .ok_or_else(|| anyhow!("missing update view variant"))?;
1919 this.update_leader_state(leader_id, cx, |state, cx| {
1920 let variant = variant.clone();
1921 match state
1922 .items_by_leader_view_id
1923 .entry(update_view.id)
1924 .or_insert(FollowerItem::Loading(Vec::new()))
1925 {
1926 FollowerItem::Loaded(item) => {
1927 item.apply_update_proto(variant, cx).log_err();
1928 }
1929 FollowerItem::Loading(updates) => updates.push(variant),
1930 }
1931 });
1932 Ok(())
1933 })
1934 }
1935 proto::update_followers::Variant::CreateView(view) => {
1936 let panes = this.read_with(&cx, |this, _| {
1937 this.follower_states_by_leader
1938 .get(&leader_id)
1939 .into_iter()
1940 .flat_map(|states_by_pane| states_by_pane.keys())
1941 .cloned()
1942 .collect()
1943 });
1944 Self::add_views_from_leader(this.clone(), leader_id, panes, vec![view], &mut cx)
1945 .await?;
1946 Ok(())
1947 }
1948 }
1949 .log_err();
1950
1951 Ok(())
1952 }
1953
1954 async fn add_views_from_leader(
1955 this: ViewHandle<Self>,
1956 leader_id: PeerId,
1957 panes: Vec<ViewHandle<Pane>>,
1958 views: Vec<proto::View>,
1959 cx: &mut AsyncAppContext,
1960 ) -> Result<()> {
1961 let project = this.read_with(cx, |this, _| this.project.clone());
1962 let replica_id = project
1963 .read_with(cx, |project, _| {
1964 project
1965 .collaborators()
1966 .get(&leader_id)
1967 .map(|c| c.replica_id)
1968 })
1969 .ok_or_else(|| anyhow!("no such collaborator {}", leader_id))?;
1970
1971 let item_builders = cx.update(|cx| {
1972 cx.default_global::<FollowableItemBuilders>()
1973 .values()
1974 .map(|b| b.0)
1975 .collect::<Vec<_>>()
1976 .clone()
1977 });
1978
1979 let mut item_tasks_by_pane = HashMap::default();
1980 for pane in panes {
1981 let mut item_tasks = Vec::new();
1982 let mut leader_view_ids = Vec::new();
1983 for view in &views {
1984 let mut variant = view.variant.clone();
1985 if variant.is_none() {
1986 Err(anyhow!("missing variant"))?;
1987 }
1988 for build_item in &item_builders {
1989 let task =
1990 cx.update(|cx| build_item(pane.clone(), project.clone(), &mut variant, cx));
1991 if let Some(task) = task {
1992 item_tasks.push(task);
1993 leader_view_ids.push(view.id);
1994 break;
1995 } else {
1996 assert!(variant.is_some());
1997 }
1998 }
1999 }
2000
2001 item_tasks_by_pane.insert(pane, (item_tasks, leader_view_ids));
2002 }
2003
2004 for (pane, (item_tasks, leader_view_ids)) in item_tasks_by_pane {
2005 let items = futures::future::try_join_all(item_tasks).await?;
2006 this.update(cx, |this, cx| {
2007 let state = this
2008 .follower_states_by_leader
2009 .get_mut(&leader_id)?
2010 .get_mut(&pane)?;
2011
2012 for (id, item) in leader_view_ids.into_iter().zip(items) {
2013 item.set_leader_replica_id(Some(replica_id), cx);
2014 match state.items_by_leader_view_id.entry(id) {
2015 hash_map::Entry::Occupied(e) => {
2016 let e = e.into_mut();
2017 if let FollowerItem::Loading(updates) = e {
2018 for update in updates.drain(..) {
2019 item.apply_update_proto(update, cx)
2020 .context("failed to apply view update")
2021 .log_err();
2022 }
2023 }
2024 *e = FollowerItem::Loaded(item);
2025 }
2026 hash_map::Entry::Vacant(e) => {
2027 e.insert(FollowerItem::Loaded(item));
2028 }
2029 }
2030 }
2031
2032 Some(())
2033 });
2034 }
2035 this.update(cx, |this, cx| this.leader_updated(leader_id, cx));
2036
2037 Ok(())
2038 }
2039
2040 fn update_followers(
2041 &self,
2042 update: proto::update_followers::Variant,
2043 cx: &AppContext,
2044 ) -> Option<()> {
2045 let project_id = self.project.read(cx).remote_id()?;
2046 if !self.leader_state.followers.is_empty() {
2047 self.client
2048 .send(proto::UpdateFollowers {
2049 project_id,
2050 follower_ids: self.leader_state.followers.iter().map(|f| f.0).collect(),
2051 variant: Some(update),
2052 })
2053 .log_err();
2054 }
2055 None
2056 }
2057
2058 pub fn leader_for_pane(&self, pane: &ViewHandle<Pane>) -> Option<PeerId> {
2059 self.follower_states_by_leader
2060 .iter()
2061 .find_map(|(leader_id, state)| {
2062 if state.contains_key(pane) {
2063 Some(*leader_id)
2064 } else {
2065 None
2066 }
2067 })
2068 }
2069
2070 fn update_leader_state(
2071 &mut self,
2072 leader_id: PeerId,
2073 cx: &mut ViewContext<Self>,
2074 mut update_fn: impl FnMut(&mut FollowerState, &mut ViewContext<Self>),
2075 ) {
2076 for (_, state) in self
2077 .follower_states_by_leader
2078 .get_mut(&leader_id)
2079 .into_iter()
2080 .flatten()
2081 {
2082 update_fn(state, cx);
2083 }
2084 self.leader_updated(leader_id, cx);
2085 }
2086
2087 fn leader_updated(&mut self, leader_id: PeerId, cx: &mut ViewContext<Self>) -> Option<()> {
2088 let mut items_to_add = Vec::new();
2089 for (pane, state) in self.follower_states_by_leader.get(&leader_id)? {
2090 if let Some(active_item) = state
2091 .active_view_id
2092 .and_then(|id| state.items_by_leader_view_id.get(&id))
2093 {
2094 if let FollowerItem::Loaded(item) = active_item {
2095 items_to_add.push((pane.clone(), item.boxed_clone()));
2096 }
2097 }
2098 }
2099
2100 for (pane, item) in items_to_add {
2101 Pane::add_item(self, pane.clone(), item.boxed_clone(), false, false, cx);
2102 if pane == self.active_pane {
2103 pane.update(cx, |pane, cx| pane.focus_active_item(cx));
2104 }
2105 cx.notify();
2106 }
2107 None
2108 }
2109}
2110
2111impl Entity for Workspace {
2112 type Event = Event;
2113}
2114
2115impl View for Workspace {
2116 fn ui_name() -> &'static str {
2117 "Workspace"
2118 }
2119
2120 fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
2121 let theme = cx.global::<Settings>().theme.clone();
2122 Stack::new()
2123 .with_child(
2124 Flex::column()
2125 .with_child(self.render_titlebar(&theme, cx))
2126 .with_child(
2127 Stack::new()
2128 .with_child({
2129 Flex::row()
2130 .with_children(
2131 if self.left_sidebar.read(cx).active_item().is_some() {
2132 Some(
2133 ChildView::new(&self.left_sidebar)
2134 .flex(0.8, false)
2135 .boxed(),
2136 )
2137 } else {
2138 None
2139 },
2140 )
2141 .with_child(
2142 FlexItem::new(self.center.render(
2143 &theme,
2144 &self.follower_states_by_leader,
2145 self.project.read(cx).collaborators(),
2146 ))
2147 .flex(1., true)
2148 .boxed(),
2149 )
2150 .with_children(
2151 if self.right_sidebar.read(cx).active_item().is_some() {
2152 Some(
2153 ChildView::new(&self.right_sidebar)
2154 .flex(0.8, false)
2155 .boxed(),
2156 )
2157 } else {
2158 None
2159 },
2160 )
2161 .boxed()
2162 })
2163 .with_children(self.modal.as_ref().map(|m| {
2164 ChildView::new(m)
2165 .contained()
2166 .with_style(theme.workspace.modal)
2167 .aligned()
2168 .top()
2169 .boxed()
2170 }))
2171 .with_children(self.render_notifications(&theme.workspace))
2172 .flex(1.0, true)
2173 .boxed(),
2174 )
2175 .with_child(ChildView::new(&self.status_bar).boxed())
2176 .contained()
2177 .with_background_color(theme.workspace.background)
2178 .boxed(),
2179 )
2180 .with_children(self.render_disconnected_overlay(cx))
2181 .named("workspace")
2182 }
2183
2184 fn on_focus(&mut self, cx: &mut ViewContext<Self>) {
2185 cx.focus(&self.active_pane);
2186 }
2187}
2188
2189pub trait WorkspaceHandle {
2190 fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath>;
2191}
2192
2193impl WorkspaceHandle for ViewHandle<Workspace> {
2194 fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath> {
2195 self.read(cx)
2196 .worktrees(cx)
2197 .flat_map(|worktree| {
2198 let worktree_id = worktree.read(cx).id();
2199 worktree.read(cx).files(true, 0).map(move |f| ProjectPath {
2200 worktree_id,
2201 path: f.path.clone(),
2202 })
2203 })
2204 .collect::<Vec<_>>()
2205 }
2206}
2207
2208pub struct AvatarRibbon {
2209 color: Color,
2210}
2211
2212impl AvatarRibbon {
2213 pub fn new(color: Color) -> AvatarRibbon {
2214 AvatarRibbon { color }
2215 }
2216}
2217
2218impl Element for AvatarRibbon {
2219 type LayoutState = ();
2220
2221 type PaintState = ();
2222
2223 fn layout(
2224 &mut self,
2225 constraint: gpui::SizeConstraint,
2226 _: &mut gpui::LayoutContext,
2227 ) -> (gpui::geometry::vector::Vector2F, Self::LayoutState) {
2228 (constraint.max, ())
2229 }
2230
2231 fn paint(
2232 &mut self,
2233 bounds: gpui::geometry::rect::RectF,
2234 _: gpui::geometry::rect::RectF,
2235 _: &mut Self::LayoutState,
2236 cx: &mut gpui::PaintContext,
2237 ) -> Self::PaintState {
2238 let mut path = PathBuilder::new();
2239 path.reset(bounds.lower_left());
2240 path.curve_to(
2241 bounds.origin() + vec2f(bounds.height(), 0.),
2242 bounds.origin(),
2243 );
2244 path.line_to(bounds.upper_right() - vec2f(bounds.height(), 0.));
2245 path.curve_to(bounds.lower_right(), bounds.upper_right());
2246 path.line_to(bounds.lower_left());
2247 cx.scene.push_path(path.build(self.color, None));
2248 }
2249
2250 fn dispatch_event(
2251 &mut self,
2252 _: &gpui::Event,
2253 _: RectF,
2254 _: RectF,
2255 _: &mut Self::LayoutState,
2256 _: &mut Self::PaintState,
2257 _: &mut gpui::EventContext,
2258 ) -> bool {
2259 false
2260 }
2261
2262 fn debug(
2263 &self,
2264 bounds: gpui::geometry::rect::RectF,
2265 _: &Self::LayoutState,
2266 _: &Self::PaintState,
2267 _: &gpui::DebugContext,
2268 ) -> gpui::json::Value {
2269 json::json!({
2270 "type": "AvatarRibbon",
2271 "bounds": bounds.to_json(),
2272 "color": self.color.to_json(),
2273 })
2274 }
2275}
2276
2277impl std::fmt::Debug for OpenPaths {
2278 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2279 f.debug_struct("OpenPaths")
2280 .field("paths", &self.paths)
2281 .finish()
2282 }
2283}
2284
2285fn open(_: &Open, cx: &mut MutableAppContext) {
2286 let mut paths = cx.prompt_for_paths(PathPromptOptions {
2287 files: true,
2288 directories: true,
2289 multiple: true,
2290 });
2291 cx.spawn(|mut cx| async move {
2292 if let Some(paths) = paths.recv().await.flatten() {
2293 cx.update(|cx| cx.dispatch_global_action(OpenPaths { paths }));
2294 }
2295 })
2296 .detach();
2297}
2298
2299pub struct WorkspaceCreated(WeakViewHandle<Workspace>);
2300
2301pub fn open_paths(
2302 abs_paths: &[PathBuf],
2303 app_state: &Arc<AppState>,
2304 cx: &mut MutableAppContext,
2305) -> Task<(
2306 ViewHandle<Workspace>,
2307 Vec<Option<Result<Box<dyn ItemHandle>, Arc<anyhow::Error>>>>,
2308)> {
2309 log::info!("open paths {:?}", abs_paths);
2310
2311 // Open paths in existing workspace if possible
2312 let mut existing = None;
2313 for window_id in cx.window_ids().collect::<Vec<_>>() {
2314 if let Some(workspace_handle) = cx.root_view::<Workspace>(window_id) {
2315 if workspace_handle.update(cx, |workspace, cx| {
2316 if workspace.contains_paths(abs_paths, cx.as_ref()) {
2317 cx.activate_window(window_id);
2318 existing = Some(workspace_handle.clone());
2319 true
2320 } else {
2321 false
2322 }
2323 }) {
2324 break;
2325 }
2326 }
2327 }
2328
2329 let app_state = app_state.clone();
2330 let abs_paths = abs_paths.to_vec();
2331 cx.spawn(|mut cx| async move {
2332 let workspace = if let Some(existing) = existing {
2333 existing
2334 } else {
2335 let contains_directory =
2336 futures::future::join_all(abs_paths.iter().map(|path| app_state.fs.is_file(path)))
2337 .await
2338 .contains(&false);
2339
2340 cx.add_window((app_state.build_window_options)(), |cx| {
2341 let mut workspace = Workspace::new(
2342 Project::local(
2343 app_state.client.clone(),
2344 app_state.user_store.clone(),
2345 app_state.languages.clone(),
2346 app_state.fs.clone(),
2347 cx,
2348 ),
2349 cx,
2350 );
2351 (app_state.initialize_workspace)(&mut workspace, &app_state, cx);
2352 if contains_directory {
2353 workspace.toggle_sidebar_item(
2354 &ToggleSidebarItem {
2355 side: Side::Left,
2356 item_index: 0,
2357 },
2358 cx,
2359 );
2360 }
2361 workspace
2362 })
2363 .1
2364 };
2365
2366 let items = workspace
2367 .update(&mut cx, |workspace, cx| workspace.open_paths(abs_paths, cx))
2368 .await;
2369 (workspace, items)
2370 })
2371}
2372
2373pub fn join_project(
2374 contact: Arc<Contact>,
2375 project_index: usize,
2376 app_state: &Arc<AppState>,
2377 cx: &mut MutableAppContext,
2378) {
2379 let project_id = contact.projects[project_index].id;
2380
2381 for window_id in cx.window_ids().collect::<Vec<_>>() {
2382 if let Some(workspace) = cx.root_view::<Workspace>(window_id) {
2383 if workspace.read(cx).project().read(cx).remote_id() == Some(project_id) {
2384 cx.activate_window(window_id);
2385 return;
2386 }
2387 }
2388 }
2389
2390 cx.add_window((app_state.build_window_options)(), |cx| {
2391 WaitingRoom::new(contact, project_index, app_state.clone(), cx)
2392 });
2393}
2394
2395fn open_new(app_state: &Arc<AppState>, cx: &mut MutableAppContext) {
2396 let (window_id, workspace) = cx.add_window((app_state.build_window_options)(), |cx| {
2397 let mut workspace = Workspace::new(
2398 Project::local(
2399 app_state.client.clone(),
2400 app_state.user_store.clone(),
2401 app_state.languages.clone(),
2402 app_state.fs.clone(),
2403 cx,
2404 ),
2405 cx,
2406 );
2407 (app_state.initialize_workspace)(&mut workspace, app_state, cx);
2408 workspace
2409 });
2410 cx.dispatch_action(window_id, vec![workspace.id()], &NewFile);
2411}
2412
2413#[cfg(test)]
2414mod tests {
2415 use super::*;
2416 use gpui::{ModelHandle, TestAppContext, ViewContext};
2417 use project::{FakeFs, Project, ProjectEntryId};
2418 use serde_json::json;
2419
2420 #[gpui::test]
2421 async fn test_close_window(cx: &mut TestAppContext) {
2422 cx.foreground().forbid_parking();
2423 Settings::test_async(cx);
2424 let fs = FakeFs::new(cx.background());
2425 fs.insert_tree("/root", json!({ "one": "" })).await;
2426
2427 let project = Project::test(fs, ["root".as_ref()], cx).await;
2428 let (window_id, workspace) = cx.add_window(|cx| Workspace::new(project.clone(), cx));
2429
2430 // When there are no dirty items, there's nothing to do.
2431 let item1 = cx.add_view(window_id, |_| TestItem::new());
2432 workspace.update(cx, |w, cx| w.add_item(Box::new(item1.clone()), cx));
2433 let task = workspace.update(cx, |w, cx| w.prepare_to_close(cx));
2434 assert_eq!(task.await.unwrap(), true);
2435
2436 // When there are dirty untitled items, prompt to save each one. If the user
2437 // cancels any prompt, then abort.
2438 let item2 = cx.add_view(window_id, |_| {
2439 let mut item = TestItem::new();
2440 item.is_dirty = true;
2441 item
2442 });
2443 let item3 = cx.add_view(window_id, |_| {
2444 let mut item = TestItem::new();
2445 item.is_dirty = true;
2446 item.project_entry_ids = vec![ProjectEntryId::from_proto(1)];
2447 item
2448 });
2449 workspace.update(cx, |w, cx| {
2450 w.add_item(Box::new(item2.clone()), cx);
2451 w.add_item(Box::new(item3.clone()), cx);
2452 });
2453 let task = workspace.update(cx, |w, cx| w.prepare_to_close(cx));
2454 cx.foreground().run_until_parked();
2455 cx.simulate_prompt_answer(window_id, 2 /* cancel */);
2456 cx.foreground().run_until_parked();
2457 assert!(!cx.has_pending_prompt(window_id));
2458 assert_eq!(task.await.unwrap(), false);
2459
2460 // If there are multiple dirty items representing the same project entry.
2461 workspace.update(cx, |w, cx| {
2462 w.add_item(Box::new(item2.clone()), cx);
2463 w.add_item(Box::new(item3.clone()), cx);
2464 });
2465 let task = workspace.update(cx, |w, cx| w.prepare_to_close(cx));
2466 cx.foreground().run_until_parked();
2467 cx.simulate_prompt_answer(window_id, 2 /* cancel */);
2468 cx.foreground().run_until_parked();
2469 assert!(!cx.has_pending_prompt(window_id));
2470 assert_eq!(task.await.unwrap(), false);
2471 }
2472
2473 #[gpui::test]
2474 async fn test_close_pane_items(cx: &mut TestAppContext) {
2475 cx.foreground().forbid_parking();
2476 Settings::test_async(cx);
2477 let fs = FakeFs::new(cx.background());
2478
2479 let project = Project::test(fs, None, cx).await;
2480 let (window_id, workspace) = cx.add_window(|cx| Workspace::new(project, cx));
2481
2482 let item1 = cx.add_view(window_id, |_| {
2483 let mut item = TestItem::new();
2484 item.is_dirty = true;
2485 item.project_entry_ids = vec![ProjectEntryId::from_proto(1)];
2486 item
2487 });
2488 let item2 = cx.add_view(window_id, |_| {
2489 let mut item = TestItem::new();
2490 item.is_dirty = true;
2491 item.has_conflict = true;
2492 item.project_entry_ids = vec![ProjectEntryId::from_proto(2)];
2493 item
2494 });
2495 let item3 = cx.add_view(window_id, |_| {
2496 let mut item = TestItem::new();
2497 item.is_dirty = true;
2498 item.has_conflict = true;
2499 item.project_entry_ids = vec![ProjectEntryId::from_proto(3)];
2500 item
2501 });
2502 let item4 = cx.add_view(window_id, |_| {
2503 let mut item = TestItem::new();
2504 item.is_dirty = true;
2505 item
2506 });
2507 let pane = workspace.update(cx, |workspace, cx| {
2508 workspace.add_item(Box::new(item1.clone()), cx);
2509 workspace.add_item(Box::new(item2.clone()), cx);
2510 workspace.add_item(Box::new(item3.clone()), cx);
2511 workspace.add_item(Box::new(item4.clone()), cx);
2512 workspace.active_pane().clone()
2513 });
2514
2515 let close_items = workspace.update(cx, |workspace, cx| {
2516 pane.update(cx, |pane, cx| {
2517 pane.activate_item(1, true, true, cx);
2518 assert_eq!(pane.active_item().unwrap().id(), item2.id());
2519 });
2520
2521 let item1_id = item1.id();
2522 let item3_id = item3.id();
2523 let item4_id = item4.id();
2524 Pane::close_items(workspace, pane.clone(), cx, move |id| {
2525 [item1_id, item3_id, item4_id].contains(&id)
2526 })
2527 });
2528
2529 cx.foreground().run_until_parked();
2530 pane.read_with(cx, |pane, _| {
2531 assert_eq!(pane.items().count(), 4);
2532 assert_eq!(pane.active_item().unwrap().id(), item1.id());
2533 });
2534
2535 cx.simulate_prompt_answer(window_id, 0);
2536 cx.foreground().run_until_parked();
2537 pane.read_with(cx, |pane, cx| {
2538 assert_eq!(item1.read(cx).save_count, 1);
2539 assert_eq!(item1.read(cx).save_as_count, 0);
2540 assert_eq!(item1.read(cx).reload_count, 0);
2541 assert_eq!(pane.items().count(), 3);
2542 assert_eq!(pane.active_item().unwrap().id(), item3.id());
2543 });
2544
2545 cx.simulate_prompt_answer(window_id, 1);
2546 cx.foreground().run_until_parked();
2547 pane.read_with(cx, |pane, cx| {
2548 assert_eq!(item3.read(cx).save_count, 0);
2549 assert_eq!(item3.read(cx).save_as_count, 0);
2550 assert_eq!(item3.read(cx).reload_count, 1);
2551 assert_eq!(pane.items().count(), 2);
2552 assert_eq!(pane.active_item().unwrap().id(), item4.id());
2553 });
2554
2555 cx.simulate_prompt_answer(window_id, 0);
2556 cx.foreground().run_until_parked();
2557 cx.simulate_new_path_selection(|_| Some(Default::default()));
2558 close_items.await.unwrap();
2559 pane.read_with(cx, |pane, cx| {
2560 assert_eq!(item4.read(cx).save_count, 0);
2561 assert_eq!(item4.read(cx).save_as_count, 1);
2562 assert_eq!(item4.read(cx).reload_count, 0);
2563 assert_eq!(pane.items().count(), 1);
2564 assert_eq!(pane.active_item().unwrap().id(), item2.id());
2565 });
2566 }
2567
2568 #[gpui::test]
2569 async fn test_prompting_to_save_only_on_last_item_for_entry(cx: &mut TestAppContext) {
2570 cx.foreground().forbid_parking();
2571 Settings::test_async(cx);
2572 let fs = FakeFs::new(cx.background());
2573
2574 let project = Project::test(fs, [], cx).await;
2575 let (window_id, workspace) = cx.add_window(|cx| Workspace::new(project, cx));
2576
2577 // Create several workspace items with single project entries, and two
2578 // workspace items with multiple project entries.
2579 let single_entry_items = (0..=4)
2580 .map(|project_entry_id| {
2581 let mut item = TestItem::new();
2582 item.is_dirty = true;
2583 item.project_entry_ids = vec![ProjectEntryId::from_proto(project_entry_id)];
2584 item.is_singleton = true;
2585 item
2586 })
2587 .collect::<Vec<_>>();
2588 let item_2_3 = {
2589 let mut item = TestItem::new();
2590 item.is_dirty = true;
2591 item.is_singleton = false;
2592 item.project_entry_ids =
2593 vec![ProjectEntryId::from_proto(2), ProjectEntryId::from_proto(3)];
2594 item
2595 };
2596 let item_3_4 = {
2597 let mut item = TestItem::new();
2598 item.is_dirty = true;
2599 item.is_singleton = false;
2600 item.project_entry_ids =
2601 vec![ProjectEntryId::from_proto(3), ProjectEntryId::from_proto(4)];
2602 item
2603 };
2604
2605 // Create two panes that contain the following project entries:
2606 // left pane:
2607 // multi-entry items: (2, 3)
2608 // single-entry items: 0, 1, 2, 3, 4
2609 // right pane:
2610 // single-entry items: 1
2611 // multi-entry items: (3, 4)
2612 let left_pane = workspace.update(cx, |workspace, cx| {
2613 let left_pane = workspace.active_pane().clone();
2614 let right_pane = workspace.split_pane(left_pane.clone(), SplitDirection::Right, cx);
2615
2616 workspace.activate_pane(left_pane.clone(), cx);
2617 workspace.add_item(Box::new(cx.add_view(|_| item_2_3.clone())), cx);
2618 for item in &single_entry_items {
2619 workspace.add_item(Box::new(cx.add_view(|_| item.clone())), cx);
2620 }
2621
2622 workspace.activate_pane(right_pane.clone(), cx);
2623 workspace.add_item(Box::new(cx.add_view(|_| single_entry_items[1].clone())), cx);
2624 workspace.add_item(Box::new(cx.add_view(|_| item_3_4.clone())), cx);
2625
2626 left_pane
2627 });
2628
2629 // When closing all of the items in the left pane, we should be prompted twice:
2630 // once for project entry 0, and once for project entry 2. After those two
2631 // prompts, the task should complete.
2632 let close = workspace.update(cx, |workspace, cx| {
2633 workspace.activate_pane(left_pane.clone(), cx);
2634 Pane::close_items(workspace, left_pane.clone(), cx, |_| true)
2635 });
2636
2637 cx.foreground().run_until_parked();
2638 left_pane.read_with(cx, |pane, cx| {
2639 assert_eq!(
2640 pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
2641 &[ProjectEntryId::from_proto(0)]
2642 );
2643 });
2644 cx.simulate_prompt_answer(window_id, 0);
2645
2646 cx.foreground().run_until_parked();
2647 left_pane.read_with(cx, |pane, cx| {
2648 assert_eq!(
2649 pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
2650 &[ProjectEntryId::from_proto(2)]
2651 );
2652 });
2653 cx.simulate_prompt_answer(window_id, 0);
2654
2655 cx.foreground().run_until_parked();
2656 close.await.unwrap();
2657 left_pane.read_with(cx, |pane, _| {
2658 assert_eq!(pane.items().count(), 0);
2659 });
2660 }
2661
2662 #[derive(Clone)]
2663 struct TestItem {
2664 save_count: usize,
2665 save_as_count: usize,
2666 reload_count: usize,
2667 is_dirty: bool,
2668 has_conflict: bool,
2669 project_entry_ids: Vec<ProjectEntryId>,
2670 is_singleton: bool,
2671 }
2672
2673 impl TestItem {
2674 fn new() -> Self {
2675 Self {
2676 save_count: 0,
2677 save_as_count: 0,
2678 reload_count: 0,
2679 is_dirty: false,
2680 has_conflict: false,
2681 project_entry_ids: Vec::new(),
2682 is_singleton: true,
2683 }
2684 }
2685 }
2686
2687 impl Entity for TestItem {
2688 type Event = ();
2689 }
2690
2691 impl View for TestItem {
2692 fn ui_name() -> &'static str {
2693 "TestItem"
2694 }
2695
2696 fn render(&mut self, _: &mut RenderContext<Self>) -> ElementBox {
2697 Empty::new().boxed()
2698 }
2699 }
2700
2701 impl Item for TestItem {
2702 fn tab_content(&self, _: &theme::Tab, _: &AppContext) -> ElementBox {
2703 Empty::new().boxed()
2704 }
2705
2706 fn project_path(&self, _: &AppContext) -> Option<ProjectPath> {
2707 None
2708 }
2709
2710 fn project_entry_ids(&self, _: &AppContext) -> SmallVec<[ProjectEntryId; 3]> {
2711 self.project_entry_ids.iter().copied().collect()
2712 }
2713
2714 fn is_singleton(&self, _: &AppContext) -> bool {
2715 self.is_singleton
2716 }
2717
2718 fn set_nav_history(&mut self, _: ItemNavHistory, _: &mut ViewContext<Self>) {}
2719
2720 fn clone_on_split(&self, _: &mut ViewContext<Self>) -> Option<Self>
2721 where
2722 Self: Sized,
2723 {
2724 Some(self.clone())
2725 }
2726
2727 fn is_dirty(&self, _: &AppContext) -> bool {
2728 self.is_dirty
2729 }
2730
2731 fn has_conflict(&self, _: &AppContext) -> bool {
2732 self.has_conflict
2733 }
2734
2735 fn can_save(&self, _: &AppContext) -> bool {
2736 self.project_entry_ids.len() > 0
2737 }
2738
2739 fn save(
2740 &mut self,
2741 _: ModelHandle<Project>,
2742 _: &mut ViewContext<Self>,
2743 ) -> Task<anyhow::Result<()>> {
2744 self.save_count += 1;
2745 Task::ready(Ok(()))
2746 }
2747
2748 fn save_as(
2749 &mut self,
2750 _: ModelHandle<Project>,
2751 _: std::path::PathBuf,
2752 _: &mut ViewContext<Self>,
2753 ) -> Task<anyhow::Result<()>> {
2754 self.save_as_count += 1;
2755 Task::ready(Ok(()))
2756 }
2757
2758 fn reload(
2759 &mut self,
2760 _: ModelHandle<Project>,
2761 _: &mut ViewContext<Self>,
2762 ) -> Task<anyhow::Result<()>> {
2763 self.reload_count += 1;
2764 Task::ready(Ok(()))
2765 }
2766 }
2767}