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