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