workspace.rs

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