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