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 match event {
671 project::Event::RemoteIdChanged(remote_id) => {
672 this.project_remote_id_changed(*remote_id, cx);
673 }
674 project::Event::CollaboratorLeft(peer_id) => {
675 this.collaborator_left(*peer_id, cx);
676 }
677 _ => {}
678 }
679 if project.read(cx).is_read_only() {
680 cx.blur();
681 }
682 cx.notify()
683 })
684 .detach();
685
686 let pane = cx.add_view(|_| Pane::new());
687 let pane_id = pane.id();
688 cx.observe(&pane, move |me, _, cx| {
689 let active_entry = me.active_project_path(cx);
690 me.project
691 .update(cx, |project, cx| project.set_active_path(active_entry, cx));
692 })
693 .detach();
694 cx.subscribe(&pane, move |me, _, event, cx| {
695 me.handle_pane_event(pane_id, event, cx)
696 })
697 .detach();
698 cx.focus(&pane);
699
700 let status_bar = cx.add_view(|cx| StatusBar::new(&pane, cx));
701 let mut current_user = params.user_store.read(cx).watch_current_user().clone();
702 let mut connection_status = params.client.status().clone();
703 let _observe_current_user = cx.spawn_weak(|this, mut cx| async move {
704 current_user.recv().await;
705 connection_status.recv().await;
706 let mut stream =
707 Stream::map(current_user, drop).merge(Stream::map(connection_status, drop));
708
709 while stream.recv().await.is_some() {
710 cx.update(|cx| {
711 if let Some(this) = this.upgrade(cx) {
712 this.update(cx, |_, cx| cx.notify());
713 }
714 })
715 }
716 });
717
718 let weak_self = cx.weak_handle();
719
720 cx.emit_global(WorkspaceCreated(weak_self.clone()));
721
722 let mut this = Workspace {
723 modal: None,
724 weak_self,
725 center: PaneGroup::new(pane.clone()),
726 panes: vec![pane.clone()],
727 active_pane: pane.clone(),
728 status_bar,
729 client: params.client.clone(),
730 remote_entity_subscription: None,
731 user_store: params.user_store.clone(),
732 fs: params.fs.clone(),
733 left_sidebar: Sidebar::new(Side::Left),
734 right_sidebar: Sidebar::new(Side::Right),
735 project: params.project.clone(),
736 leader_state: Default::default(),
737 follower_states_by_leader: Default::default(),
738 last_leaders_by_pane: Default::default(),
739 _observe_current_user,
740 };
741 this.project_remote_id_changed(this.project.read(cx).remote_id(), cx);
742 this
743 }
744
745 pub fn weak_handle(&self) -> WeakViewHandle<Self> {
746 self.weak_self.clone()
747 }
748
749 pub fn left_sidebar_mut(&mut self) -> &mut Sidebar {
750 &mut self.left_sidebar
751 }
752
753 pub fn right_sidebar_mut(&mut self) -> &mut Sidebar {
754 &mut self.right_sidebar
755 }
756
757 pub fn status_bar(&self) -> &ViewHandle<StatusBar> {
758 &self.status_bar
759 }
760
761 pub fn project(&self) -> &ModelHandle<Project> {
762 &self.project
763 }
764
765 pub fn worktrees<'a>(
766 &self,
767 cx: &'a AppContext,
768 ) -> impl 'a + Iterator<Item = ModelHandle<Worktree>> {
769 self.project.read(cx).worktrees(cx)
770 }
771
772 pub fn contains_paths(&self, paths: &[PathBuf], cx: &AppContext) -> bool {
773 paths.iter().all(|path| self.contains_path(&path, cx))
774 }
775
776 pub fn contains_path(&self, path: &Path, cx: &AppContext) -> bool {
777 for worktree in self.worktrees(cx) {
778 let worktree = worktree.read(cx).as_local();
779 if worktree.map_or(false, |w| w.contains_abs_path(path)) {
780 return true;
781 }
782 }
783 false
784 }
785
786 pub fn worktree_scans_complete(&self, cx: &AppContext) -> impl Future<Output = ()> + 'static {
787 let futures = self
788 .worktrees(cx)
789 .filter_map(|worktree| worktree.read(cx).as_local())
790 .map(|worktree| worktree.scan_complete())
791 .collect::<Vec<_>>();
792 async move {
793 for future in futures {
794 future.await;
795 }
796 }
797 }
798
799 pub fn open_paths(
800 &mut self,
801 abs_paths: &[PathBuf],
802 cx: &mut ViewContext<Self>,
803 ) -> Task<Vec<Option<Result<Box<dyn ItemHandle>, Arc<anyhow::Error>>>>> {
804 let entries = abs_paths
805 .iter()
806 .cloned()
807 .map(|path| self.project_path_for_path(&path, cx))
808 .collect::<Vec<_>>();
809
810 let fs = self.fs.clone();
811 let tasks = abs_paths
812 .iter()
813 .cloned()
814 .zip(entries.into_iter())
815 .map(|(abs_path, project_path)| {
816 cx.spawn(|this, mut cx| {
817 let fs = fs.clone();
818 async move {
819 let project_path = project_path.await.ok()?;
820 if fs.is_file(&abs_path).await {
821 Some(
822 this.update(&mut cx, |this, cx| this.open_path(project_path, cx))
823 .await,
824 )
825 } else {
826 None
827 }
828 }
829 })
830 })
831 .collect::<Vec<_>>();
832
833 cx.foreground().spawn(async move {
834 let mut items = Vec::new();
835 for task in tasks {
836 items.push(task.await);
837 }
838 items
839 })
840 }
841
842 fn project_path_for_path(
843 &self,
844 abs_path: &Path,
845 cx: &mut ViewContext<Self>,
846 ) -> Task<Result<ProjectPath>> {
847 let entry = self.project().update(cx, |project, cx| {
848 project.find_or_create_local_worktree(abs_path, true, cx)
849 });
850 cx.spawn(|_, cx| async move {
851 let (worktree, path) = entry.await?;
852 Ok(ProjectPath {
853 worktree_id: worktree.read_with(&cx, |t, _| t.id()),
854 path: path.into(),
855 })
856 })
857 }
858
859 // Returns the model that was toggled closed if it was open
860 pub fn toggle_modal<V, F>(
861 &mut self,
862 cx: &mut ViewContext<Self>,
863 add_view: F,
864 ) -> Option<ViewHandle<V>>
865 where
866 V: 'static + View,
867 F: FnOnce(&mut ViewContext<Self>, &mut Self) -> ViewHandle<V>,
868 {
869 cx.notify();
870 // Whatever modal was visible is getting clobbered. If its the same type as V, then return
871 // it. Otherwise, create a new modal and set it as active.
872 let already_open_modal = self.modal.take().and_then(|modal| modal.downcast::<V>());
873 if let Some(already_open_modal) = already_open_modal {
874 cx.focus_self();
875 Some(already_open_modal)
876 } else {
877 let modal = add_view(cx, self);
878 cx.focus(&modal);
879 self.modal = Some(modal.into());
880 None
881 }
882 }
883
884 pub fn modal(&self) -> Option<&AnyViewHandle> {
885 self.modal.as_ref()
886 }
887
888 pub fn dismiss_modal(&mut self, cx: &mut ViewContext<Self>) {
889 if self.modal.take().is_some() {
890 cx.focus(&self.active_pane);
891 cx.notify();
892 }
893 }
894
895 pub fn items<'a>(
896 &'a self,
897 cx: &'a AppContext,
898 ) -> impl 'a + Iterator<Item = &Box<dyn ItemHandle>> {
899 self.panes.iter().flat_map(|pane| pane.read(cx).items())
900 }
901
902 pub fn item_of_type<T: Item>(&self, cx: &AppContext) -> Option<ViewHandle<T>> {
903 self.items_of_type(cx).max_by_key(|item| item.id())
904 }
905
906 pub fn items_of_type<'a, T: Item>(
907 &'a self,
908 cx: &'a AppContext,
909 ) -> impl 'a + Iterator<Item = ViewHandle<T>> {
910 self.panes
911 .iter()
912 .flat_map(|pane| pane.read(cx).items_of_type())
913 }
914
915 pub fn active_item(&self, cx: &AppContext) -> Option<Box<dyn ItemHandle>> {
916 self.active_pane().read(cx).active_item()
917 }
918
919 fn active_project_path(&self, cx: &ViewContext<Self>) -> Option<ProjectPath> {
920 self.active_item(cx).and_then(|item| item.project_path(cx))
921 }
922
923 pub fn save_active_item(&mut self, cx: &mut ViewContext<Self>) -> Task<Result<()>> {
924 let project = self.project.clone();
925 if let Some(item) = self.active_item(cx) {
926 if item.can_save(cx) {
927 if item.has_conflict(cx.as_ref()) {
928 const CONFLICT_MESSAGE: &'static str = "This file has changed on disk since you started editing it. Do you want to overwrite it?";
929
930 let mut answer = cx.prompt(
931 PromptLevel::Warning,
932 CONFLICT_MESSAGE,
933 &["Overwrite", "Cancel"],
934 );
935 cx.spawn(|_, mut cx| async move {
936 let answer = answer.recv().await;
937 if answer == Some(0) {
938 cx.update(|cx| item.save(project, cx)).await?;
939 }
940 Ok(())
941 })
942 } else {
943 item.save(project, cx)
944 }
945 } else if item.can_save_as(cx) {
946 let worktree = self.worktrees(cx).next();
947 let start_abs_path = worktree
948 .and_then(|w| w.read(cx).as_local())
949 .map_or(Path::new(""), |w| w.abs_path())
950 .to_path_buf();
951 let mut abs_path = cx.prompt_for_new_path(&start_abs_path);
952 cx.spawn(|_, mut cx| async move {
953 if let Some(abs_path) = abs_path.recv().await.flatten() {
954 cx.update(|cx| item.save_as(project, abs_path, cx)).await?;
955 }
956 Ok(())
957 })
958 } else {
959 Task::ready(Ok(()))
960 }
961 } else {
962 Task::ready(Ok(()))
963 }
964 }
965
966 pub fn toggle_sidebar_item(&mut self, action: &ToggleSidebarItem, cx: &mut ViewContext<Self>) {
967 let sidebar = match action.0.side {
968 Side::Left => &mut self.left_sidebar,
969 Side::Right => &mut self.right_sidebar,
970 };
971 sidebar.toggle_item(action.0.item_index);
972 if let Some(active_item) = sidebar.active_item() {
973 cx.focus(active_item);
974 } else {
975 cx.focus_self();
976 }
977 cx.notify();
978 }
979
980 pub fn toggle_sidebar_item_focus(
981 &mut self,
982 action: &ToggleSidebarItemFocus,
983 cx: &mut ViewContext<Self>,
984 ) {
985 let sidebar = match action.0.side {
986 Side::Left => &mut self.left_sidebar,
987 Side::Right => &mut self.right_sidebar,
988 };
989 sidebar.activate_item(action.0.item_index);
990 if let Some(active_item) = sidebar.active_item() {
991 if active_item.is_focused(cx) {
992 cx.focus_self();
993 } else {
994 cx.focus(active_item);
995 }
996 }
997 cx.notify();
998 }
999
1000 pub fn debug_elements(&mut self, _: &DebugElements, cx: &mut ViewContext<Self>) {
1001 match to_string_pretty(&cx.debug_elements()) {
1002 Ok(json) => {
1003 let kib = json.len() as f32 / 1024.;
1004 cx.as_mut().write_to_clipboard(ClipboardItem::new(json));
1005 log::info!(
1006 "copied {:.1} KiB of element debug JSON to the clipboard",
1007 kib
1008 );
1009 }
1010 Err(error) => {
1011 log::error!("error debugging elements: {}", error);
1012 }
1013 };
1014 }
1015
1016 fn add_pane(&mut self, cx: &mut ViewContext<Self>) -> ViewHandle<Pane> {
1017 let pane = cx.add_view(|_| Pane::new());
1018 let pane_id = pane.id();
1019 cx.observe(&pane, move |me, _, cx| {
1020 let active_entry = me.active_project_path(cx);
1021 me.project
1022 .update(cx, |project, cx| project.set_active_path(active_entry, cx));
1023 })
1024 .detach();
1025 cx.subscribe(&pane, move |me, _, event, cx| {
1026 me.handle_pane_event(pane_id, event, cx)
1027 })
1028 .detach();
1029 self.panes.push(pane.clone());
1030 self.activate_pane(pane.clone(), cx);
1031 pane
1032 }
1033
1034 pub fn add_item(&mut self, item: Box<dyn ItemHandle>, cx: &mut ViewContext<Self>) {
1035 let pane = self.active_pane().clone();
1036 Pane::add_item(self, pane, item, true, cx);
1037 }
1038
1039 pub fn open_path(
1040 &mut self,
1041 path: impl Into<ProjectPath>,
1042 cx: &mut ViewContext<Self>,
1043 ) -> Task<Result<Box<dyn ItemHandle>, Arc<anyhow::Error>>> {
1044 let pane = self.active_pane().downgrade();
1045 let task = self.load_path(path.into(), cx);
1046 cx.spawn(|this, mut cx| async move {
1047 let (project_entry_id, build_item) = task.await?;
1048 let pane = pane
1049 .upgrade(&cx)
1050 .ok_or_else(|| anyhow!("pane was closed"))?;
1051 this.update(&mut cx, |this, cx| {
1052 Ok(Pane::open_item(
1053 this,
1054 pane,
1055 project_entry_id,
1056 cx,
1057 build_item,
1058 ))
1059 })
1060 })
1061 }
1062
1063 pub(crate) fn load_path(
1064 &mut self,
1065 path: ProjectPath,
1066 cx: &mut ViewContext<Self>,
1067 ) -> Task<
1068 Result<(
1069 ProjectEntryId,
1070 impl 'static + FnOnce(&mut MutableAppContext) -> Box<dyn ItemHandle>,
1071 )>,
1072 > {
1073 let project = self.project().clone();
1074 let project_item = project.update(cx, |project, cx| project.open_path(path, cx));
1075 let window_id = cx.window_id();
1076 cx.as_mut().spawn(|mut cx| async move {
1077 let (project_entry_id, project_item) = project_item.await?;
1078 let build_item = cx.update(|cx| {
1079 cx.default_global::<ProjectItemBuilders>()
1080 .get(&project_item.model_type())
1081 .ok_or_else(|| anyhow!("no item builder for project item"))
1082 .cloned()
1083 })?;
1084 let build_item =
1085 move |cx: &mut MutableAppContext| build_item(window_id, project, project_item, cx);
1086 Ok((project_entry_id, build_item))
1087 })
1088 }
1089
1090 pub fn open_project_item<T>(
1091 &mut self,
1092 project_item: ModelHandle<T::Item>,
1093 cx: &mut ViewContext<Self>,
1094 ) -> ViewHandle<T>
1095 where
1096 T: ProjectItem,
1097 {
1098 use project::Item as _;
1099
1100 let entry_id = project_item.read(cx).entry_id(cx);
1101 if let Some(item) = entry_id
1102 .and_then(|entry_id| self.active_pane().read(cx).item_for_entry(entry_id, cx))
1103 .and_then(|item| item.downcast())
1104 {
1105 self.activate_item(&item, cx);
1106 return item;
1107 }
1108
1109 let item = cx.add_view(|cx| T::for_project_item(self.project().clone(), project_item, cx));
1110 self.add_item(Box::new(item.clone()), cx);
1111 item
1112 }
1113
1114 pub fn activate_item(&mut self, item: &dyn ItemHandle, cx: &mut ViewContext<Self>) -> bool {
1115 let result = self.panes.iter().find_map(|pane| {
1116 if let Some(ix) = pane.read(cx).index_for_item(item) {
1117 Some((pane.clone(), ix))
1118 } else {
1119 None
1120 }
1121 });
1122 if let Some((pane, ix)) = result {
1123 self.activate_pane(pane.clone(), cx);
1124 pane.update(cx, |pane, cx| pane.activate_item(ix, true, cx));
1125 true
1126 } else {
1127 false
1128 }
1129 }
1130
1131 pub fn activate_next_pane(&mut self, cx: &mut ViewContext<Self>) {
1132 let ix = self
1133 .panes
1134 .iter()
1135 .position(|pane| pane == &self.active_pane)
1136 .unwrap();
1137 let next_ix = (ix + 1) % self.panes.len();
1138 self.activate_pane(self.panes[next_ix].clone(), cx);
1139 }
1140
1141 fn activate_pane(&mut self, pane: ViewHandle<Pane>, cx: &mut ViewContext<Self>) {
1142 if self.active_pane != pane {
1143 self.active_pane = pane.clone();
1144 self.status_bar.update(cx, |status_bar, cx| {
1145 status_bar.set_active_pane(&self.active_pane, cx);
1146 });
1147 cx.focus(&self.active_pane);
1148 cx.notify();
1149 }
1150
1151 self.update_followers(
1152 proto::update_followers::Variant::UpdateActiveView(proto::UpdateActiveView {
1153 id: self.active_item(cx).map(|item| item.id() as u64),
1154 leader_id: self.leader_for_pane(&pane).map(|id| id.0),
1155 }),
1156 cx,
1157 );
1158 }
1159
1160 fn handle_pane_event(
1161 &mut self,
1162 pane_id: usize,
1163 event: &pane::Event,
1164 cx: &mut ViewContext<Self>,
1165 ) {
1166 if let Some(pane) = self.pane(pane_id) {
1167 match event {
1168 pane::Event::Split(direction) => {
1169 self.split_pane(pane, *direction, cx);
1170 }
1171 pane::Event::Remove => {
1172 self.remove_pane(pane, cx);
1173 }
1174 pane::Event::Activate => {
1175 self.activate_pane(pane, cx);
1176 }
1177 pane::Event::ActivateItem { local } => {
1178 if *local {
1179 self.unfollow(&pane, cx);
1180 }
1181 }
1182 }
1183 } else {
1184 error!("pane {} not found", pane_id);
1185 }
1186 }
1187
1188 pub fn split_pane(
1189 &mut self,
1190 pane: ViewHandle<Pane>,
1191 direction: SplitDirection,
1192 cx: &mut ViewContext<Self>,
1193 ) -> ViewHandle<Pane> {
1194 let new_pane = self.add_pane(cx);
1195 self.activate_pane(new_pane.clone(), cx);
1196 if let Some(item) = pane.read(cx).active_item() {
1197 if let Some(clone) = item.clone_on_split(cx.as_mut()) {
1198 Pane::add_item(self, new_pane.clone(), clone, true, cx);
1199 }
1200 }
1201 self.center.split(&pane, &new_pane, direction).unwrap();
1202 cx.notify();
1203 new_pane
1204 }
1205
1206 fn remove_pane(&mut self, pane: ViewHandle<Pane>, cx: &mut ViewContext<Self>) {
1207 if self.center.remove(&pane).unwrap() {
1208 self.panes.retain(|p| p != &pane);
1209 self.activate_pane(self.panes.last().unwrap().clone(), cx);
1210 self.unfollow(&pane, cx);
1211 self.last_leaders_by_pane.remove(&pane.downgrade());
1212 cx.notify();
1213 }
1214 }
1215
1216 pub fn panes(&self) -> &[ViewHandle<Pane>] {
1217 &self.panes
1218 }
1219
1220 fn pane(&self, pane_id: usize) -> Option<ViewHandle<Pane>> {
1221 self.panes.iter().find(|pane| pane.id() == pane_id).cloned()
1222 }
1223
1224 pub fn active_pane(&self) -> &ViewHandle<Pane> {
1225 &self.active_pane
1226 }
1227
1228 fn toggle_share(&mut self, _: &ToggleShare, cx: &mut ViewContext<Self>) {
1229 self.project.update(cx, |project, cx| {
1230 if project.is_local() {
1231 if project.is_shared() {
1232 project.unshare(cx).detach();
1233 } else {
1234 project.share(cx).detach();
1235 }
1236 }
1237 });
1238 }
1239
1240 fn project_remote_id_changed(&mut self, remote_id: Option<u64>, cx: &mut ViewContext<Self>) {
1241 if let Some(remote_id) = remote_id {
1242 self.remote_entity_subscription =
1243 Some(self.client.add_view_for_remote_entity(remote_id, cx));
1244 } else {
1245 self.remote_entity_subscription.take();
1246 }
1247 }
1248
1249 fn collaborator_left(&mut self, peer_id: PeerId, cx: &mut ViewContext<Self>) {
1250 self.leader_state.followers.remove(&peer_id);
1251 if let Some(states_by_pane) = self.follower_states_by_leader.remove(&peer_id) {
1252 for state in states_by_pane.into_values() {
1253 for item in state.items_by_leader_view_id.into_values() {
1254 if let FollowerItem::Loaded(item) = item {
1255 item.set_leader_replica_id(None, cx);
1256 }
1257 }
1258 }
1259 }
1260 cx.notify();
1261 }
1262
1263 pub fn toggle_follow(
1264 &mut self,
1265 ToggleFollow(leader_id): &ToggleFollow,
1266 cx: &mut ViewContext<Self>,
1267 ) -> Option<Task<Result<()>>> {
1268 let leader_id = *leader_id;
1269 let pane = self.active_pane().clone();
1270
1271 if let Some(prev_leader_id) = self.unfollow(&pane, cx) {
1272 if leader_id == prev_leader_id {
1273 return None;
1274 }
1275 }
1276
1277 self.last_leaders_by_pane
1278 .insert(pane.downgrade(), leader_id);
1279 self.follower_states_by_leader
1280 .entry(leader_id)
1281 .or_default()
1282 .insert(pane.clone(), Default::default());
1283 cx.notify();
1284
1285 let project_id = self.project.read(cx).remote_id()?;
1286 let request = self.client.request(proto::Follow {
1287 project_id,
1288 leader_id: leader_id.0,
1289 });
1290 Some(cx.spawn_weak(|this, mut cx| async move {
1291 let response = request.await?;
1292 if let Some(this) = this.upgrade(&cx) {
1293 this.update(&mut cx, |this, _| {
1294 let state = this
1295 .follower_states_by_leader
1296 .get_mut(&leader_id)
1297 .and_then(|states_by_pane| states_by_pane.get_mut(&pane))
1298 .ok_or_else(|| anyhow!("following interrupted"))?;
1299 state.active_view_id = response.active_view_id;
1300 Ok::<_, anyhow::Error>(())
1301 })?;
1302 Self::add_views_from_leader(this, leader_id, vec![pane], response.views, &mut cx)
1303 .await?;
1304 }
1305 Ok(())
1306 }))
1307 }
1308
1309 pub fn follow_next_collaborator(
1310 &mut self,
1311 _: &FollowNextCollaborator,
1312 cx: &mut ViewContext<Self>,
1313 ) -> Option<Task<Result<()>>> {
1314 let collaborators = self.project.read(cx).collaborators();
1315 let next_leader_id = if let Some(leader_id) = self.leader_for_pane(&self.active_pane) {
1316 let mut collaborators = collaborators.keys().copied();
1317 while let Some(peer_id) = collaborators.next() {
1318 if peer_id == leader_id {
1319 break;
1320 }
1321 }
1322 collaborators.next()
1323 } else if let Some(last_leader_id) =
1324 self.last_leaders_by_pane.get(&self.active_pane.downgrade())
1325 {
1326 if collaborators.contains_key(last_leader_id) {
1327 Some(*last_leader_id)
1328 } else {
1329 None
1330 }
1331 } else {
1332 None
1333 };
1334
1335 next_leader_id
1336 .or_else(|| collaborators.keys().copied().next())
1337 .and_then(|leader_id| self.toggle_follow(&ToggleFollow(leader_id), cx))
1338 }
1339
1340 pub fn unfollow(
1341 &mut self,
1342 pane: &ViewHandle<Pane>,
1343 cx: &mut ViewContext<Self>,
1344 ) -> Option<PeerId> {
1345 for (leader_id, states_by_pane) in &mut self.follower_states_by_leader {
1346 let leader_id = *leader_id;
1347 if let Some(state) = states_by_pane.remove(&pane) {
1348 for (_, item) in state.items_by_leader_view_id {
1349 if let FollowerItem::Loaded(item) = item {
1350 item.set_leader_replica_id(None, cx);
1351 }
1352 }
1353
1354 if states_by_pane.is_empty() {
1355 self.follower_states_by_leader.remove(&leader_id);
1356 if let Some(project_id) = self.project.read(cx).remote_id() {
1357 self.client
1358 .send(proto::Unfollow {
1359 project_id,
1360 leader_id: leader_id.0,
1361 })
1362 .log_err();
1363 }
1364 }
1365
1366 cx.notify();
1367 return Some(leader_id);
1368 }
1369 }
1370 None
1371 }
1372
1373 fn render_connection_status(&self, cx: &mut RenderContext<Self>) -> Option<ElementBox> {
1374 let theme = &cx.global::<Settings>().theme;
1375 match &*self.client.status().borrow() {
1376 client::Status::ConnectionError
1377 | client::Status::ConnectionLost
1378 | client::Status::Reauthenticating
1379 | client::Status::Reconnecting { .. }
1380 | client::Status::ReconnectionError { .. } => Some(
1381 Container::new(
1382 Align::new(
1383 ConstrainedBox::new(
1384 Svg::new("icons/offline-14.svg")
1385 .with_color(theme.workspace.titlebar.offline_icon.color)
1386 .boxed(),
1387 )
1388 .with_width(theme.workspace.titlebar.offline_icon.width)
1389 .boxed(),
1390 )
1391 .boxed(),
1392 )
1393 .with_style(theme.workspace.titlebar.offline_icon.container)
1394 .boxed(),
1395 ),
1396 client::Status::UpgradeRequired => Some(
1397 Label::new(
1398 "Please update Zed to collaborate".to_string(),
1399 theme.workspace.titlebar.outdated_warning.text.clone(),
1400 )
1401 .contained()
1402 .with_style(theme.workspace.titlebar.outdated_warning.container)
1403 .aligned()
1404 .boxed(),
1405 ),
1406 _ => None,
1407 }
1408 }
1409
1410 fn render_titlebar(&self, theme: &Theme, cx: &mut RenderContext<Self>) -> ElementBox {
1411 ConstrainedBox::new(
1412 Container::new(
1413 Stack::new()
1414 .with_child(
1415 Align::new(
1416 Label::new("zed".into(), theme.workspace.titlebar.title.clone())
1417 .boxed(),
1418 )
1419 .boxed(),
1420 )
1421 .with_child(
1422 Align::new(
1423 Flex::row()
1424 .with_children(self.render_share_icon(theme, cx))
1425 .with_children(self.render_collaborators(theme, cx))
1426 .with_child(self.render_current_user(
1427 self.user_store.read(cx).current_user().as_ref(),
1428 self.project.read(cx).replica_id(),
1429 theme,
1430 cx,
1431 ))
1432 .with_children(self.render_connection_status(cx))
1433 .boxed(),
1434 )
1435 .right()
1436 .boxed(),
1437 )
1438 .boxed(),
1439 )
1440 .with_style(theme.workspace.titlebar.container)
1441 .boxed(),
1442 )
1443 .with_height(theme.workspace.titlebar.height)
1444 .named("titlebar")
1445 }
1446
1447 fn render_collaborators(&self, theme: &Theme, cx: &mut RenderContext<Self>) -> Vec<ElementBox> {
1448 let mut collaborators = self
1449 .project
1450 .read(cx)
1451 .collaborators()
1452 .values()
1453 .cloned()
1454 .collect::<Vec<_>>();
1455 collaborators.sort_unstable_by_key(|collaborator| collaborator.replica_id);
1456 collaborators
1457 .into_iter()
1458 .filter_map(|collaborator| {
1459 Some(self.render_avatar(
1460 collaborator.user.avatar.clone()?,
1461 collaborator.replica_id,
1462 Some(collaborator.peer_id),
1463 theme,
1464 cx,
1465 ))
1466 })
1467 .collect()
1468 }
1469
1470 fn render_current_user(
1471 &self,
1472 user: Option<&Arc<User>>,
1473 replica_id: ReplicaId,
1474 theme: &Theme,
1475 cx: &mut RenderContext<Self>,
1476 ) -> ElementBox {
1477 if let Some(avatar) = user.and_then(|user| user.avatar.clone()) {
1478 self.render_avatar(avatar, replica_id, None, theme, cx)
1479 } else {
1480 MouseEventHandler::new::<Authenticate, _, _>(0, cx, |state, _| {
1481 let style = if state.hovered {
1482 &theme.workspace.titlebar.hovered_sign_in_prompt
1483 } else {
1484 &theme.workspace.titlebar.sign_in_prompt
1485 };
1486 Label::new("Sign in".to_string(), style.text.clone())
1487 .contained()
1488 .with_style(style.container)
1489 .boxed()
1490 })
1491 .on_click(|cx| cx.dispatch_action(Authenticate))
1492 .with_cursor_style(CursorStyle::PointingHand)
1493 .aligned()
1494 .boxed()
1495 }
1496 }
1497
1498 fn render_avatar(
1499 &self,
1500 avatar: Arc<ImageData>,
1501 replica_id: ReplicaId,
1502 peer_id: Option<PeerId>,
1503 theme: &Theme,
1504 cx: &mut RenderContext<Self>,
1505 ) -> ElementBox {
1506 let replica_color = theme.editor.replica_selection_style(replica_id).cursor;
1507 let is_followed = peer_id.map_or(false, |peer_id| {
1508 self.follower_states_by_leader.contains_key(&peer_id)
1509 });
1510 let mut avatar_style = theme.workspace.titlebar.avatar;
1511 if is_followed {
1512 avatar_style.border = Border::all(1.0, replica_color);
1513 }
1514 let content = Stack::new()
1515 .with_child(
1516 Image::new(avatar)
1517 .with_style(avatar_style)
1518 .constrained()
1519 .with_width(theme.workspace.titlebar.avatar_width)
1520 .aligned()
1521 .boxed(),
1522 )
1523 .with_child(
1524 AvatarRibbon::new(replica_color)
1525 .constrained()
1526 .with_width(theme.workspace.titlebar.avatar_ribbon.width)
1527 .with_height(theme.workspace.titlebar.avatar_ribbon.height)
1528 .aligned()
1529 .bottom()
1530 .boxed(),
1531 )
1532 .constrained()
1533 .with_width(theme.workspace.right_sidebar.width)
1534 .boxed();
1535
1536 if let Some(peer_id) = peer_id {
1537 MouseEventHandler::new::<ToggleFollow, _, _>(replica_id.into(), cx, move |_, _| content)
1538 .with_cursor_style(CursorStyle::PointingHand)
1539 .on_click(move |cx| cx.dispatch_action(ToggleFollow(peer_id)))
1540 .boxed()
1541 } else {
1542 content
1543 }
1544 }
1545
1546 fn render_share_icon(&self, theme: &Theme, cx: &mut RenderContext<Self>) -> Option<ElementBox> {
1547 if self.project().read(cx).is_local() && self.client.user_id().is_some() {
1548 let color = if self.project().read(cx).is_shared() {
1549 theme.workspace.titlebar.share_icon_active_color
1550 } else {
1551 theme.workspace.titlebar.share_icon_color
1552 };
1553 Some(
1554 MouseEventHandler::new::<ToggleShare, _, _>(0, cx, |_, _| {
1555 Align::new(
1556 Svg::new("icons/broadcast-24.svg")
1557 .with_color(color)
1558 .constrained()
1559 .with_width(24.)
1560 .boxed(),
1561 )
1562 .boxed()
1563 })
1564 .with_cursor_style(CursorStyle::PointingHand)
1565 .on_click(|cx| cx.dispatch_action(ToggleShare))
1566 .boxed(),
1567 )
1568 } else {
1569 None
1570 }
1571 }
1572
1573 fn render_disconnected_overlay(&self, cx: &AppContext) -> Option<ElementBox> {
1574 if self.project.read(cx).is_read_only() {
1575 let theme = &cx.global::<Settings>().theme;
1576 Some(
1577 EventHandler::new(
1578 Label::new(
1579 "Your connection to the remote project has been lost.".to_string(),
1580 theme.workspace.disconnected_overlay.text.clone(),
1581 )
1582 .aligned()
1583 .contained()
1584 .with_style(theme.workspace.disconnected_overlay.container)
1585 .boxed(),
1586 )
1587 .capture(|_, _, _| true)
1588 .boxed(),
1589 )
1590 } else {
1591 None
1592 }
1593 }
1594
1595 // RPC handlers
1596
1597 async fn handle_follow(
1598 this: ViewHandle<Self>,
1599 envelope: TypedEnvelope<proto::Follow>,
1600 _: Arc<Client>,
1601 mut cx: AsyncAppContext,
1602 ) -> Result<proto::FollowResponse> {
1603 this.update(&mut cx, |this, cx| {
1604 this.leader_state
1605 .followers
1606 .insert(envelope.original_sender_id()?);
1607
1608 let active_view_id = this
1609 .active_item(cx)
1610 .and_then(|i| i.to_followable_item_handle(cx))
1611 .map(|i| i.id() as u64);
1612 Ok(proto::FollowResponse {
1613 active_view_id,
1614 views: this
1615 .panes()
1616 .iter()
1617 .flat_map(|pane| {
1618 let leader_id = this.leader_for_pane(pane).map(|id| id.0);
1619 pane.read(cx).items().filter_map({
1620 let cx = &cx;
1621 move |item| {
1622 let id = item.id() as u64;
1623 let item = item.to_followable_item_handle(cx)?;
1624 let variant = item.to_state_message(cx)?;
1625 Some(proto::View {
1626 id,
1627 leader_id,
1628 variant: Some(variant),
1629 })
1630 }
1631 })
1632 })
1633 .collect(),
1634 })
1635 })
1636 }
1637
1638 async fn handle_unfollow(
1639 this: ViewHandle<Self>,
1640 envelope: TypedEnvelope<proto::Unfollow>,
1641 _: Arc<Client>,
1642 mut cx: AsyncAppContext,
1643 ) -> Result<()> {
1644 this.update(&mut cx, |this, _| {
1645 this.leader_state
1646 .followers
1647 .remove(&envelope.original_sender_id()?);
1648 Ok(())
1649 })
1650 }
1651
1652 async fn handle_update_followers(
1653 this: ViewHandle<Self>,
1654 envelope: TypedEnvelope<proto::UpdateFollowers>,
1655 _: Arc<Client>,
1656 mut cx: AsyncAppContext,
1657 ) -> Result<()> {
1658 let leader_id = envelope.original_sender_id()?;
1659 match envelope
1660 .payload
1661 .variant
1662 .ok_or_else(|| anyhow!("invalid update"))?
1663 {
1664 proto::update_followers::Variant::UpdateActiveView(update_active_view) => {
1665 this.update(&mut cx, |this, cx| {
1666 this.update_leader_state(leader_id, cx, |state, _| {
1667 state.active_view_id = update_active_view.id;
1668 });
1669 Ok::<_, anyhow::Error>(())
1670 })
1671 }
1672 proto::update_followers::Variant::UpdateView(update_view) => {
1673 this.update(&mut cx, |this, cx| {
1674 let variant = update_view
1675 .variant
1676 .ok_or_else(|| anyhow!("missing update view variant"))?;
1677 this.update_leader_state(leader_id, cx, |state, cx| {
1678 let variant = variant.clone();
1679 match state
1680 .items_by_leader_view_id
1681 .entry(update_view.id)
1682 .or_insert(FollowerItem::Loading(Vec::new()))
1683 {
1684 FollowerItem::Loaded(item) => {
1685 item.apply_update_message(variant, cx).log_err();
1686 }
1687 FollowerItem::Loading(updates) => updates.push(variant),
1688 }
1689 });
1690 Ok(())
1691 })
1692 }
1693 proto::update_followers::Variant::CreateView(view) => {
1694 let panes = this.read_with(&cx, |this, _| {
1695 this.follower_states_by_leader
1696 .get(&leader_id)
1697 .into_iter()
1698 .flat_map(|states_by_pane| states_by_pane.keys())
1699 .cloned()
1700 .collect()
1701 });
1702 Self::add_views_from_leader(this.clone(), leader_id, panes, vec![view], &mut cx)
1703 .await?;
1704 Ok(())
1705 }
1706 }
1707 .log_err();
1708
1709 Ok(())
1710 }
1711
1712 async fn add_views_from_leader(
1713 this: ViewHandle<Self>,
1714 leader_id: PeerId,
1715 panes: Vec<ViewHandle<Pane>>,
1716 views: Vec<proto::View>,
1717 cx: &mut AsyncAppContext,
1718 ) -> Result<()> {
1719 let project = this.read_with(cx, |this, _| this.project.clone());
1720 let replica_id = project
1721 .read_with(cx, |project, _| {
1722 project
1723 .collaborators()
1724 .get(&leader_id)
1725 .map(|c| c.replica_id)
1726 })
1727 .ok_or_else(|| anyhow!("no such collaborator {}", leader_id))?;
1728
1729 let item_builders = cx.update(|cx| {
1730 cx.default_global::<FollowableItemBuilders>()
1731 .values()
1732 .map(|b| b.0)
1733 .collect::<Vec<_>>()
1734 .clone()
1735 });
1736
1737 let mut item_tasks_by_pane = HashMap::default();
1738 for pane in panes {
1739 let mut item_tasks = Vec::new();
1740 let mut leader_view_ids = Vec::new();
1741 for view in &views {
1742 let mut variant = view.variant.clone();
1743 if variant.is_none() {
1744 Err(anyhow!("missing variant"))?;
1745 }
1746 for build_item in &item_builders {
1747 let task =
1748 cx.update(|cx| build_item(pane.clone(), project.clone(), &mut variant, cx));
1749 if let Some(task) = task {
1750 item_tasks.push(task);
1751 leader_view_ids.push(view.id);
1752 break;
1753 } else {
1754 assert!(variant.is_some());
1755 }
1756 }
1757 }
1758
1759 item_tasks_by_pane.insert(pane, (item_tasks, leader_view_ids));
1760 }
1761
1762 for (pane, (item_tasks, leader_view_ids)) in item_tasks_by_pane {
1763 let items = futures::future::try_join_all(item_tasks).await?;
1764 this.update(cx, |this, cx| {
1765 let state = this
1766 .follower_states_by_leader
1767 .get_mut(&leader_id)?
1768 .get_mut(&pane)?;
1769
1770 for (id, item) in leader_view_ids.into_iter().zip(items) {
1771 item.set_leader_replica_id(Some(replica_id), cx);
1772 match state.items_by_leader_view_id.entry(id) {
1773 hash_map::Entry::Occupied(e) => {
1774 let e = e.into_mut();
1775 if let FollowerItem::Loading(updates) = e {
1776 for update in updates.drain(..) {
1777 item.apply_update_message(update, cx)
1778 .context("failed to apply view update")
1779 .log_err();
1780 }
1781 }
1782 *e = FollowerItem::Loaded(item);
1783 }
1784 hash_map::Entry::Vacant(e) => {
1785 e.insert(FollowerItem::Loaded(item));
1786 }
1787 }
1788 }
1789
1790 Some(())
1791 });
1792 }
1793 this.update(cx, |this, cx| this.leader_updated(leader_id, cx));
1794
1795 Ok(())
1796 }
1797
1798 fn update_followers(
1799 &self,
1800 update: proto::update_followers::Variant,
1801 cx: &AppContext,
1802 ) -> Option<()> {
1803 let project_id = self.project.read(cx).remote_id()?;
1804 if !self.leader_state.followers.is_empty() {
1805 self.client
1806 .send(proto::UpdateFollowers {
1807 project_id,
1808 follower_ids: self.leader_state.followers.iter().map(|f| f.0).collect(),
1809 variant: Some(update),
1810 })
1811 .log_err();
1812 }
1813 None
1814 }
1815
1816 pub fn leader_for_pane(&self, pane: &ViewHandle<Pane>) -> Option<PeerId> {
1817 self.follower_states_by_leader
1818 .iter()
1819 .find_map(|(leader_id, state)| {
1820 if state.contains_key(pane) {
1821 Some(*leader_id)
1822 } else {
1823 None
1824 }
1825 })
1826 }
1827
1828 fn update_leader_state(
1829 &mut self,
1830 leader_id: PeerId,
1831 cx: &mut ViewContext<Self>,
1832 mut update_fn: impl FnMut(&mut FollowerState, &mut ViewContext<Self>),
1833 ) {
1834 for (_, state) in self
1835 .follower_states_by_leader
1836 .get_mut(&leader_id)
1837 .into_iter()
1838 .flatten()
1839 {
1840 update_fn(state, cx);
1841 }
1842 self.leader_updated(leader_id, cx);
1843 }
1844
1845 fn leader_updated(&mut self, leader_id: PeerId, cx: &mut ViewContext<Self>) -> Option<()> {
1846 let mut items_to_add = Vec::new();
1847 for (pane, state) in self.follower_states_by_leader.get(&leader_id)? {
1848 if let Some(active_item) = state
1849 .active_view_id
1850 .and_then(|id| state.items_by_leader_view_id.get(&id))
1851 {
1852 if let FollowerItem::Loaded(item) = active_item {
1853 items_to_add.push((pane.clone(), item.boxed_clone()));
1854 }
1855 }
1856 }
1857
1858 for (pane, item) in items_to_add {
1859 Pane::add_item(self, pane.clone(), item.boxed_clone(), false, cx);
1860 cx.notify();
1861 }
1862 None
1863 }
1864}
1865
1866impl Entity for Workspace {
1867 type Event = ();
1868}
1869
1870impl View for Workspace {
1871 fn ui_name() -> &'static str {
1872 "Workspace"
1873 }
1874
1875 fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
1876 let theme = cx.global::<Settings>().theme.clone();
1877 Stack::new()
1878 .with_child(
1879 Flex::column()
1880 .with_child(self.render_titlebar(&theme, cx))
1881 .with_child(
1882 Stack::new()
1883 .with_child({
1884 let mut content = Flex::row();
1885 content.add_child(self.left_sidebar.render(&theme, cx));
1886 if let Some(element) =
1887 self.left_sidebar.render_active_item(&theme, cx)
1888 {
1889 content.add_child(Flexible::new(0.8, false, element).boxed());
1890 }
1891 content.add_child(
1892 Flex::column()
1893 .with_child(
1894 Flexible::new(
1895 1.,
1896 true,
1897 self.center.render(
1898 &theme,
1899 &self.follower_states_by_leader,
1900 self.project.read(cx).collaborators(),
1901 ),
1902 )
1903 .boxed(),
1904 )
1905 .with_child(ChildView::new(&self.status_bar).boxed())
1906 .flexible(1., true)
1907 .boxed(),
1908 );
1909 if let Some(element) =
1910 self.right_sidebar.render_active_item(&theme, cx)
1911 {
1912 content.add_child(Flexible::new(0.8, false, element).boxed());
1913 }
1914 content.add_child(self.right_sidebar.render(&theme, cx));
1915 content.boxed()
1916 })
1917 .with_children(self.modal.as_ref().map(|m| ChildView::new(m).boxed()))
1918 .flexible(1.0, true)
1919 .boxed(),
1920 )
1921 .contained()
1922 .with_background_color(theme.workspace.background)
1923 .boxed(),
1924 )
1925 .with_children(self.render_disconnected_overlay(cx))
1926 .named("workspace")
1927 }
1928
1929 fn on_focus(&mut self, cx: &mut ViewContext<Self>) {
1930 cx.focus(&self.active_pane);
1931 }
1932}
1933
1934pub trait WorkspaceHandle {
1935 fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath>;
1936}
1937
1938impl WorkspaceHandle for ViewHandle<Workspace> {
1939 fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath> {
1940 self.read(cx)
1941 .worktrees(cx)
1942 .flat_map(|worktree| {
1943 let worktree_id = worktree.read(cx).id();
1944 worktree.read(cx).files(true, 0).map(move |f| ProjectPath {
1945 worktree_id,
1946 path: f.path.clone(),
1947 })
1948 })
1949 .collect::<Vec<_>>()
1950 }
1951}
1952
1953pub struct AvatarRibbon {
1954 color: Color,
1955}
1956
1957impl AvatarRibbon {
1958 pub fn new(color: Color) -> AvatarRibbon {
1959 AvatarRibbon { color }
1960 }
1961}
1962
1963impl Element for AvatarRibbon {
1964 type LayoutState = ();
1965
1966 type PaintState = ();
1967
1968 fn layout(
1969 &mut self,
1970 constraint: gpui::SizeConstraint,
1971 _: &mut gpui::LayoutContext,
1972 ) -> (gpui::geometry::vector::Vector2F, Self::LayoutState) {
1973 (constraint.max, ())
1974 }
1975
1976 fn paint(
1977 &mut self,
1978 bounds: gpui::geometry::rect::RectF,
1979 _: gpui::geometry::rect::RectF,
1980 _: &mut Self::LayoutState,
1981 cx: &mut gpui::PaintContext,
1982 ) -> Self::PaintState {
1983 let mut path = PathBuilder::new();
1984 path.reset(bounds.lower_left());
1985 path.curve_to(
1986 bounds.origin() + vec2f(bounds.height(), 0.),
1987 bounds.origin(),
1988 );
1989 path.line_to(bounds.upper_right() - vec2f(bounds.height(), 0.));
1990 path.curve_to(bounds.lower_right(), bounds.upper_right());
1991 path.line_to(bounds.lower_left());
1992 cx.scene.push_path(path.build(self.color, None));
1993 }
1994
1995 fn dispatch_event(
1996 &mut self,
1997 _: &gpui::Event,
1998 _: gpui::geometry::rect::RectF,
1999 _: &mut Self::LayoutState,
2000 _: &mut Self::PaintState,
2001 _: &mut gpui::EventContext,
2002 ) -> bool {
2003 false
2004 }
2005
2006 fn debug(
2007 &self,
2008 bounds: gpui::geometry::rect::RectF,
2009 _: &Self::LayoutState,
2010 _: &Self::PaintState,
2011 _: &gpui::DebugContext,
2012 ) -> gpui::json::Value {
2013 json::json!({
2014 "type": "AvatarRibbon",
2015 "bounds": bounds.to_json(),
2016 "color": self.color.to_json(),
2017 })
2018 }
2019}
2020
2021impl std::fmt::Debug for OpenParams {
2022 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2023 f.debug_struct("OpenParams")
2024 .field("paths", &self.paths)
2025 .finish()
2026 }
2027}
2028
2029fn open(action: &Open, cx: &mut MutableAppContext) {
2030 let app_state = action.0.clone();
2031 let mut paths = cx.prompt_for_paths(PathPromptOptions {
2032 files: true,
2033 directories: true,
2034 multiple: true,
2035 });
2036 cx.spawn(|mut cx| async move {
2037 if let Some(paths) = paths.recv().await.flatten() {
2038 cx.update(|cx| cx.dispatch_global_action(OpenPaths(OpenParams { paths, app_state })));
2039 }
2040 })
2041 .detach();
2042}
2043
2044pub struct WorkspaceCreated(WeakViewHandle<Workspace>);
2045
2046pub fn open_paths(
2047 abs_paths: &[PathBuf],
2048 app_state: &Arc<AppState>,
2049 cx: &mut MutableAppContext,
2050) -> Task<ViewHandle<Workspace>> {
2051 log::info!("open paths {:?}", abs_paths);
2052
2053 // Open paths in existing workspace if possible
2054 let mut existing = None;
2055 for window_id in cx.window_ids().collect::<Vec<_>>() {
2056 if let Some(workspace_handle) = cx.root_view::<Workspace>(window_id) {
2057 if workspace_handle.update(cx, |workspace, cx| {
2058 if workspace.contains_paths(abs_paths, cx.as_ref()) {
2059 cx.activate_window(window_id);
2060 existing = Some(workspace_handle.clone());
2061 true
2062 } else {
2063 false
2064 }
2065 }) {
2066 break;
2067 }
2068 }
2069 }
2070
2071 let workspace = existing.unwrap_or_else(|| {
2072 cx.add_window((app_state.build_window_options)(), |cx| {
2073 let project = Project::local(
2074 app_state.client.clone(),
2075 app_state.user_store.clone(),
2076 app_state.languages.clone(),
2077 app_state.fs.clone(),
2078 cx,
2079 );
2080 (app_state.build_workspace)(project, &app_state, cx)
2081 })
2082 .1
2083 });
2084
2085 let task = workspace.update(cx, |workspace, cx| workspace.open_paths(abs_paths, cx));
2086 cx.spawn(|_| async move {
2087 task.await;
2088 workspace
2089 })
2090}
2091
2092pub fn join_project(
2093 project_id: u64,
2094 app_state: &Arc<AppState>,
2095 cx: &mut MutableAppContext,
2096) -> Task<Result<ViewHandle<Workspace>>> {
2097 for window_id in cx.window_ids().collect::<Vec<_>>() {
2098 if let Some(workspace) = cx.root_view::<Workspace>(window_id) {
2099 if workspace.read(cx).project().read(cx).remote_id() == Some(project_id) {
2100 return Task::ready(Ok(workspace));
2101 }
2102 }
2103 }
2104
2105 let app_state = app_state.clone();
2106 cx.spawn(|mut cx| async move {
2107 let project = Project::remote(
2108 project_id,
2109 app_state.client.clone(),
2110 app_state.user_store.clone(),
2111 app_state.languages.clone(),
2112 app_state.fs.clone(),
2113 &mut cx,
2114 )
2115 .await?;
2116 Ok(cx.update(|cx| {
2117 cx.add_window((app_state.build_window_options)(), |cx| {
2118 (app_state.build_workspace)(project, &app_state, cx)
2119 })
2120 .1
2121 }))
2122 })
2123}
2124
2125fn open_new(app_state: &Arc<AppState>, cx: &mut MutableAppContext) {
2126 let (window_id, workspace) = cx.add_window((app_state.build_window_options)(), |cx| {
2127 let project = Project::local(
2128 app_state.client.clone(),
2129 app_state.user_store.clone(),
2130 app_state.languages.clone(),
2131 app_state.fs.clone(),
2132 cx,
2133 );
2134 (app_state.build_workspace)(project, &app_state, cx)
2135 });
2136 cx.dispatch_action(window_id, vec![workspace.id()], &OpenNew(app_state.clone()));
2137}