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