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