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