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