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