workspace.rs

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