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!(ToggleFollow, 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::toggle_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 toggle_follow(
1215 &mut self,
1216 ToggleFollow(leader_id): &ToggleFollow,
1217 cx: &mut ViewContext<Self>,
1218 ) -> Option<Task<Result<()>>> {
1219 let leader_id = *leader_id;
1220 let pane = self.active_pane().clone();
1221
1222 if let Some(prev_leader_id) = self.unfollow(&pane, cx) {
1223 if leader_id == prev_leader_id {
1224 cx.notify();
1225 return None;
1226 }
1227 }
1228
1229 self.follower_states_by_leader
1230 .entry(leader_id)
1231 .or_default()
1232 .insert(pane.clone(), Default::default());
1233
1234 let project_id = self.project.read(cx).remote_id()?;
1235 let request = self.client.request(proto::Follow {
1236 project_id,
1237 leader_id: leader_id.0,
1238 });
1239 Some(cx.spawn_weak(|this, mut cx| async move {
1240 let response = request.await?;
1241 if let Some(this) = this.upgrade(&cx) {
1242 this.update(&mut cx, |this, _| {
1243 let state = this
1244 .follower_states_by_leader
1245 .get_mut(&leader_id)
1246 .and_then(|states_by_pane| states_by_pane.get_mut(&pane))
1247 .ok_or_else(|| anyhow!("following interrupted"))?;
1248 state.active_view_id = response.active_view_id;
1249 Ok::<_, anyhow::Error>(())
1250 })?;
1251 Self::add_views_from_leader(this, leader_id, vec![pane], response.views, &mut cx)
1252 .await?;
1253 }
1254 Ok(())
1255 }))
1256 }
1257
1258 pub fn unfollow(
1259 &mut self,
1260 pane: &ViewHandle<Pane>,
1261 cx: &mut ViewContext<Self>,
1262 ) -> Option<PeerId> {
1263 for (leader_id, states_by_pane) in &mut self.follower_states_by_leader {
1264 if let Some(state) = states_by_pane.remove(&pane) {
1265 for (_, item) in state.items_by_leader_view_id {
1266 if let FollowerItem::Loaded(item) = item {
1267 item.set_following(false, cx);
1268 }
1269 }
1270
1271 if states_by_pane.is_empty() {
1272 if let Some(project_id) = self.project.read(cx).remote_id() {
1273 self.client
1274 .send(proto::Unfollow {
1275 project_id,
1276 leader_id: leader_id.0,
1277 })
1278 .log_err();
1279 }
1280 }
1281
1282 cx.notify();
1283 return Some(*leader_id);
1284 }
1285 }
1286 None
1287 }
1288
1289 fn render_connection_status(&self, cx: &mut RenderContext<Self>) -> Option<ElementBox> {
1290 let theme = &cx.global::<Settings>().theme;
1291 match &*self.client.status().borrow() {
1292 client::Status::ConnectionError
1293 | client::Status::ConnectionLost
1294 | client::Status::Reauthenticating
1295 | client::Status::Reconnecting { .. }
1296 | client::Status::ReconnectionError { .. } => Some(
1297 Container::new(
1298 Align::new(
1299 ConstrainedBox::new(
1300 Svg::new("icons/offline-14.svg")
1301 .with_color(theme.workspace.titlebar.offline_icon.color)
1302 .boxed(),
1303 )
1304 .with_width(theme.workspace.titlebar.offline_icon.width)
1305 .boxed(),
1306 )
1307 .boxed(),
1308 )
1309 .with_style(theme.workspace.titlebar.offline_icon.container)
1310 .boxed(),
1311 ),
1312 client::Status::UpgradeRequired => Some(
1313 Label::new(
1314 "Please update Zed to collaborate".to_string(),
1315 theme.workspace.titlebar.outdated_warning.text.clone(),
1316 )
1317 .contained()
1318 .with_style(theme.workspace.titlebar.outdated_warning.container)
1319 .aligned()
1320 .boxed(),
1321 ),
1322 _ => None,
1323 }
1324 }
1325
1326 fn render_titlebar(&self, theme: &Theme, cx: &mut RenderContext<Self>) -> ElementBox {
1327 ConstrainedBox::new(
1328 Container::new(
1329 Stack::new()
1330 .with_child(
1331 Align::new(
1332 Label::new("zed".into(), theme.workspace.titlebar.title.clone())
1333 .boxed(),
1334 )
1335 .boxed(),
1336 )
1337 .with_child(
1338 Align::new(
1339 Flex::row()
1340 .with_children(self.render_share_icon(theme, cx))
1341 .with_children(self.render_collaborators(theme, cx))
1342 .with_child(self.render_current_user(
1343 self.user_store.read(cx).current_user().as_ref(),
1344 self.project.read(cx).replica_id(),
1345 theme,
1346 cx,
1347 ))
1348 .with_children(self.render_connection_status(cx))
1349 .boxed(),
1350 )
1351 .right()
1352 .boxed(),
1353 )
1354 .boxed(),
1355 )
1356 .with_style(theme.workspace.titlebar.container)
1357 .boxed(),
1358 )
1359 .with_height(theme.workspace.titlebar.height)
1360 .named("titlebar")
1361 }
1362
1363 fn render_collaborators(&self, theme: &Theme, cx: &mut RenderContext<Self>) -> Vec<ElementBox> {
1364 let mut collaborators = self
1365 .project
1366 .read(cx)
1367 .collaborators()
1368 .values()
1369 .cloned()
1370 .collect::<Vec<_>>();
1371 collaborators.sort_unstable_by_key(|collaborator| collaborator.replica_id);
1372 collaborators
1373 .into_iter()
1374 .filter_map(|collaborator| {
1375 Some(self.render_avatar(
1376 collaborator.user.avatar.clone()?,
1377 collaborator.replica_id,
1378 Some(collaborator.peer_id),
1379 theme,
1380 cx,
1381 ))
1382 })
1383 .collect()
1384 }
1385
1386 fn render_current_user(
1387 &self,
1388 user: Option<&Arc<User>>,
1389 replica_id: ReplicaId,
1390 theme: &Theme,
1391 cx: &mut RenderContext<Self>,
1392 ) -> ElementBox {
1393 if let Some(avatar) = user.and_then(|user| user.avatar.clone()) {
1394 self.render_avatar(avatar, replica_id, None, theme, cx)
1395 } else {
1396 MouseEventHandler::new::<Authenticate, _, _>(0, cx, |state, _| {
1397 let style = if state.hovered {
1398 &theme.workspace.titlebar.hovered_sign_in_prompt
1399 } else {
1400 &theme.workspace.titlebar.sign_in_prompt
1401 };
1402 Label::new("Sign in".to_string(), style.text.clone())
1403 .contained()
1404 .with_style(style.container)
1405 .boxed()
1406 })
1407 .on_click(|cx| cx.dispatch_action(Authenticate))
1408 .with_cursor_style(CursorStyle::PointingHand)
1409 .aligned()
1410 .boxed()
1411 }
1412 }
1413
1414 fn render_avatar(
1415 &self,
1416 avatar: Arc<ImageData>,
1417 replica_id: ReplicaId,
1418 peer_id: Option<PeerId>,
1419 theme: &Theme,
1420 cx: &mut RenderContext<Self>,
1421 ) -> ElementBox {
1422 let content = Stack::new()
1423 .with_child(
1424 Image::new(avatar)
1425 .with_style(theme.workspace.titlebar.avatar)
1426 .constrained()
1427 .with_width(theme.workspace.titlebar.avatar_width)
1428 .aligned()
1429 .boxed(),
1430 )
1431 .with_child(
1432 AvatarRibbon::new(theme.editor.replica_selection_style(replica_id).cursor)
1433 .constrained()
1434 .with_width(theme.workspace.titlebar.avatar_ribbon.width)
1435 .with_height(theme.workspace.titlebar.avatar_ribbon.height)
1436 .aligned()
1437 .bottom()
1438 .boxed(),
1439 )
1440 .constrained()
1441 .with_width(theme.workspace.right_sidebar.width)
1442 .boxed();
1443
1444 if let Some(peer_id) = peer_id {
1445 MouseEventHandler::new::<ToggleFollow, _, _>(replica_id.into(), cx, move |_, _| content)
1446 .with_cursor_style(CursorStyle::PointingHand)
1447 .on_click(move |cx| cx.dispatch_action(ToggleFollow(peer_id)))
1448 .boxed()
1449 } else {
1450 content
1451 }
1452 }
1453
1454 fn render_share_icon(&self, theme: &Theme, cx: &mut RenderContext<Self>) -> Option<ElementBox> {
1455 if self.project().read(cx).is_local() && self.client.user_id().is_some() {
1456 let color = if self.project().read(cx).is_shared() {
1457 theme.workspace.titlebar.share_icon_active_color
1458 } else {
1459 theme.workspace.titlebar.share_icon_color
1460 };
1461 Some(
1462 MouseEventHandler::new::<ToggleShare, _, _>(0, cx, |_, _| {
1463 Align::new(
1464 Svg::new("icons/broadcast-24.svg")
1465 .with_color(color)
1466 .constrained()
1467 .with_width(24.)
1468 .boxed(),
1469 )
1470 .boxed()
1471 })
1472 .with_cursor_style(CursorStyle::PointingHand)
1473 .on_click(|cx| cx.dispatch_action(ToggleShare))
1474 .boxed(),
1475 )
1476 } else {
1477 None
1478 }
1479 }
1480
1481 fn render_disconnected_overlay(&self, cx: &AppContext) -> Option<ElementBox> {
1482 if self.project.read(cx).is_read_only() {
1483 let theme = &cx.global::<Settings>().theme;
1484 Some(
1485 EventHandler::new(
1486 Label::new(
1487 "Your connection to the remote project has been lost.".to_string(),
1488 theme.workspace.disconnected_overlay.text.clone(),
1489 )
1490 .aligned()
1491 .contained()
1492 .with_style(theme.workspace.disconnected_overlay.container)
1493 .boxed(),
1494 )
1495 .capture(|_, _, _| true)
1496 .boxed(),
1497 )
1498 } else {
1499 None
1500 }
1501 }
1502
1503 // RPC handlers
1504
1505 async fn handle_follow(
1506 this: ViewHandle<Self>,
1507 envelope: TypedEnvelope<proto::Follow>,
1508 _: Arc<Client>,
1509 mut cx: AsyncAppContext,
1510 ) -> Result<proto::FollowResponse> {
1511 this.update(&mut cx, |this, cx| {
1512 this.leader_state
1513 .followers
1514 .insert(envelope.original_sender_id()?);
1515
1516 let active_view_id = this
1517 .active_item(cx)
1518 .and_then(|i| i.to_followable_item_handle(cx))
1519 .map(|i| i.id() as u64);
1520 Ok(proto::FollowResponse {
1521 active_view_id,
1522 views: this
1523 .items(cx)
1524 .filter_map(|item| {
1525 let id = item.id() as u64;
1526 let item = item.to_followable_item_handle(cx)?;
1527 let variant = item.to_state_message(cx)?;
1528 Some(proto::View {
1529 id,
1530 variant: Some(variant),
1531 })
1532 })
1533 .collect(),
1534 })
1535 })
1536 }
1537
1538 async fn handle_unfollow(
1539 this: ViewHandle<Self>,
1540 envelope: TypedEnvelope<proto::Unfollow>,
1541 _: Arc<Client>,
1542 mut cx: AsyncAppContext,
1543 ) -> Result<()> {
1544 this.update(&mut cx, |this, _| {
1545 this.leader_state
1546 .followers
1547 .remove(&envelope.original_sender_id()?);
1548 Ok(())
1549 })
1550 }
1551
1552 async fn handle_update_followers(
1553 this: ViewHandle<Self>,
1554 envelope: TypedEnvelope<proto::UpdateFollowers>,
1555 _: Arc<Client>,
1556 mut cx: AsyncAppContext,
1557 ) -> Result<()> {
1558 let leader_id = envelope.original_sender_id()?;
1559 match envelope
1560 .payload
1561 .variant
1562 .ok_or_else(|| anyhow!("invalid update"))?
1563 {
1564 proto::update_followers::Variant::UpdateActiveView(update_active_view) => {
1565 this.update(&mut cx, |this, cx| {
1566 this.update_leader_state(leader_id, cx, |state, _| {
1567 state.active_view_id = update_active_view.id;
1568 });
1569 Ok::<_, anyhow::Error>(())
1570 })
1571 }
1572 proto::update_followers::Variant::UpdateView(update_view) => {
1573 this.update(&mut cx, |this, cx| {
1574 let variant = update_view
1575 .variant
1576 .ok_or_else(|| anyhow!("missing update view variant"))?;
1577 this.update_leader_state(leader_id, cx, |state, cx| {
1578 let variant = variant.clone();
1579 match state
1580 .items_by_leader_view_id
1581 .entry(update_view.id)
1582 .or_insert(FollowerItem::Loading(Vec::new()))
1583 {
1584 FollowerItem::Loaded(item) => {
1585 item.apply_update_message(variant, cx).log_err();
1586 }
1587 FollowerItem::Loading(updates) => updates.push(variant),
1588 }
1589 });
1590 Ok(())
1591 })
1592 }
1593 proto::update_followers::Variant::CreateView(view) => {
1594 let panes = this.read_with(&cx, |this, _| {
1595 this.follower_states_by_leader
1596 .get(&leader_id)
1597 .into_iter()
1598 .flat_map(|states_by_pane| states_by_pane.keys())
1599 .cloned()
1600 .collect()
1601 });
1602 Self::add_views_from_leader(this.clone(), leader_id, panes, vec![view], &mut cx)
1603 .await?;
1604 Ok(())
1605 }
1606 }
1607 .log_err();
1608
1609 Ok(())
1610 }
1611
1612 async fn add_views_from_leader(
1613 this: ViewHandle<Self>,
1614 leader_id: PeerId,
1615 panes: Vec<ViewHandle<Pane>>,
1616 views: Vec<proto::View>,
1617 cx: &mut AsyncAppContext,
1618 ) -> Result<()> {
1619 let project = this.read_with(cx, |this, _| this.project.clone());
1620
1621 let item_builders = cx.update(|cx| {
1622 cx.default_global::<FollowableItemBuilders>()
1623 .values()
1624 .map(|b| b.0)
1625 .collect::<Vec<_>>()
1626 .clone()
1627 });
1628
1629 let mut item_tasks_by_pane = HashMap::default();
1630 for pane in panes {
1631 let mut item_tasks = Vec::new();
1632 let mut leader_view_ids = Vec::new();
1633 for view in &views {
1634 let mut variant = view.variant.clone();
1635 if variant.is_none() {
1636 Err(anyhow!("missing variant"))?;
1637 }
1638 for build_item in &item_builders {
1639 let task =
1640 cx.update(|cx| build_item(pane.clone(), project.clone(), &mut variant, cx));
1641 if let Some(task) = task {
1642 item_tasks.push(task);
1643 leader_view_ids.push(view.id);
1644 break;
1645 } else {
1646 assert!(variant.is_some());
1647 }
1648 }
1649 }
1650
1651 item_tasks_by_pane.insert(pane, (item_tasks, leader_view_ids));
1652 }
1653
1654 for (pane, (item_tasks, leader_view_ids)) in item_tasks_by_pane {
1655 let items = futures::future::try_join_all(item_tasks).await?;
1656 this.update(cx, |this, cx| {
1657 let state = this
1658 .follower_states_by_leader
1659 .get_mut(&leader_id)?
1660 .get_mut(&pane)?;
1661
1662 for (id, item) in leader_view_ids.into_iter().zip(items) {
1663 item.set_following(true, cx);
1664 match state.items_by_leader_view_id.entry(id) {
1665 hash_map::Entry::Occupied(e) => {
1666 let e = e.into_mut();
1667 if let FollowerItem::Loading(updates) = e {
1668 for update in updates.drain(..) {
1669 item.apply_update_message(update, cx)
1670 .context("failed to apply view update")
1671 .log_err();
1672 }
1673 }
1674 *e = FollowerItem::Loaded(item);
1675 }
1676 hash_map::Entry::Vacant(e) => {
1677 e.insert(FollowerItem::Loaded(item));
1678 }
1679 }
1680 }
1681
1682 Some(())
1683 });
1684 }
1685 this.update(cx, |this, cx| this.leader_updated(leader_id, cx));
1686
1687 Ok(())
1688 }
1689
1690 fn update_followers(
1691 &self,
1692 update: proto::update_followers::Variant,
1693 cx: &AppContext,
1694 ) -> Option<()> {
1695 let project_id = self.project.read(cx).remote_id()?;
1696 if !self.leader_state.followers.is_empty() {
1697 self.client
1698 .send(proto::UpdateFollowers {
1699 project_id,
1700 follower_ids: self.leader_state.followers.iter().map(|f| f.0).collect(),
1701 variant: Some(update),
1702 })
1703 .log_err();
1704 }
1705 None
1706 }
1707
1708 fn update_leader_state(
1709 &mut self,
1710 leader_id: PeerId,
1711 cx: &mut ViewContext<Self>,
1712 mut update_fn: impl FnMut(&mut FollowerState, &mut ViewContext<Self>),
1713 ) {
1714 for (_, state) in self
1715 .follower_states_by_leader
1716 .get_mut(&leader_id)
1717 .into_iter()
1718 .flatten()
1719 {
1720 update_fn(state, cx);
1721 }
1722 self.leader_updated(leader_id, cx);
1723 }
1724
1725 fn leader_updated(&mut self, leader_id: PeerId, cx: &mut ViewContext<Self>) -> Option<()> {
1726 let mut items_to_add = Vec::new();
1727 for (pane, state) in self.follower_states_by_leader.get(&leader_id)? {
1728 if let Some(active_item) = state
1729 .active_view_id
1730 .and_then(|id| state.items_by_leader_view_id.get(&id))
1731 {
1732 if let FollowerItem::Loaded(item) = active_item {
1733 items_to_add.push((pane.clone(), item.boxed_clone()));
1734 }
1735 }
1736 }
1737
1738 for (pane, item) in items_to_add {
1739 Pane::add_item(self, pane.clone(), item.boxed_clone(), cx);
1740 cx.notify();
1741 }
1742 None
1743 }
1744}
1745
1746impl Entity for Workspace {
1747 type Event = ();
1748}
1749
1750impl View for Workspace {
1751 fn ui_name() -> &'static str {
1752 "Workspace"
1753 }
1754
1755 fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
1756 let theme = cx.global::<Settings>().theme.clone();
1757 Stack::new()
1758 .with_child(
1759 Flex::column()
1760 .with_child(self.render_titlebar(&theme, cx))
1761 .with_child(
1762 Stack::new()
1763 .with_child({
1764 let mut content = Flex::row();
1765 content.add_child(self.left_sidebar.render(&theme, cx));
1766 if let Some(element) =
1767 self.left_sidebar.render_active_item(&theme, cx)
1768 {
1769 content.add_child(Flexible::new(0.8, false, element).boxed());
1770 }
1771 content.add_child(
1772 Flex::column()
1773 .with_child(
1774 Flexible::new(1., true, self.center.render(&theme))
1775 .boxed(),
1776 )
1777 .with_child(ChildView::new(&self.status_bar).boxed())
1778 .flexible(1., true)
1779 .boxed(),
1780 );
1781 if let Some(element) =
1782 self.right_sidebar.render_active_item(&theme, cx)
1783 {
1784 content.add_child(Flexible::new(0.8, false, element).boxed());
1785 }
1786 content.add_child(self.right_sidebar.render(&theme, cx));
1787 content.boxed()
1788 })
1789 .with_children(self.modal.as_ref().map(|m| ChildView::new(m).boxed()))
1790 .flexible(1.0, true)
1791 .boxed(),
1792 )
1793 .contained()
1794 .with_background_color(theme.workspace.background)
1795 .boxed(),
1796 )
1797 .with_children(self.render_disconnected_overlay(cx))
1798 .named("workspace")
1799 }
1800
1801 fn on_focus(&mut self, cx: &mut ViewContext<Self>) {
1802 cx.focus(&self.active_pane);
1803 }
1804}
1805
1806pub trait WorkspaceHandle {
1807 fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath>;
1808}
1809
1810impl WorkspaceHandle for ViewHandle<Workspace> {
1811 fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath> {
1812 self.read(cx)
1813 .worktrees(cx)
1814 .flat_map(|worktree| {
1815 let worktree_id = worktree.read(cx).id();
1816 worktree.read(cx).files(true, 0).map(move |f| ProjectPath {
1817 worktree_id,
1818 path: f.path.clone(),
1819 })
1820 })
1821 .collect::<Vec<_>>()
1822 }
1823}
1824
1825pub struct AvatarRibbon {
1826 color: Color,
1827}
1828
1829impl AvatarRibbon {
1830 pub fn new(color: Color) -> AvatarRibbon {
1831 AvatarRibbon { color }
1832 }
1833}
1834
1835impl Element for AvatarRibbon {
1836 type LayoutState = ();
1837
1838 type PaintState = ();
1839
1840 fn layout(
1841 &mut self,
1842 constraint: gpui::SizeConstraint,
1843 _: &mut gpui::LayoutContext,
1844 ) -> (gpui::geometry::vector::Vector2F, Self::LayoutState) {
1845 (constraint.max, ())
1846 }
1847
1848 fn paint(
1849 &mut self,
1850 bounds: gpui::geometry::rect::RectF,
1851 _: gpui::geometry::rect::RectF,
1852 _: &mut Self::LayoutState,
1853 cx: &mut gpui::PaintContext,
1854 ) -> Self::PaintState {
1855 let mut path = PathBuilder::new();
1856 path.reset(bounds.lower_left());
1857 path.curve_to(
1858 bounds.origin() + vec2f(bounds.height(), 0.),
1859 bounds.origin(),
1860 );
1861 path.line_to(bounds.upper_right() - vec2f(bounds.height(), 0.));
1862 path.curve_to(bounds.lower_right(), bounds.upper_right());
1863 path.line_to(bounds.lower_left());
1864 cx.scene.push_path(path.build(self.color, None));
1865 }
1866
1867 fn dispatch_event(
1868 &mut self,
1869 _: &gpui::Event,
1870 _: gpui::geometry::rect::RectF,
1871 _: &mut Self::LayoutState,
1872 _: &mut Self::PaintState,
1873 _: &mut gpui::EventContext,
1874 ) -> bool {
1875 false
1876 }
1877
1878 fn debug(
1879 &self,
1880 bounds: gpui::geometry::rect::RectF,
1881 _: &Self::LayoutState,
1882 _: &Self::PaintState,
1883 _: &gpui::DebugContext,
1884 ) -> gpui::json::Value {
1885 json::json!({
1886 "type": "AvatarRibbon",
1887 "bounds": bounds.to_json(),
1888 "color": self.color.to_json(),
1889 })
1890 }
1891}
1892
1893impl std::fmt::Debug for OpenParams {
1894 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1895 f.debug_struct("OpenParams")
1896 .field("paths", &self.paths)
1897 .finish()
1898 }
1899}
1900
1901fn open(action: &Open, cx: &mut MutableAppContext) {
1902 let app_state = action.0.clone();
1903 let mut paths = cx.prompt_for_paths(PathPromptOptions {
1904 files: true,
1905 directories: true,
1906 multiple: true,
1907 });
1908 cx.spawn(|mut cx| async move {
1909 if let Some(paths) = paths.recv().await.flatten() {
1910 cx.update(|cx| cx.dispatch_global_action(OpenPaths(OpenParams { paths, app_state })));
1911 }
1912 })
1913 .detach();
1914}
1915
1916pub struct WorkspaceCreated(WeakViewHandle<Workspace>);
1917
1918pub fn open_paths(
1919 abs_paths: &[PathBuf],
1920 app_state: &Arc<AppState>,
1921 cx: &mut MutableAppContext,
1922) -> Task<ViewHandle<Workspace>> {
1923 log::info!("open paths {:?}", abs_paths);
1924
1925 // Open paths in existing workspace if possible
1926 let mut existing = None;
1927 for window_id in cx.window_ids().collect::<Vec<_>>() {
1928 if let Some(workspace_handle) = cx.root_view::<Workspace>(window_id) {
1929 if workspace_handle.update(cx, |workspace, cx| {
1930 if workspace.contains_paths(abs_paths, cx.as_ref()) {
1931 cx.activate_window(window_id);
1932 existing = Some(workspace_handle.clone());
1933 true
1934 } else {
1935 false
1936 }
1937 }) {
1938 break;
1939 }
1940 }
1941 }
1942
1943 let workspace = existing.unwrap_or_else(|| {
1944 cx.add_window((app_state.build_window_options)(), |cx| {
1945 let project = Project::local(
1946 app_state.client.clone(),
1947 app_state.user_store.clone(),
1948 app_state.languages.clone(),
1949 app_state.fs.clone(),
1950 cx,
1951 );
1952 (app_state.build_workspace)(project, &app_state, cx)
1953 })
1954 .1
1955 });
1956
1957 let task = workspace.update(cx, |workspace, cx| workspace.open_paths(abs_paths, cx));
1958 cx.spawn(|_| async move {
1959 task.await;
1960 workspace
1961 })
1962}
1963
1964pub fn join_project(
1965 project_id: u64,
1966 app_state: &Arc<AppState>,
1967 cx: &mut MutableAppContext,
1968) -> Task<Result<ViewHandle<Workspace>>> {
1969 for window_id in cx.window_ids().collect::<Vec<_>>() {
1970 if let Some(workspace) = cx.root_view::<Workspace>(window_id) {
1971 if workspace.read(cx).project().read(cx).remote_id() == Some(project_id) {
1972 return Task::ready(Ok(workspace));
1973 }
1974 }
1975 }
1976
1977 let app_state = app_state.clone();
1978 cx.spawn(|mut cx| async move {
1979 let project = Project::remote(
1980 project_id,
1981 app_state.client.clone(),
1982 app_state.user_store.clone(),
1983 app_state.languages.clone(),
1984 app_state.fs.clone(),
1985 &mut cx,
1986 )
1987 .await?;
1988 Ok(cx.update(|cx| {
1989 cx.add_window((app_state.build_window_options)(), |cx| {
1990 (app_state.build_workspace)(project, &app_state, cx)
1991 })
1992 .1
1993 }))
1994 })
1995}
1996
1997fn open_new(app_state: &Arc<AppState>, cx: &mut MutableAppContext) {
1998 let (window_id, workspace) = cx.add_window((app_state.build_window_options)(), |cx| {
1999 let project = Project::local(
2000 app_state.client.clone(),
2001 app_state.user_store.clone(),
2002 app_state.languages.clone(),
2003 app_state.fs.clone(),
2004 cx,
2005 );
2006 (app_state.build_workspace)(project, &app_state, cx)
2007 });
2008 cx.dispatch_action(window_id, vec![workspace.id()], &OpenNew(app_state.clone()));
2009}