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