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