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