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