workspace.rs

   1pub mod pane;
   2pub mod pane_group;
   3pub mod sidebar;
   4mod status_bar;
   5mod toolbar;
   6mod waiting_room;
   7
   8use anyhow::{anyhow, Context, Result};
   9use client::{
  10    proto, Authenticate, Client, Contact, PeerId, Subscription, TypedEnvelope, User, UserStore,
  11};
  12use clock::ReplicaId;
  13use collections::{hash_map, HashMap, HashSet};
  14use gpui::{
  15    actions,
  16    color::Color,
  17    elements::*,
  18    geometry::{rect::RectF, vector::vec2f, PathBuilder},
  19    impl_actions, impl_internal_actions,
  20    json::{self, ToJson},
  21    platform::{CursorStyle, WindowOptions},
  22    AnyModelHandle, AnyViewHandle, AppContext, AsyncAppContext, Border, Entity, ImageData,
  23    ModelContext, ModelHandle, MutableAppContext, PathPromptOptions, PromptLevel, RenderContext,
  24    Task, View, ViewContext, ViewHandle, WeakViewHandle,
  25};
  26use language::LanguageRegistry;
  27use log::error;
  28pub use pane::*;
  29pub use pane_group::*;
  30use postage::prelude::Stream;
  31use project::{fs, Fs, Project, ProjectEntryId, ProjectPath, ProjectStore, Worktree, WorktreeId};
  32use serde::Deserialize;
  33use settings::Settings;
  34use sidebar::{Side, Sidebar, SidebarButtons, ToggleSidebarItem, ToggleSidebarItemFocus};
  35use smallvec::SmallVec;
  36use status_bar::StatusBar;
  37pub use status_bar::StatusItemView;
  38use std::{
  39    any::{Any, TypeId},
  40    borrow::Cow,
  41    cell::RefCell,
  42    fmt,
  43    future::Future,
  44    path::{Path, PathBuf},
  45    rc::Rc,
  46    sync::{
  47        atomic::{AtomicBool, Ordering::SeqCst},
  48        Arc,
  49    },
  50};
  51use theme::{Theme, ThemeRegistry};
  52pub use toolbar::{ToolbarItemLocation, ToolbarItemView};
  53use util::ResultExt;
  54use waiting_room::WaitingRoom;
  55
  56type ProjectItemBuilders = HashMap<
  57    TypeId,
  58    fn(usize, ModelHandle<Project>, AnyModelHandle, &mut MutableAppContext) -> Box<dyn ItemHandle>,
  59>;
  60
  61type FollowableItemBuilder = fn(
  62    ViewHandle<Pane>,
  63    ModelHandle<Project>,
  64    &mut Option<proto::view::Variant>,
  65    &mut MutableAppContext,
  66) -> Option<Task<Result<Box<dyn FollowableItemHandle>>>>;
  67type FollowableItemBuilders = HashMap<
  68    TypeId,
  69    (
  70        FollowableItemBuilder,
  71        fn(AnyViewHandle) -> Box<dyn FollowableItemHandle>,
  72    ),
  73>;
  74
  75#[derive(Clone, PartialEq)]
  76pub struct RemoveWorktreeFromProject(pub WorktreeId);
  77
  78actions!(
  79    workspace,
  80    [
  81        Open,
  82        NewFile,
  83        NewWindow,
  84        CloseWindow,
  85        AddFolderToProject,
  86        Unfollow,
  87        Save,
  88        SaveAs,
  89        SaveAll,
  90        ActivatePreviousPane,
  91        ActivateNextPane,
  92        FollowNextCollaborator,
  93    ]
  94);
  95
  96#[derive(Clone, PartialEq)]
  97pub struct OpenPaths {
  98    pub paths: Vec<PathBuf>,
  99}
 100
 101#[derive(Clone, Deserialize, PartialEq)]
 102pub struct ToggleProjectOnline {
 103    #[serde(skip_deserializing)]
 104    pub project: Option<ModelHandle<Project>>,
 105}
 106
 107#[derive(Clone, PartialEq)]
 108pub struct ToggleFollow(pub PeerId);
 109
 110#[derive(Clone, PartialEq)]
 111pub struct JoinProject {
 112    pub contact: Arc<Contact>,
 113    pub project_index: usize,
 114}
 115
 116impl_internal_actions!(
 117    workspace,
 118    [
 119        OpenPaths,
 120        ToggleFollow,
 121        JoinProject,
 122        RemoveWorktreeFromProject
 123    ]
 124);
 125impl_actions!(workspace, [ToggleProjectOnline]);
 126
 127pub fn init(app_state: Arc<AppState>, cx: &mut MutableAppContext) {
 128    pane::init(cx);
 129
 130    cx.add_global_action(open);
 131    cx.add_global_action({
 132        let app_state = Arc::downgrade(&app_state);
 133        move |action: &OpenPaths, cx: &mut MutableAppContext| {
 134            if let Some(app_state) = app_state.upgrade() {
 135                open_paths(&action.paths, &app_state, cx).detach();
 136            }
 137        }
 138    });
 139    cx.add_global_action({
 140        let app_state = Arc::downgrade(&app_state);
 141        move |_: &NewFile, cx: &mut MutableAppContext| {
 142            if let Some(app_state) = app_state.upgrade() {
 143                open_new(&app_state, cx)
 144            }
 145        }
 146    });
 147    cx.add_global_action({
 148        let app_state = Arc::downgrade(&app_state);
 149        move |_: &NewWindow, cx: &mut MutableAppContext| {
 150            if let Some(app_state) = app_state.upgrade() {
 151                open_new(&app_state, cx)
 152            }
 153        }
 154    });
 155    cx.add_global_action({
 156        let app_state = Arc::downgrade(&app_state);
 157        move |action: &JoinProject, cx: &mut MutableAppContext| {
 158            if let Some(app_state) = app_state.upgrade() {
 159                join_project(action.contact.clone(), action.project_index, &app_state, cx);
 160            }
 161        }
 162    });
 163
 164    cx.add_async_action(Workspace::toggle_follow);
 165    cx.add_async_action(Workspace::follow_next_collaborator);
 166    cx.add_async_action(Workspace::close);
 167    cx.add_async_action(Workspace::save_all);
 168    cx.add_action(Workspace::add_folder_to_project);
 169    cx.add_action(Workspace::remove_folder_from_project);
 170    cx.add_action(Workspace::toggle_project_online);
 171    cx.add_action(
 172        |workspace: &mut Workspace, _: &Unfollow, cx: &mut ViewContext<Workspace>| {
 173            let pane = workspace.active_pane().clone();
 174            workspace.unfollow(&pane, cx);
 175        },
 176    );
 177    cx.add_action(
 178        |workspace: &mut Workspace, _: &Save, cx: &mut ViewContext<Workspace>| {
 179            workspace.save_active_item(false, cx).detach_and_log_err(cx);
 180        },
 181    );
 182    cx.add_action(
 183        |workspace: &mut Workspace, _: &SaveAs, cx: &mut ViewContext<Workspace>| {
 184            workspace.save_active_item(true, cx).detach_and_log_err(cx);
 185        },
 186    );
 187    cx.add_action(Workspace::toggle_sidebar_item);
 188    cx.add_action(Workspace::toggle_sidebar_item_focus);
 189    cx.add_action(Workspace::focus_center);
 190    cx.add_action(|workspace: &mut Workspace, _: &ActivatePreviousPane, cx| {
 191        workspace.activate_previous_pane(cx)
 192    });
 193    cx.add_action(|workspace: &mut Workspace, _: &ActivateNextPane, cx| {
 194        workspace.activate_next_pane(cx)
 195    });
 196
 197    let client = &app_state.client;
 198    client.add_view_request_handler(Workspace::handle_follow);
 199    client.add_view_message_handler(Workspace::handle_unfollow);
 200    client.add_view_message_handler(Workspace::handle_update_followers);
 201}
 202
 203pub fn register_project_item<I: ProjectItem>(cx: &mut MutableAppContext) {
 204    cx.update_default_global(|builders: &mut ProjectItemBuilders, _| {
 205        builders.insert(TypeId::of::<I::Item>(), |window_id, project, model, cx| {
 206            let item = model.downcast::<I::Item>().unwrap();
 207            Box::new(cx.add_view(window_id, |cx| I::for_project_item(project, item, cx)))
 208        });
 209    });
 210}
 211
 212pub fn register_followable_item<I: FollowableItem>(cx: &mut MutableAppContext) {
 213    cx.update_default_global(|builders: &mut FollowableItemBuilders, _| {
 214        builders.insert(
 215            TypeId::of::<I>(),
 216            (
 217                |pane, project, state, cx| {
 218                    I::from_state_proto(pane, project, state, cx).map(|task| {
 219                        cx.foreground()
 220                            .spawn(async move { Ok(Box::new(task.await?) as Box<_>) })
 221                    })
 222                },
 223                |this| Box::new(this.downcast::<I>().unwrap()),
 224            ),
 225        );
 226    });
 227}
 228
 229pub struct AppState {
 230    pub languages: Arc<LanguageRegistry>,
 231    pub themes: Arc<ThemeRegistry>,
 232    pub client: Arc<client::Client>,
 233    pub user_store: ModelHandle<client::UserStore>,
 234    pub project_store: ModelHandle<ProjectStore>,
 235    pub fs: Arc<dyn fs::Fs>,
 236    pub build_window_options: fn() -> WindowOptions<'static>,
 237    pub initialize_workspace: fn(&mut Workspace, &Arc<AppState>, &mut ViewContext<Workspace>),
 238}
 239
 240pub trait Item: View {
 241    fn deactivated(&mut self, _: &mut ViewContext<Self>) {}
 242    fn navigate(&mut self, _: Box<dyn Any>, _: &mut ViewContext<Self>) -> bool {
 243        false
 244    }
 245    fn tab_content(&self, style: &theme::Tab, cx: &AppContext) -> ElementBox;
 246    fn project_path(&self, cx: &AppContext) -> Option<ProjectPath>;
 247    fn project_entry_ids(&self, cx: &AppContext) -> SmallVec<[ProjectEntryId; 3]>;
 248    fn is_singleton(&self, cx: &AppContext) -> bool;
 249    fn set_nav_history(&mut self, _: ItemNavHistory, _: &mut ViewContext<Self>);
 250    fn clone_on_split(&self, _: &mut ViewContext<Self>) -> Option<Self>
 251    where
 252        Self: Sized,
 253    {
 254        None
 255    }
 256    fn is_dirty(&self, _: &AppContext) -> bool {
 257        false
 258    }
 259    fn has_conflict(&self, _: &AppContext) -> bool {
 260        false
 261    }
 262    fn can_save(&self, cx: &AppContext) -> bool;
 263    fn save(
 264        &mut self,
 265        project: ModelHandle<Project>,
 266        cx: &mut ViewContext<Self>,
 267    ) -> Task<Result<()>>;
 268    fn save_as(
 269        &mut self,
 270        project: ModelHandle<Project>,
 271        abs_path: PathBuf,
 272        cx: &mut ViewContext<Self>,
 273    ) -> Task<Result<()>>;
 274    fn reload(
 275        &mut self,
 276        project: ModelHandle<Project>,
 277        cx: &mut ViewContext<Self>,
 278    ) -> Task<Result<()>>;
 279    fn should_activate_item_on_event(_: &Self::Event) -> bool {
 280        false
 281    }
 282    fn should_close_item_on_event(_: &Self::Event) -> bool {
 283        false
 284    }
 285    fn should_update_tab_on_event(_: &Self::Event) -> bool {
 286        false
 287    }
 288    fn act_as_type(
 289        &self,
 290        type_id: TypeId,
 291        self_handle: &ViewHandle<Self>,
 292        _: &AppContext,
 293    ) -> Option<AnyViewHandle> {
 294        if TypeId::of::<Self>() == type_id {
 295            Some(self_handle.into())
 296        } else {
 297            None
 298        }
 299    }
 300}
 301
 302pub trait ProjectItem: Item {
 303    type Item: project::Item;
 304
 305    fn for_project_item(
 306        project: ModelHandle<Project>,
 307        item: ModelHandle<Self::Item>,
 308        cx: &mut ViewContext<Self>,
 309    ) -> Self;
 310}
 311
 312pub trait FollowableItem: Item {
 313    fn to_state_proto(&self, cx: &AppContext) -> Option<proto::view::Variant>;
 314    fn from_state_proto(
 315        pane: ViewHandle<Pane>,
 316        project: ModelHandle<Project>,
 317        state: &mut Option<proto::view::Variant>,
 318        cx: &mut MutableAppContext,
 319    ) -> Option<Task<Result<ViewHandle<Self>>>>;
 320    fn add_event_to_update_proto(
 321        &self,
 322        event: &Self::Event,
 323        update: &mut Option<proto::update_view::Variant>,
 324        cx: &AppContext,
 325    ) -> bool;
 326    fn apply_update_proto(
 327        &mut self,
 328        message: proto::update_view::Variant,
 329        cx: &mut ViewContext<Self>,
 330    ) -> Result<()>;
 331
 332    fn set_leader_replica_id(&mut self, leader_replica_id: Option<u16>, cx: &mut ViewContext<Self>);
 333    fn should_unfollow_on_event(event: &Self::Event, cx: &AppContext) -> bool;
 334}
 335
 336pub trait FollowableItemHandle: ItemHandle {
 337    fn set_leader_replica_id(&self, leader_replica_id: Option<u16>, cx: &mut MutableAppContext);
 338    fn to_state_proto(&self, cx: &AppContext) -> Option<proto::view::Variant>;
 339    fn add_event_to_update_proto(
 340        &self,
 341        event: &dyn Any,
 342        update: &mut Option<proto::update_view::Variant>,
 343        cx: &AppContext,
 344    ) -> bool;
 345    fn apply_update_proto(
 346        &self,
 347        message: proto::update_view::Variant,
 348        cx: &mut MutableAppContext,
 349    ) -> Result<()>;
 350    fn should_unfollow_on_event(&self, event: &dyn Any, cx: &AppContext) -> bool;
 351}
 352
 353impl<T: FollowableItem> FollowableItemHandle for ViewHandle<T> {
 354    fn set_leader_replica_id(&self, leader_replica_id: Option<u16>, cx: &mut MutableAppContext) {
 355        self.update(cx, |this, cx| {
 356            this.set_leader_replica_id(leader_replica_id, cx)
 357        })
 358    }
 359
 360    fn to_state_proto(&self, cx: &AppContext) -> Option<proto::view::Variant> {
 361        self.read(cx).to_state_proto(cx)
 362    }
 363
 364    fn add_event_to_update_proto(
 365        &self,
 366        event: &dyn Any,
 367        update: &mut Option<proto::update_view::Variant>,
 368        cx: &AppContext,
 369    ) -> bool {
 370        if let Some(event) = event.downcast_ref() {
 371            self.read(cx).add_event_to_update_proto(event, update, cx)
 372        } else {
 373            false
 374        }
 375    }
 376
 377    fn apply_update_proto(
 378        &self,
 379        message: proto::update_view::Variant,
 380        cx: &mut MutableAppContext,
 381    ) -> Result<()> {
 382        self.update(cx, |this, cx| this.apply_update_proto(message, cx))
 383    }
 384
 385    fn should_unfollow_on_event(&self, event: &dyn Any, cx: &AppContext) -> bool {
 386        if let Some(event) = event.downcast_ref() {
 387            T::should_unfollow_on_event(event, cx)
 388        } else {
 389            false
 390        }
 391    }
 392}
 393
 394pub trait ItemHandle: 'static + fmt::Debug {
 395    fn tab_content(&self, style: &theme::Tab, cx: &AppContext) -> ElementBox;
 396    fn project_path(&self, cx: &AppContext) -> Option<ProjectPath>;
 397    fn project_entry_ids(&self, cx: &AppContext) -> SmallVec<[ProjectEntryId; 3]>;
 398    fn is_singleton(&self, cx: &AppContext) -> bool;
 399    fn boxed_clone(&self) -> Box<dyn ItemHandle>;
 400    fn set_nav_history(&self, nav_history: Rc<RefCell<NavHistory>>, cx: &mut MutableAppContext);
 401    fn clone_on_split(&self, cx: &mut MutableAppContext) -> Option<Box<dyn ItemHandle>>;
 402    fn added_to_pane(
 403        &self,
 404        workspace: &mut Workspace,
 405        pane: ViewHandle<Pane>,
 406        cx: &mut ViewContext<Workspace>,
 407    );
 408    fn deactivated(&self, cx: &mut MutableAppContext);
 409    fn navigate(&self, data: Box<dyn Any>, cx: &mut MutableAppContext) -> bool;
 410    fn id(&self) -> usize;
 411    fn to_any(&self) -> AnyViewHandle;
 412    fn is_dirty(&self, cx: &AppContext) -> bool;
 413    fn has_conflict(&self, cx: &AppContext) -> bool;
 414    fn can_save(&self, cx: &AppContext) -> bool;
 415    fn save(&self, project: ModelHandle<Project>, cx: &mut MutableAppContext) -> Task<Result<()>>;
 416    fn save_as(
 417        &self,
 418        project: ModelHandle<Project>,
 419        abs_path: PathBuf,
 420        cx: &mut MutableAppContext,
 421    ) -> Task<Result<()>>;
 422    fn reload(&self, project: ModelHandle<Project>, cx: &mut MutableAppContext)
 423        -> Task<Result<()>>;
 424    fn act_as_type(&self, type_id: TypeId, cx: &AppContext) -> Option<AnyViewHandle>;
 425    fn to_followable_item_handle(&self, cx: &AppContext) -> Option<Box<dyn FollowableItemHandle>>;
 426    fn on_release(
 427        &self,
 428        cx: &mut MutableAppContext,
 429        callback: Box<dyn FnOnce(&mut MutableAppContext)>,
 430    ) -> gpui::Subscription;
 431}
 432
 433pub trait WeakItemHandle {
 434    fn id(&self) -> usize;
 435    fn upgrade(&self, cx: &AppContext) -> Option<Box<dyn ItemHandle>>;
 436}
 437
 438impl dyn ItemHandle {
 439    pub fn downcast<T: View>(&self) -> Option<ViewHandle<T>> {
 440        self.to_any().downcast()
 441    }
 442
 443    pub fn act_as<T: View>(&self, cx: &AppContext) -> Option<ViewHandle<T>> {
 444        self.act_as_type(TypeId::of::<T>(), cx)
 445            .and_then(|t| t.downcast())
 446    }
 447}
 448
 449impl<T: Item> ItemHandle for ViewHandle<T> {
 450    fn tab_content(&self, style: &theme::Tab, cx: &AppContext) -> ElementBox {
 451        self.read(cx).tab_content(style, cx)
 452    }
 453
 454    fn project_path(&self, cx: &AppContext) -> Option<ProjectPath> {
 455        self.read(cx).project_path(cx)
 456    }
 457
 458    fn project_entry_ids(&self, cx: &AppContext) -> SmallVec<[ProjectEntryId; 3]> {
 459        self.read(cx).project_entry_ids(cx)
 460    }
 461
 462    fn is_singleton(&self, cx: &AppContext) -> bool {
 463        self.read(cx).is_singleton(cx)
 464    }
 465
 466    fn boxed_clone(&self) -> Box<dyn ItemHandle> {
 467        Box::new(self.clone())
 468    }
 469
 470    fn set_nav_history(&self, nav_history: Rc<RefCell<NavHistory>>, cx: &mut MutableAppContext) {
 471        self.update(cx, |item, cx| {
 472            item.set_nav_history(ItemNavHistory::new(nav_history, &cx.handle()), cx);
 473        })
 474    }
 475
 476    fn clone_on_split(&self, cx: &mut MutableAppContext) -> Option<Box<dyn ItemHandle>> {
 477        self.update(cx, |item, cx| {
 478            cx.add_option_view(|cx| item.clone_on_split(cx))
 479        })
 480        .map(|handle| Box::new(handle) as Box<dyn ItemHandle>)
 481    }
 482
 483    fn added_to_pane(
 484        &self,
 485        workspace: &mut Workspace,
 486        pane: ViewHandle<Pane>,
 487        cx: &mut ViewContext<Workspace>,
 488    ) {
 489        if let Some(followed_item) = self.to_followable_item_handle(cx) {
 490            if let Some(message) = followed_item.to_state_proto(cx) {
 491                workspace.update_followers(
 492                    proto::update_followers::Variant::CreateView(proto::View {
 493                        id: followed_item.id() as u64,
 494                        variant: Some(message),
 495                        leader_id: workspace.leader_for_pane(&pane).map(|id| id.0),
 496                    }),
 497                    cx,
 498                );
 499            }
 500        }
 501
 502        let pending_update = Rc::new(RefCell::new(None));
 503        let pending_update_scheduled = Rc::new(AtomicBool::new(false));
 504        let pane = pane.downgrade();
 505        cx.subscribe(self, move |workspace, item, event, cx| {
 506            let pane = if let Some(pane) = pane.upgrade(cx) {
 507                pane
 508            } else {
 509                log::error!("unexpected item event after pane was dropped");
 510                return;
 511            };
 512
 513            if let Some(item) = item.to_followable_item_handle(cx) {
 514                let leader_id = workspace.leader_for_pane(&pane);
 515
 516                if leader_id.is_some() && item.should_unfollow_on_event(event, cx) {
 517                    workspace.unfollow(&pane, cx);
 518                }
 519
 520                if item.add_event_to_update_proto(event, &mut *pending_update.borrow_mut(), cx)
 521                    && !pending_update_scheduled.load(SeqCst)
 522                {
 523                    pending_update_scheduled.store(true, SeqCst);
 524                    cx.after_window_update({
 525                        let pending_update = pending_update.clone();
 526                        let pending_update_scheduled = pending_update_scheduled.clone();
 527                        move |this, cx| {
 528                            pending_update_scheduled.store(false, SeqCst);
 529                            this.update_followers(
 530                                proto::update_followers::Variant::UpdateView(proto::UpdateView {
 531                                    id: item.id() as u64,
 532                                    variant: pending_update.borrow_mut().take(),
 533                                    leader_id: leader_id.map(|id| id.0),
 534                                }),
 535                                cx,
 536                            );
 537                        }
 538                    });
 539                }
 540            }
 541
 542            if T::should_close_item_on_event(event) {
 543                Pane::close_item(workspace, pane, item.id(), cx).detach_and_log_err(cx);
 544                return;
 545            }
 546
 547            if T::should_activate_item_on_event(event) {
 548                pane.update(cx, |pane, cx| {
 549                    if let Some(ix) = pane.index_for_item(&item) {
 550                        pane.activate_item(ix, true, true, cx);
 551                        pane.activate(cx);
 552                    }
 553                });
 554            }
 555
 556            if T::should_update_tab_on_event(event) {
 557                pane.update(cx, |_, cx| {
 558                    cx.emit(pane::Event::ChangeItemTitle);
 559                    cx.notify();
 560                });
 561            }
 562        })
 563        .detach();
 564    }
 565
 566    fn deactivated(&self, cx: &mut MutableAppContext) {
 567        self.update(cx, |this, cx| this.deactivated(cx));
 568    }
 569
 570    fn navigate(&self, data: Box<dyn Any>, cx: &mut MutableAppContext) -> bool {
 571        self.update(cx, |this, cx| this.navigate(data, cx))
 572    }
 573
 574    fn id(&self) -> usize {
 575        self.id()
 576    }
 577
 578    fn to_any(&self) -> AnyViewHandle {
 579        self.into()
 580    }
 581
 582    fn is_dirty(&self, cx: &AppContext) -> bool {
 583        self.read(cx).is_dirty(cx)
 584    }
 585
 586    fn has_conflict(&self, cx: &AppContext) -> bool {
 587        self.read(cx).has_conflict(cx)
 588    }
 589
 590    fn can_save(&self, cx: &AppContext) -> bool {
 591        self.read(cx).can_save(cx)
 592    }
 593
 594    fn save(&self, project: ModelHandle<Project>, cx: &mut MutableAppContext) -> Task<Result<()>> {
 595        self.update(cx, |item, cx| item.save(project, cx))
 596    }
 597
 598    fn save_as(
 599        &self,
 600        project: ModelHandle<Project>,
 601        abs_path: PathBuf,
 602        cx: &mut MutableAppContext,
 603    ) -> Task<anyhow::Result<()>> {
 604        self.update(cx, |item, cx| item.save_as(project, abs_path, cx))
 605    }
 606
 607    fn reload(
 608        &self,
 609        project: ModelHandle<Project>,
 610        cx: &mut MutableAppContext,
 611    ) -> Task<Result<()>> {
 612        self.update(cx, |item, cx| item.reload(project, cx))
 613    }
 614
 615    fn act_as_type(&self, type_id: TypeId, cx: &AppContext) -> Option<AnyViewHandle> {
 616        self.read(cx).act_as_type(type_id, self, cx)
 617    }
 618
 619    fn to_followable_item_handle(&self, cx: &AppContext) -> Option<Box<dyn FollowableItemHandle>> {
 620        if cx.has_global::<FollowableItemBuilders>() {
 621            let builders = cx.global::<FollowableItemBuilders>();
 622            let item = self.to_any();
 623            Some(builders.get(&item.view_type())?.1(item))
 624        } else {
 625            None
 626        }
 627    }
 628
 629    fn on_release(
 630        &self,
 631        cx: &mut MutableAppContext,
 632        callback: Box<dyn FnOnce(&mut MutableAppContext)>,
 633    ) -> gpui::Subscription {
 634        cx.observe_release(self, move |_, cx| callback(cx))
 635    }
 636}
 637
 638impl Into<AnyViewHandle> for Box<dyn ItemHandle> {
 639    fn into(self) -> AnyViewHandle {
 640        self.to_any()
 641    }
 642}
 643
 644impl Clone for Box<dyn ItemHandle> {
 645    fn clone(&self) -> Box<dyn ItemHandle> {
 646        self.boxed_clone()
 647    }
 648}
 649
 650impl<T: Item> WeakItemHandle for WeakViewHandle<T> {
 651    fn id(&self) -> usize {
 652        self.id()
 653    }
 654
 655    fn upgrade(&self, cx: &AppContext) -> Option<Box<dyn ItemHandle>> {
 656        self.upgrade(cx).map(|v| Box::new(v) as Box<dyn ItemHandle>)
 657    }
 658}
 659
 660pub trait Notification: View {
 661    fn should_dismiss_notification_on_event(&self, event: &<Self as Entity>::Event) -> bool;
 662}
 663
 664pub trait NotificationHandle {
 665    fn id(&self) -> usize;
 666    fn to_any(&self) -> AnyViewHandle;
 667}
 668
 669impl<T: Notification> NotificationHandle for ViewHandle<T> {
 670    fn id(&self) -> usize {
 671        self.id()
 672    }
 673
 674    fn to_any(&self) -> AnyViewHandle {
 675        self.into()
 676    }
 677}
 678
 679impl Into<AnyViewHandle> for &dyn NotificationHandle {
 680    fn into(self) -> AnyViewHandle {
 681        self.to_any()
 682    }
 683}
 684
 685impl AppState {
 686    #[cfg(any(test, feature = "test-support"))]
 687    pub fn test(cx: &mut MutableAppContext) -> Arc<Self> {
 688        let settings = Settings::test(cx);
 689        cx.set_global(settings);
 690
 691        let fs = project::FakeFs::new(cx.background().clone());
 692        let languages = Arc::new(LanguageRegistry::test());
 693        let http_client = client::test::FakeHttpClient::with_404_response();
 694        let client = Client::new(http_client.clone());
 695        let project_store = cx.add_model(|_| ProjectStore::new(project::Db::open_fake()));
 696        let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx));
 697        let themes = ThemeRegistry::new((), cx.font_cache().clone());
 698        Arc::new(Self {
 699            client,
 700            themes,
 701            fs,
 702            languages,
 703            user_store,
 704            project_store,
 705            initialize_workspace: |_, _, _| {},
 706            build_window_options: || Default::default(),
 707        })
 708    }
 709}
 710
 711pub enum Event {
 712    PaneAdded(ViewHandle<Pane>),
 713    ContactRequestedJoin(u64),
 714}
 715
 716pub struct Workspace {
 717    weak_self: WeakViewHandle<Self>,
 718    client: Arc<Client>,
 719    user_store: ModelHandle<client::UserStore>,
 720    remote_entity_subscription: Option<Subscription>,
 721    fs: Arc<dyn Fs>,
 722    modal: Option<AnyViewHandle>,
 723    center: PaneGroup,
 724    left_sidebar: ViewHandle<Sidebar>,
 725    right_sidebar: ViewHandle<Sidebar>,
 726    panes: Vec<ViewHandle<Pane>>,
 727    active_pane: ViewHandle<Pane>,
 728    status_bar: ViewHandle<StatusBar>,
 729    notifications: Vec<(TypeId, usize, Box<dyn NotificationHandle>)>,
 730    project: ModelHandle<Project>,
 731    leader_state: LeaderState,
 732    follower_states_by_leader: FollowerStatesByLeader,
 733    last_leaders_by_pane: HashMap<WeakViewHandle<Pane>, PeerId>,
 734    window_edited: bool,
 735    _observe_current_user: Task<()>,
 736}
 737
 738#[derive(Default)]
 739struct LeaderState {
 740    followers: HashSet<PeerId>,
 741}
 742
 743type FollowerStatesByLeader = HashMap<PeerId, HashMap<ViewHandle<Pane>, FollowerState>>;
 744
 745#[derive(Default)]
 746struct FollowerState {
 747    active_view_id: Option<u64>,
 748    items_by_leader_view_id: HashMap<u64, FollowerItem>,
 749}
 750
 751#[derive(Debug)]
 752enum FollowerItem {
 753    Loading(Vec<proto::update_view::Variant>),
 754    Loaded(Box<dyn FollowableItemHandle>),
 755}
 756
 757impl Workspace {
 758    pub fn new(project: ModelHandle<Project>, cx: &mut ViewContext<Self>) -> Self {
 759        cx.observe(&project, |_, project, cx| {
 760            if project.read(cx).is_read_only() {
 761                cx.blur();
 762            }
 763            cx.notify()
 764        })
 765        .detach();
 766
 767        cx.subscribe(&project, move |this, project, event, cx| {
 768            match event {
 769                project::Event::RemoteIdChanged(remote_id) => {
 770                    this.project_remote_id_changed(*remote_id, cx);
 771                }
 772                project::Event::CollaboratorLeft(peer_id) => {
 773                    this.collaborator_left(*peer_id, cx);
 774                }
 775                project::Event::WorktreeRemoved(_) | project::Event::WorktreeAdded => {
 776                    this.update_window_title(cx);
 777                }
 778                _ => {}
 779            }
 780            if project.read(cx).is_read_only() {
 781                cx.blur();
 782            }
 783            cx.notify()
 784        })
 785        .detach();
 786
 787        let pane = cx.add_view(|cx| Pane::new(cx));
 788        let pane_id = pane.id();
 789        cx.subscribe(&pane, move |this, _, event, cx| {
 790            this.handle_pane_event(pane_id, event, cx)
 791        })
 792        .detach();
 793        cx.focus(&pane);
 794        cx.emit(Event::PaneAdded(pane.clone()));
 795
 796        let fs = project.read(cx).fs().clone();
 797        let user_store = project.read(cx).user_store();
 798        let client = project.read(cx).client();
 799        let mut current_user = user_store.read(cx).watch_current_user().clone();
 800        let mut connection_status = client.status().clone();
 801        let _observe_current_user = cx.spawn_weak(|this, mut cx| async move {
 802            current_user.recv().await;
 803            connection_status.recv().await;
 804            let mut stream =
 805                Stream::map(current_user, drop).merge(Stream::map(connection_status, drop));
 806
 807            while stream.recv().await.is_some() {
 808                cx.update(|cx| {
 809                    if let Some(this) = this.upgrade(cx) {
 810                        this.update(cx, |_, cx| cx.notify());
 811                    }
 812                })
 813            }
 814        });
 815
 816        let weak_self = cx.weak_handle();
 817
 818        cx.emit_global(WorkspaceCreated(weak_self.clone()));
 819
 820        let left_sidebar = cx.add_view(|_| Sidebar::new(Side::Left));
 821        let right_sidebar = cx.add_view(|_| Sidebar::new(Side::Right));
 822        let left_sidebar_buttons = cx.add_view(|cx| SidebarButtons::new(left_sidebar.clone(), cx));
 823        let right_sidebar_buttons =
 824            cx.add_view(|cx| SidebarButtons::new(right_sidebar.clone(), cx));
 825        let status_bar = cx.add_view(|cx| {
 826            let mut status_bar = StatusBar::new(&pane.clone(), cx);
 827            status_bar.add_left_item(left_sidebar_buttons, cx);
 828            status_bar.add_right_item(right_sidebar_buttons, cx);
 829            status_bar
 830        });
 831
 832        let mut this = Workspace {
 833            modal: None,
 834            weak_self,
 835            center: PaneGroup::new(pane.clone()),
 836            panes: vec![pane.clone()],
 837            active_pane: pane.clone(),
 838            status_bar,
 839            notifications: Default::default(),
 840            client,
 841            remote_entity_subscription: None,
 842            user_store,
 843            fs,
 844            left_sidebar,
 845            right_sidebar,
 846            project,
 847            leader_state: Default::default(),
 848            follower_states_by_leader: Default::default(),
 849            last_leaders_by_pane: Default::default(),
 850            window_edited: false,
 851            _observe_current_user,
 852        };
 853        this.project_remote_id_changed(this.project.read(cx).remote_id(), cx);
 854        cx.defer(|this, cx| this.update_window_title(cx));
 855
 856        this
 857    }
 858
 859    pub fn weak_handle(&self) -> WeakViewHandle<Self> {
 860        self.weak_self.clone()
 861    }
 862
 863    pub fn left_sidebar(&self) -> &ViewHandle<Sidebar> {
 864        &self.left_sidebar
 865    }
 866
 867    pub fn right_sidebar(&self) -> &ViewHandle<Sidebar> {
 868        &self.right_sidebar
 869    }
 870
 871    pub fn status_bar(&self) -> &ViewHandle<StatusBar> {
 872        &self.status_bar
 873    }
 874
 875    pub fn user_store(&self) -> &ModelHandle<UserStore> {
 876        &self.user_store
 877    }
 878
 879    pub fn project(&self) -> &ModelHandle<Project> {
 880        &self.project
 881    }
 882
 883    pub fn worktrees<'a>(
 884        &self,
 885        cx: &'a AppContext,
 886    ) -> impl 'a + Iterator<Item = ModelHandle<Worktree>> {
 887        self.project.read(cx).worktrees(cx)
 888    }
 889
 890    pub fn visible_worktrees<'a>(
 891        &self,
 892        cx: &'a AppContext,
 893    ) -> impl 'a + Iterator<Item = ModelHandle<Worktree>> {
 894        self.project.read(cx).visible_worktrees(cx)
 895    }
 896
 897    pub fn worktree_scans_complete(&self, cx: &AppContext) -> impl Future<Output = ()> + 'static {
 898        let futures = self
 899            .worktrees(cx)
 900            .filter_map(|worktree| worktree.read(cx).as_local())
 901            .map(|worktree| worktree.scan_complete())
 902            .collect::<Vec<_>>();
 903        async move {
 904            for future in futures {
 905                future.await;
 906            }
 907        }
 908    }
 909
 910    pub fn close(
 911        &mut self,
 912        _: &CloseWindow,
 913        cx: &mut ViewContext<Self>,
 914    ) -> Option<Task<Result<()>>> {
 915        let prepare = self.prepare_to_close(cx);
 916        Some(cx.spawn(|this, mut cx| async move {
 917            if prepare.await? {
 918                this.update(&mut cx, |_, cx| {
 919                    let window_id = cx.window_id();
 920                    cx.remove_window(window_id);
 921                });
 922            }
 923            Ok(())
 924        }))
 925    }
 926
 927    pub fn prepare_to_close(&mut self, cx: &mut ViewContext<Self>) -> Task<Result<bool>> {
 928        self.save_all_internal(true, cx)
 929    }
 930
 931    fn save_all(&mut self, _: &SaveAll, cx: &mut ViewContext<Self>) -> Option<Task<Result<()>>> {
 932        let save_all = self.save_all_internal(false, cx);
 933        Some(cx.foreground().spawn(async move {
 934            save_all.await?;
 935            Ok(())
 936        }))
 937    }
 938
 939    fn save_all_internal(
 940        &mut self,
 941        should_prompt_to_save: bool,
 942        cx: &mut ViewContext<Self>,
 943    ) -> Task<Result<bool>> {
 944        let dirty_items = self
 945            .panes
 946            .iter()
 947            .flat_map(|pane| {
 948                pane.read(cx).items().filter_map(|item| {
 949                    if item.is_dirty(cx) {
 950                        Some((pane.clone(), item.boxed_clone()))
 951                    } else {
 952                        None
 953                    }
 954                })
 955            })
 956            .collect::<Vec<_>>();
 957
 958        let project = self.project.clone();
 959        cx.spawn_weak(|_, mut cx| async move {
 960            // let mut saved_project_entry_ids = HashSet::default();
 961            for (pane, item) in dirty_items {
 962                let (is_singl, project_entry_ids) =
 963                    cx.read(|cx| (item.is_singleton(cx), item.project_entry_ids(cx)));
 964                if is_singl || !project_entry_ids.is_empty() {
 965                    if let Some(ix) =
 966                        pane.read_with(&cx, |pane, _| pane.index_for_item(item.as_ref()))
 967                    {
 968                        if !Pane::save_item(
 969                            project.clone(),
 970                            &pane,
 971                            ix,
 972                            &item,
 973                            should_prompt_to_save,
 974                            &mut cx,
 975                        )
 976                        .await?
 977                        {
 978                            return Ok(false);
 979                        }
 980                    }
 981                }
 982            }
 983            Ok(true)
 984        })
 985    }
 986
 987    pub fn open_paths(
 988        &mut self,
 989        mut abs_paths: Vec<PathBuf>,
 990        visible: bool,
 991        cx: &mut ViewContext<Self>,
 992    ) -> Task<Vec<Option<Result<Box<dyn ItemHandle>, Arc<anyhow::Error>>>>> {
 993        let fs = self.fs.clone();
 994
 995        // Sort the paths to ensure we add worktrees for parents before their children.
 996        abs_paths.sort_unstable();
 997        cx.spawn(|this, mut cx| async move {
 998            let mut project_paths = Vec::new();
 999            for path in &abs_paths {
1000                project_paths.push(
1001                    this.update(&mut cx, |this, cx| {
1002                        this.project_path_for_path(path, visible, cx)
1003                    })
1004                    .await
1005                    .log_err(),
1006                );
1007            }
1008
1009            let tasks = abs_paths
1010                .iter()
1011                .cloned()
1012                .zip(project_paths.into_iter())
1013                .map(|(abs_path, project_path)| {
1014                    let this = this.clone();
1015                    cx.spawn(|mut cx| {
1016                        let fs = fs.clone();
1017                        async move {
1018                            let (_worktree, project_path) = project_path?;
1019                            if fs.is_file(&abs_path).await {
1020                                Some(
1021                                    this.update(&mut cx, |this, cx| {
1022                                        this.open_path(project_path, true, cx)
1023                                    })
1024                                    .await,
1025                                )
1026                            } else {
1027                                None
1028                            }
1029                        }
1030                    })
1031                })
1032                .collect::<Vec<_>>();
1033
1034            futures::future::join_all(tasks).await
1035        })
1036    }
1037
1038    fn add_folder_to_project(&mut self, _: &AddFolderToProject, cx: &mut ViewContext<Self>) {
1039        let mut paths = cx.prompt_for_paths(PathPromptOptions {
1040            files: false,
1041            directories: true,
1042            multiple: true,
1043        });
1044        cx.spawn(|this, mut cx| async move {
1045            if let Some(paths) = paths.recv().await.flatten() {
1046                let results = this
1047                    .update(&mut cx, |this, cx| this.open_paths(paths, true, cx))
1048                    .await;
1049                for result in results {
1050                    if let Some(result) = result {
1051                        result.log_err();
1052                    }
1053                }
1054            }
1055        })
1056        .detach();
1057    }
1058
1059    fn remove_folder_from_project(
1060        &mut self,
1061        RemoveWorktreeFromProject(worktree_id): &RemoveWorktreeFromProject,
1062        cx: &mut ViewContext<Self>,
1063    ) {
1064        self.project
1065            .update(cx, |project, cx| project.remove_worktree(*worktree_id, cx));
1066    }
1067
1068    fn toggle_project_online(&mut self, action: &ToggleProjectOnline, cx: &mut ViewContext<Self>) {
1069        let project = action
1070            .project
1071            .clone()
1072            .unwrap_or_else(|| self.project.clone());
1073        project.update(cx, |project, cx| {
1074            let public = !project.is_online();
1075            project.set_online(public, cx);
1076        });
1077    }
1078
1079    fn project_path_for_path(
1080        &self,
1081        abs_path: &Path,
1082        visible: bool,
1083        cx: &mut ViewContext<Self>,
1084    ) -> Task<Result<(ModelHandle<Worktree>, ProjectPath)>> {
1085        let entry = self.project().update(cx, |project, cx| {
1086            project.find_or_create_local_worktree(abs_path, visible, cx)
1087        });
1088        cx.spawn(|_, cx| async move {
1089            let (worktree, path) = entry.await?;
1090            let worktree_id = worktree.read_with(&cx, |t, _| t.id());
1091            Ok((
1092                worktree,
1093                ProjectPath {
1094                    worktree_id,
1095                    path: path.into(),
1096                },
1097            ))
1098        })
1099    }
1100
1101    /// Returns the modal that was toggled closed if it was open.
1102    pub fn toggle_modal<V, F>(
1103        &mut self,
1104        cx: &mut ViewContext<Self>,
1105        add_view: F,
1106    ) -> Option<ViewHandle<V>>
1107    where
1108        V: 'static + View,
1109        F: FnOnce(&mut Self, &mut ViewContext<Self>) -> ViewHandle<V>,
1110    {
1111        cx.notify();
1112        // Whatever modal was visible is getting clobbered. If its the same type as V, then return
1113        // it. Otherwise, create a new modal and set it as active.
1114        let already_open_modal = self.modal.take().and_then(|modal| modal.downcast::<V>());
1115        if let Some(already_open_modal) = already_open_modal {
1116            cx.focus_self();
1117            Some(already_open_modal)
1118        } else {
1119            let modal = add_view(self, cx);
1120            cx.focus(&modal);
1121            self.modal = Some(modal.into());
1122            None
1123        }
1124    }
1125
1126    pub fn modal(&self) -> Option<&AnyViewHandle> {
1127        self.modal.as_ref()
1128    }
1129
1130    pub fn dismiss_modal(&mut self, cx: &mut ViewContext<Self>) {
1131        if self.modal.take().is_some() {
1132            cx.focus(&self.active_pane);
1133            cx.notify();
1134        }
1135    }
1136
1137    pub fn show_notification<V: Notification>(
1138        &mut self,
1139        id: usize,
1140        cx: &mut ViewContext<Self>,
1141        build_notification: impl FnOnce(&mut ViewContext<Self>) -> ViewHandle<V>,
1142    ) {
1143        let type_id = TypeId::of::<V>();
1144        if self
1145            .notifications
1146            .iter()
1147            .all(|(existing_type_id, existing_id, _)| {
1148                (*existing_type_id, *existing_id) != (type_id, id)
1149            })
1150        {
1151            let notification = build_notification(cx);
1152            cx.subscribe(&notification, move |this, handle, event, cx| {
1153                if handle.read(cx).should_dismiss_notification_on_event(event) {
1154                    this.dismiss_notification(type_id, id, cx);
1155                }
1156            })
1157            .detach();
1158            self.notifications
1159                .push((type_id, id, Box::new(notification)));
1160            cx.notify();
1161        }
1162    }
1163
1164    fn dismiss_notification(&mut self, type_id: TypeId, id: usize, cx: &mut ViewContext<Self>) {
1165        self.notifications
1166            .retain(|(existing_type_id, existing_id, _)| {
1167                if (*existing_type_id, *existing_id) == (type_id, id) {
1168                    cx.notify();
1169                    false
1170                } else {
1171                    true
1172                }
1173            });
1174    }
1175
1176    pub fn items<'a>(
1177        &'a self,
1178        cx: &'a AppContext,
1179    ) -> impl 'a + Iterator<Item = &Box<dyn ItemHandle>> {
1180        self.panes.iter().flat_map(|pane| pane.read(cx).items())
1181    }
1182
1183    pub fn item_of_type<T: Item>(&self, cx: &AppContext) -> Option<ViewHandle<T>> {
1184        self.items_of_type(cx).max_by_key(|item| item.id())
1185    }
1186
1187    pub fn items_of_type<'a, T: Item>(
1188        &'a self,
1189        cx: &'a AppContext,
1190    ) -> impl 'a + Iterator<Item = ViewHandle<T>> {
1191        self.panes
1192            .iter()
1193            .flat_map(|pane| pane.read(cx).items_of_type())
1194    }
1195
1196    pub fn active_item(&self, cx: &AppContext) -> Option<Box<dyn ItemHandle>> {
1197        self.active_pane().read(cx).active_item()
1198    }
1199
1200    fn active_project_path(&self, cx: &ViewContext<Self>) -> Option<ProjectPath> {
1201        self.active_item(cx).and_then(|item| item.project_path(cx))
1202    }
1203
1204    pub fn save_active_item(
1205        &mut self,
1206        force_name_change: bool,
1207        cx: &mut ViewContext<Self>,
1208    ) -> Task<Result<()>> {
1209        let project = self.project.clone();
1210        if let Some(item) = self.active_item(cx) {
1211            if !force_name_change && item.can_save(cx) {
1212                if item.has_conflict(cx.as_ref()) {
1213                    const CONFLICT_MESSAGE: &'static str = "This file has changed on disk since you started editing it. Do you want to overwrite it?";
1214
1215                    let mut answer = cx.prompt(
1216                        PromptLevel::Warning,
1217                        CONFLICT_MESSAGE,
1218                        &["Overwrite", "Cancel"],
1219                    );
1220                    cx.spawn(|_, mut cx| async move {
1221                        let answer = answer.recv().await;
1222                        if answer == Some(0) {
1223                            cx.update(|cx| item.save(project, cx)).await?;
1224                        }
1225                        Ok(())
1226                    })
1227                } else {
1228                    item.save(project, cx)
1229                }
1230            } else if item.is_singleton(cx) {
1231                let worktree = self.worktrees(cx).next();
1232                let start_abs_path = worktree
1233                    .and_then(|w| w.read(cx).as_local())
1234                    .map_or(Path::new(""), |w| w.abs_path())
1235                    .to_path_buf();
1236                let mut abs_path = cx.prompt_for_new_path(&start_abs_path);
1237                cx.spawn(|_, mut cx| async move {
1238                    if let Some(abs_path) = abs_path.recv().await.flatten() {
1239                        cx.update(|cx| item.save_as(project, abs_path, cx)).await?;
1240                    }
1241                    Ok(())
1242                })
1243            } else {
1244                Task::ready(Ok(()))
1245            }
1246        } else {
1247            Task::ready(Ok(()))
1248        }
1249    }
1250
1251    pub fn toggle_sidebar_item(&mut self, action: &ToggleSidebarItem, cx: &mut ViewContext<Self>) {
1252        let sidebar = match action.side {
1253            Side::Left => &mut self.left_sidebar,
1254            Side::Right => &mut self.right_sidebar,
1255        };
1256        let active_item = sidebar.update(cx, |sidebar, cx| {
1257            sidebar.toggle_item(action.item_index, cx);
1258            sidebar.active_item().map(|item| item.to_any())
1259        });
1260        if let Some(active_item) = active_item {
1261            cx.focus(active_item);
1262        } else {
1263            cx.focus_self();
1264        }
1265        cx.notify();
1266    }
1267
1268    pub fn toggle_sidebar_item_focus(
1269        &mut self,
1270        action: &ToggleSidebarItemFocus,
1271        cx: &mut ViewContext<Self>,
1272    ) {
1273        let sidebar = match action.side {
1274            Side::Left => &mut self.left_sidebar,
1275            Side::Right => &mut self.right_sidebar,
1276        };
1277        let active_item = sidebar.update(cx, |sidebar, cx| {
1278            sidebar.activate_item(action.item_index, cx);
1279            sidebar.active_item().cloned()
1280        });
1281        if let Some(active_item) = active_item {
1282            if active_item.is_focused(cx) {
1283                cx.focus_self();
1284            } else {
1285                cx.focus(active_item.to_any());
1286            }
1287        }
1288        cx.notify();
1289    }
1290
1291    pub fn focus_center(&mut self, _: &menu::Cancel, cx: &mut ViewContext<Self>) {
1292        cx.focus_self();
1293        cx.notify();
1294    }
1295
1296    fn add_pane(&mut self, cx: &mut ViewContext<Self>) -> ViewHandle<Pane> {
1297        let pane = cx.add_view(|cx| Pane::new(cx));
1298        let pane_id = pane.id();
1299        cx.subscribe(&pane, move |this, _, event, cx| {
1300            this.handle_pane_event(pane_id, event, cx)
1301        })
1302        .detach();
1303        self.panes.push(pane.clone());
1304        self.activate_pane(pane.clone(), cx);
1305        cx.emit(Event::PaneAdded(pane.clone()));
1306        pane
1307    }
1308
1309    pub fn add_item(&mut self, item: Box<dyn ItemHandle>, cx: &mut ViewContext<Self>) {
1310        let pane = self.active_pane().clone();
1311        Pane::add_item(self, pane, item, true, true, cx);
1312    }
1313
1314    pub fn open_path(
1315        &mut self,
1316        path: impl Into<ProjectPath>,
1317        focus_item: bool,
1318        cx: &mut ViewContext<Self>,
1319    ) -> Task<Result<Box<dyn ItemHandle>, Arc<anyhow::Error>>> {
1320        let pane = self.active_pane().downgrade();
1321        let task = self.load_path(path.into(), cx);
1322        cx.spawn(|this, mut cx| async move {
1323            let (project_entry_id, build_item) = task.await?;
1324            let pane = pane
1325                .upgrade(&cx)
1326                .ok_or_else(|| anyhow!("pane was closed"))?;
1327            this.update(&mut cx, |this, cx| {
1328                Ok(Pane::open_item(
1329                    this,
1330                    pane,
1331                    project_entry_id,
1332                    focus_item,
1333                    cx,
1334                    build_item,
1335                ))
1336            })
1337        })
1338    }
1339
1340    pub(crate) fn load_path(
1341        &mut self,
1342        path: ProjectPath,
1343        cx: &mut ViewContext<Self>,
1344    ) -> Task<
1345        Result<(
1346            ProjectEntryId,
1347            impl 'static + FnOnce(&mut MutableAppContext) -> Box<dyn ItemHandle>,
1348        )>,
1349    > {
1350        let project = self.project().clone();
1351        let project_item = project.update(cx, |project, cx| project.open_path(path, cx));
1352        let window_id = cx.window_id();
1353        cx.as_mut().spawn(|mut cx| async move {
1354            let (project_entry_id, project_item) = project_item.await?;
1355            let build_item = cx.update(|cx| {
1356                cx.default_global::<ProjectItemBuilders>()
1357                    .get(&project_item.model_type())
1358                    .ok_or_else(|| anyhow!("no item builder for project item"))
1359                    .cloned()
1360            })?;
1361            let build_item =
1362                move |cx: &mut MutableAppContext| build_item(window_id, project, project_item, cx);
1363            Ok((project_entry_id, build_item))
1364        })
1365    }
1366
1367    pub fn open_project_item<T>(
1368        &mut self,
1369        project_item: ModelHandle<T::Item>,
1370        cx: &mut ViewContext<Self>,
1371    ) -> ViewHandle<T>
1372    where
1373        T: ProjectItem,
1374    {
1375        use project::Item as _;
1376
1377        let entry_id = project_item.read(cx).entry_id(cx);
1378        if let Some(item) = entry_id
1379            .and_then(|entry_id| self.active_pane().read(cx).item_for_entry(entry_id, cx))
1380            .and_then(|item| item.downcast())
1381        {
1382            self.activate_item(&item, cx);
1383            return item;
1384        }
1385
1386        let item = cx.add_view(|cx| T::for_project_item(self.project().clone(), project_item, cx));
1387        self.add_item(Box::new(item.clone()), cx);
1388        item
1389    }
1390
1391    pub fn activate_item(&mut self, item: &dyn ItemHandle, cx: &mut ViewContext<Self>) -> bool {
1392        let result = self.panes.iter().find_map(|pane| {
1393            if let Some(ix) = pane.read(cx).index_for_item(item) {
1394                Some((pane.clone(), ix))
1395            } else {
1396                None
1397            }
1398        });
1399        if let Some((pane, ix)) = result {
1400            self.activate_pane(pane.clone(), cx);
1401            pane.update(cx, |pane, cx| pane.activate_item(ix, true, true, cx));
1402            true
1403        } else {
1404            false
1405        }
1406    }
1407
1408    pub fn activate_next_pane(&mut self, cx: &mut ViewContext<Self>) {
1409        let next_pane = {
1410            let panes = self.center.panes();
1411            let ix = panes
1412                .iter()
1413                .position(|pane| **pane == self.active_pane)
1414                .unwrap();
1415            let next_ix = (ix + 1) % panes.len();
1416            panes[next_ix].clone()
1417        };
1418        self.activate_pane(next_pane, cx);
1419    }
1420
1421    pub fn activate_previous_pane(&mut self, cx: &mut ViewContext<Self>) {
1422        let prev_pane = {
1423            let panes = self.center.panes();
1424            let ix = panes
1425                .iter()
1426                .position(|pane| **pane == self.active_pane)
1427                .unwrap();
1428            let prev_ix = if ix == 0 { panes.len() - 1 } else { ix - 1 };
1429            panes[prev_ix].clone()
1430        };
1431        self.activate_pane(prev_pane, cx);
1432    }
1433
1434    fn activate_pane(&mut self, pane: ViewHandle<Pane>, cx: &mut ViewContext<Self>) {
1435        if self.active_pane != pane {
1436            self.active_pane = pane.clone();
1437            self.status_bar.update(cx, |status_bar, cx| {
1438                status_bar.set_active_pane(&self.active_pane, cx);
1439            });
1440            self.active_item_path_changed(cx);
1441            cx.focus(&self.active_pane);
1442            cx.notify();
1443        }
1444
1445        self.update_followers(
1446            proto::update_followers::Variant::UpdateActiveView(proto::UpdateActiveView {
1447                id: self.active_item(cx).map(|item| item.id() as u64),
1448                leader_id: self.leader_for_pane(&pane).map(|id| id.0),
1449            }),
1450            cx,
1451        );
1452    }
1453
1454    fn handle_pane_event(
1455        &mut self,
1456        pane_id: usize,
1457        event: &pane::Event,
1458        cx: &mut ViewContext<Self>,
1459    ) {
1460        if let Some(pane) = self.pane(pane_id) {
1461            match event {
1462                pane::Event::Split(direction) => {
1463                    self.split_pane(pane, *direction, cx);
1464                }
1465                pane::Event::Remove => {
1466                    self.remove_pane(pane, cx);
1467                }
1468                pane::Event::Activate => {
1469                    self.activate_pane(pane, cx);
1470                }
1471                pane::Event::ActivateItem { local } => {
1472                    if *local {
1473                        self.unfollow(&pane, cx);
1474                    }
1475                    if pane == self.active_pane {
1476                        self.active_item_path_changed(cx);
1477                    }
1478                }
1479                pane::Event::ChangeItemTitle => {
1480                    if pane == self.active_pane {
1481                        self.active_item_path_changed(cx);
1482                    }
1483                    self.update_window_edited(cx);
1484                }
1485                pane::Event::RemoveItem => {
1486                    self.update_window_edited(cx);
1487                }
1488            }
1489        } else {
1490            error!("pane {} not found", pane_id);
1491        }
1492    }
1493
1494    pub fn split_pane(
1495        &mut self,
1496        pane: ViewHandle<Pane>,
1497        direction: SplitDirection,
1498        cx: &mut ViewContext<Self>,
1499    ) -> ViewHandle<Pane> {
1500        let new_pane = self.add_pane(cx);
1501        self.activate_pane(new_pane.clone(), cx);
1502        if let Some(item) = pane.read(cx).active_item() {
1503            if let Some(clone) = item.clone_on_split(cx.as_mut()) {
1504                Pane::add_item(self, new_pane.clone(), clone, true, true, cx);
1505            }
1506        }
1507        self.center.split(&pane, &new_pane, direction).unwrap();
1508        cx.notify();
1509        new_pane
1510    }
1511
1512    fn remove_pane(&mut self, pane: ViewHandle<Pane>, cx: &mut ViewContext<Self>) {
1513        if self.center.remove(&pane).unwrap() {
1514            self.panes.retain(|p| p != &pane);
1515            self.activate_pane(self.panes.last().unwrap().clone(), cx);
1516            self.unfollow(&pane, cx);
1517            self.last_leaders_by_pane.remove(&pane.downgrade());
1518            cx.notify();
1519        } else {
1520            self.active_item_path_changed(cx);
1521        }
1522    }
1523
1524    pub fn panes(&self) -> &[ViewHandle<Pane>] {
1525        &self.panes
1526    }
1527
1528    fn pane(&self, pane_id: usize) -> Option<ViewHandle<Pane>> {
1529        self.panes.iter().find(|pane| pane.id() == pane_id).cloned()
1530    }
1531
1532    pub fn active_pane(&self) -> &ViewHandle<Pane> {
1533        &self.active_pane
1534    }
1535
1536    fn project_remote_id_changed(&mut self, remote_id: Option<u64>, cx: &mut ViewContext<Self>) {
1537        if let Some(remote_id) = remote_id {
1538            self.remote_entity_subscription =
1539                Some(self.client.add_view_for_remote_entity(remote_id, cx));
1540        } else {
1541            self.remote_entity_subscription.take();
1542        }
1543    }
1544
1545    fn collaborator_left(&mut self, peer_id: PeerId, cx: &mut ViewContext<Self>) {
1546        self.leader_state.followers.remove(&peer_id);
1547        if let Some(states_by_pane) = self.follower_states_by_leader.remove(&peer_id) {
1548            for state in states_by_pane.into_values() {
1549                for item in state.items_by_leader_view_id.into_values() {
1550                    if let FollowerItem::Loaded(item) = item {
1551                        item.set_leader_replica_id(None, cx);
1552                    }
1553                }
1554            }
1555        }
1556        cx.notify();
1557    }
1558
1559    pub fn toggle_follow(
1560        &mut self,
1561        ToggleFollow(leader_id): &ToggleFollow,
1562        cx: &mut ViewContext<Self>,
1563    ) -> Option<Task<Result<()>>> {
1564        let leader_id = *leader_id;
1565        let pane = self.active_pane().clone();
1566
1567        if let Some(prev_leader_id) = self.unfollow(&pane, cx) {
1568            if leader_id == prev_leader_id {
1569                return None;
1570            }
1571        }
1572
1573        self.last_leaders_by_pane
1574            .insert(pane.downgrade(), leader_id);
1575        self.follower_states_by_leader
1576            .entry(leader_id)
1577            .or_default()
1578            .insert(pane.clone(), Default::default());
1579        cx.notify();
1580
1581        let project_id = self.project.read(cx).remote_id()?;
1582        let request = self.client.request(proto::Follow {
1583            project_id,
1584            leader_id: leader_id.0,
1585        });
1586        Some(cx.spawn_weak(|this, mut cx| async move {
1587            let response = request.await?;
1588            if let Some(this) = this.upgrade(&cx) {
1589                this.update(&mut cx, |this, _| {
1590                    let state = this
1591                        .follower_states_by_leader
1592                        .get_mut(&leader_id)
1593                        .and_then(|states_by_pane| states_by_pane.get_mut(&pane))
1594                        .ok_or_else(|| anyhow!("following interrupted"))?;
1595                    state.active_view_id = response.active_view_id;
1596                    Ok::<_, anyhow::Error>(())
1597                })?;
1598                Self::add_views_from_leader(this, leader_id, vec![pane], response.views, &mut cx)
1599                    .await?;
1600            }
1601            Ok(())
1602        }))
1603    }
1604
1605    pub fn follow_next_collaborator(
1606        &mut self,
1607        _: &FollowNextCollaborator,
1608        cx: &mut ViewContext<Self>,
1609    ) -> Option<Task<Result<()>>> {
1610        let collaborators = self.project.read(cx).collaborators();
1611        let next_leader_id = if let Some(leader_id) = self.leader_for_pane(&self.active_pane) {
1612            let mut collaborators = collaborators.keys().copied();
1613            while let Some(peer_id) = collaborators.next() {
1614                if peer_id == leader_id {
1615                    break;
1616                }
1617            }
1618            collaborators.next()
1619        } else if let Some(last_leader_id) =
1620            self.last_leaders_by_pane.get(&self.active_pane.downgrade())
1621        {
1622            if collaborators.contains_key(last_leader_id) {
1623                Some(*last_leader_id)
1624            } else {
1625                None
1626            }
1627        } else {
1628            None
1629        };
1630
1631        next_leader_id
1632            .or_else(|| collaborators.keys().copied().next())
1633            .and_then(|leader_id| self.toggle_follow(&ToggleFollow(leader_id), cx))
1634    }
1635
1636    pub fn unfollow(
1637        &mut self,
1638        pane: &ViewHandle<Pane>,
1639        cx: &mut ViewContext<Self>,
1640    ) -> Option<PeerId> {
1641        for (leader_id, states_by_pane) in &mut self.follower_states_by_leader {
1642            let leader_id = *leader_id;
1643            if let Some(state) = states_by_pane.remove(&pane) {
1644                for (_, item) in state.items_by_leader_view_id {
1645                    if let FollowerItem::Loaded(item) = item {
1646                        item.set_leader_replica_id(None, cx);
1647                    }
1648                }
1649
1650                if states_by_pane.is_empty() {
1651                    self.follower_states_by_leader.remove(&leader_id);
1652                    if let Some(project_id) = self.project.read(cx).remote_id() {
1653                        self.client
1654                            .send(proto::Unfollow {
1655                                project_id,
1656                                leader_id: leader_id.0,
1657                            })
1658                            .log_err();
1659                    }
1660                }
1661
1662                cx.notify();
1663                return Some(leader_id);
1664            }
1665        }
1666        None
1667    }
1668
1669    fn render_connection_status(&self, cx: &mut RenderContext<Self>) -> Option<ElementBox> {
1670        let theme = &cx.global::<Settings>().theme;
1671        match &*self.client.status().borrow() {
1672            client::Status::ConnectionError
1673            | client::Status::ConnectionLost
1674            | client::Status::Reauthenticating
1675            | client::Status::Reconnecting { .. }
1676            | client::Status::ReconnectionError { .. } => Some(
1677                Container::new(
1678                    Align::new(
1679                        ConstrainedBox::new(
1680                            Svg::new("icons/offline-14.svg")
1681                                .with_color(theme.workspace.titlebar.offline_icon.color)
1682                                .boxed(),
1683                        )
1684                        .with_width(theme.workspace.titlebar.offline_icon.width)
1685                        .boxed(),
1686                    )
1687                    .boxed(),
1688                )
1689                .with_style(theme.workspace.titlebar.offline_icon.container)
1690                .boxed(),
1691            ),
1692            client::Status::UpgradeRequired => Some(
1693                Label::new(
1694                    "Please update Zed to collaborate".to_string(),
1695                    theme.workspace.titlebar.outdated_warning.text.clone(),
1696                )
1697                .contained()
1698                .with_style(theme.workspace.titlebar.outdated_warning.container)
1699                .aligned()
1700                .boxed(),
1701            ),
1702            _ => None,
1703        }
1704    }
1705
1706    fn render_titlebar(&self, theme: &Theme, cx: &mut RenderContext<Self>) -> ElementBox {
1707        let project = &self.project.read(cx);
1708        let replica_id = project.replica_id();
1709        let mut worktree_root_names = String::new();
1710        for (i, name) in project.worktree_root_names(cx).enumerate() {
1711            if i > 0 {
1712                worktree_root_names.push_str(", ");
1713            }
1714            worktree_root_names.push_str(name);
1715        }
1716
1717        ConstrainedBox::new(
1718            Container::new(
1719                Stack::new()
1720                    .with_child(
1721                        Label::new(worktree_root_names, theme.workspace.titlebar.title.clone())
1722                            .aligned()
1723                            .left()
1724                            .boxed(),
1725                    )
1726                    .with_child(
1727                        Align::new(
1728                            Flex::row()
1729                                .with_children(self.render_collaborators(theme, cx))
1730                                .with_children(self.render_current_user(
1731                                    self.user_store.read(cx).current_user().as_ref(),
1732                                    replica_id,
1733                                    theme,
1734                                    cx,
1735                                ))
1736                                .with_children(self.render_connection_status(cx))
1737                                .boxed(),
1738                        )
1739                        .right()
1740                        .boxed(),
1741                    )
1742                    .boxed(),
1743            )
1744            .with_style(theme.workspace.titlebar.container)
1745            .boxed(),
1746        )
1747        .with_height(theme.workspace.titlebar.height)
1748        .named("titlebar")
1749    }
1750
1751    fn active_item_path_changed(&mut self, cx: &mut ViewContext<Self>) {
1752        let active_entry = self.active_project_path(cx);
1753        self.project
1754            .update(cx, |project, cx| project.set_active_path(active_entry, cx));
1755        self.update_window_title(cx);
1756    }
1757
1758    fn update_window_title(&mut self, cx: &mut ViewContext<Self>) {
1759        let mut title = String::new();
1760        let project = self.project().read(cx);
1761        if let Some(path) = self.active_item(cx).and_then(|item| item.project_path(cx)) {
1762            let filename = path
1763                .path
1764                .file_name()
1765                .map(|s| s.to_string_lossy())
1766                .or_else(|| {
1767                    Some(Cow::Borrowed(
1768                        project
1769                            .worktree_for_id(path.worktree_id, cx)?
1770                            .read(cx)
1771                            .root_name(),
1772                    ))
1773                });
1774            if let Some(filename) = filename {
1775                title.push_str(filename.as_ref());
1776                title.push_str("");
1777            }
1778        }
1779        for (i, name) in project.worktree_root_names(cx).enumerate() {
1780            if i > 0 {
1781                title.push_str(", ");
1782            }
1783            title.push_str(name);
1784        }
1785        if title.is_empty() {
1786            title = "empty project".to_string();
1787        }
1788        cx.set_window_title(&title);
1789    }
1790
1791    fn update_window_edited(&mut self, cx: &mut ViewContext<Self>) {
1792        let is_edited = self
1793            .items(cx)
1794            .any(|item| item.has_conflict(cx) || item.is_dirty(cx));
1795        if is_edited != self.window_edited {
1796            self.window_edited = is_edited;
1797            cx.set_window_edited(self.window_edited)
1798        }
1799    }
1800
1801    fn render_collaborators(&self, theme: &Theme, cx: &mut RenderContext<Self>) -> Vec<ElementBox> {
1802        let mut collaborators = self
1803            .project
1804            .read(cx)
1805            .collaborators()
1806            .values()
1807            .cloned()
1808            .collect::<Vec<_>>();
1809        collaborators.sort_unstable_by_key(|collaborator| collaborator.replica_id);
1810        collaborators
1811            .into_iter()
1812            .filter_map(|collaborator| {
1813                Some(self.render_avatar(
1814                    collaborator.user.avatar.clone()?,
1815                    collaborator.replica_id,
1816                    Some((collaborator.peer_id, &collaborator.user.github_login)),
1817                    theme,
1818                    cx,
1819                ))
1820            })
1821            .collect()
1822    }
1823
1824    fn render_current_user(
1825        &self,
1826        user: Option<&Arc<User>>,
1827        replica_id: ReplicaId,
1828        theme: &Theme,
1829        cx: &mut RenderContext<Self>,
1830    ) -> Option<ElementBox> {
1831        let status = *self.client.status().borrow();
1832        if let Some(avatar) = user.and_then(|user| user.avatar.clone()) {
1833            Some(self.render_avatar(avatar, replica_id, None, theme, cx))
1834        } else if matches!(status, client::Status::UpgradeRequired) {
1835            None
1836        } else {
1837            Some(
1838                MouseEventHandler::new::<Authenticate, _, _>(0, cx, |state, _| {
1839                    let style = theme
1840                        .workspace
1841                        .titlebar
1842                        .sign_in_prompt
1843                        .style_for(state, false);
1844                    Label::new("Sign in".to_string(), style.text.clone())
1845                        .contained()
1846                        .with_style(style.container)
1847                        .boxed()
1848                })
1849                .on_click(|_, _, cx| cx.dispatch_action(Authenticate))
1850                .with_cursor_style(CursorStyle::PointingHand)
1851                .aligned()
1852                .boxed(),
1853            )
1854        }
1855    }
1856
1857    fn render_avatar(
1858        &self,
1859        avatar: Arc<ImageData>,
1860        replica_id: ReplicaId,
1861        peer: Option<(PeerId, &str)>,
1862        theme: &Theme,
1863        cx: &mut RenderContext<Self>,
1864    ) -> ElementBox {
1865        let replica_color = theme.editor.replica_selection_style(replica_id).cursor;
1866        let is_followed = peer.map_or(false, |(peer_id, _)| {
1867            self.follower_states_by_leader.contains_key(&peer_id)
1868        });
1869        let mut avatar_style = theme.workspace.titlebar.avatar;
1870        if is_followed {
1871            avatar_style.border = Border::all(1.0, replica_color);
1872        }
1873        let content = Stack::new()
1874            .with_child(
1875                Image::new(avatar)
1876                    .with_style(avatar_style)
1877                    .constrained()
1878                    .with_width(theme.workspace.titlebar.avatar_width)
1879                    .aligned()
1880                    .boxed(),
1881            )
1882            .with_child(
1883                AvatarRibbon::new(replica_color)
1884                    .constrained()
1885                    .with_width(theme.workspace.titlebar.avatar_ribbon.width)
1886                    .with_height(theme.workspace.titlebar.avatar_ribbon.height)
1887                    .aligned()
1888                    .bottom()
1889                    .boxed(),
1890            )
1891            .constrained()
1892            .with_width(theme.workspace.titlebar.avatar_width)
1893            .contained()
1894            .with_margin_left(theme.workspace.titlebar.avatar_margin)
1895            .boxed();
1896
1897        if let Some((peer_id, peer_github_login)) = peer {
1898            MouseEventHandler::new::<ToggleFollow, _, _>(replica_id.into(), cx, move |_, _| content)
1899                .with_cursor_style(CursorStyle::PointingHand)
1900                .on_click(move |_, _, cx| cx.dispatch_action(ToggleFollow(peer_id)))
1901                .with_tooltip::<ToggleFollow, _>(
1902                    peer_id.0 as usize,
1903                    if is_followed {
1904                        format!("Unfollow {}", peer_github_login)
1905                    } else {
1906                        format!("Follow {}", peer_github_login)
1907                    },
1908                    Some(Box::new(FollowNextCollaborator)),
1909                    theme.tooltip.clone(),
1910                    cx,
1911                )
1912                .boxed()
1913        } else {
1914            content
1915        }
1916    }
1917
1918    fn render_disconnected_overlay(&self, cx: &AppContext) -> Option<ElementBox> {
1919        if self.project.read(cx).is_read_only() {
1920            let theme = &cx.global::<Settings>().theme;
1921            Some(
1922                EventHandler::new(
1923                    Label::new(
1924                        "Your connection to the remote project has been lost.".to_string(),
1925                        theme.workspace.disconnected_overlay.text.clone(),
1926                    )
1927                    .aligned()
1928                    .contained()
1929                    .with_style(theme.workspace.disconnected_overlay.container)
1930                    .boxed(),
1931                )
1932                .capture_all::<Self>(0)
1933                .boxed(),
1934            )
1935        } else {
1936            None
1937        }
1938    }
1939
1940    fn render_notifications(&self, theme: &theme::Workspace) -> Option<ElementBox> {
1941        if self.notifications.is_empty() {
1942            None
1943        } else {
1944            Some(
1945                Flex::column()
1946                    .with_children(self.notifications.iter().map(|(_, _, notification)| {
1947                        ChildView::new(notification.as_ref())
1948                            .contained()
1949                            .with_style(theme.notification)
1950                            .boxed()
1951                    }))
1952                    .constrained()
1953                    .with_width(theme.notifications.width)
1954                    .contained()
1955                    .with_style(theme.notifications.container)
1956                    .aligned()
1957                    .bottom()
1958                    .right()
1959                    .boxed(),
1960            )
1961        }
1962    }
1963
1964    // RPC handlers
1965
1966    async fn handle_follow(
1967        this: ViewHandle<Self>,
1968        envelope: TypedEnvelope<proto::Follow>,
1969        _: Arc<Client>,
1970        mut cx: AsyncAppContext,
1971    ) -> Result<proto::FollowResponse> {
1972        this.update(&mut cx, |this, cx| {
1973            this.leader_state
1974                .followers
1975                .insert(envelope.original_sender_id()?);
1976
1977            let active_view_id = this
1978                .active_item(cx)
1979                .and_then(|i| i.to_followable_item_handle(cx))
1980                .map(|i| i.id() as u64);
1981            Ok(proto::FollowResponse {
1982                active_view_id,
1983                views: this
1984                    .panes()
1985                    .iter()
1986                    .flat_map(|pane| {
1987                        let leader_id = this.leader_for_pane(pane).map(|id| id.0);
1988                        pane.read(cx).items().filter_map({
1989                            let cx = &cx;
1990                            move |item| {
1991                                let id = item.id() as u64;
1992                                let item = item.to_followable_item_handle(cx)?;
1993                                let variant = item.to_state_proto(cx)?;
1994                                Some(proto::View {
1995                                    id,
1996                                    leader_id,
1997                                    variant: Some(variant),
1998                                })
1999                            }
2000                        })
2001                    })
2002                    .collect(),
2003            })
2004        })
2005    }
2006
2007    async fn handle_unfollow(
2008        this: ViewHandle<Self>,
2009        envelope: TypedEnvelope<proto::Unfollow>,
2010        _: Arc<Client>,
2011        mut cx: AsyncAppContext,
2012    ) -> Result<()> {
2013        this.update(&mut cx, |this, _| {
2014            this.leader_state
2015                .followers
2016                .remove(&envelope.original_sender_id()?);
2017            Ok(())
2018        })
2019    }
2020
2021    async fn handle_update_followers(
2022        this: ViewHandle<Self>,
2023        envelope: TypedEnvelope<proto::UpdateFollowers>,
2024        _: Arc<Client>,
2025        mut cx: AsyncAppContext,
2026    ) -> Result<()> {
2027        let leader_id = envelope.original_sender_id()?;
2028        match envelope
2029            .payload
2030            .variant
2031            .ok_or_else(|| anyhow!("invalid update"))?
2032        {
2033            proto::update_followers::Variant::UpdateActiveView(update_active_view) => {
2034                this.update(&mut cx, |this, cx| {
2035                    this.update_leader_state(leader_id, cx, |state, _| {
2036                        state.active_view_id = update_active_view.id;
2037                    });
2038                    Ok::<_, anyhow::Error>(())
2039                })
2040            }
2041            proto::update_followers::Variant::UpdateView(update_view) => {
2042                this.update(&mut cx, |this, cx| {
2043                    let variant = update_view
2044                        .variant
2045                        .ok_or_else(|| anyhow!("missing update view variant"))?;
2046                    this.update_leader_state(leader_id, cx, |state, cx| {
2047                        let variant = variant.clone();
2048                        match state
2049                            .items_by_leader_view_id
2050                            .entry(update_view.id)
2051                            .or_insert(FollowerItem::Loading(Vec::new()))
2052                        {
2053                            FollowerItem::Loaded(item) => {
2054                                item.apply_update_proto(variant, cx).log_err();
2055                            }
2056                            FollowerItem::Loading(updates) => updates.push(variant),
2057                        }
2058                    });
2059                    Ok(())
2060                })
2061            }
2062            proto::update_followers::Variant::CreateView(view) => {
2063                let panes = this.read_with(&cx, |this, _| {
2064                    this.follower_states_by_leader
2065                        .get(&leader_id)
2066                        .into_iter()
2067                        .flat_map(|states_by_pane| states_by_pane.keys())
2068                        .cloned()
2069                        .collect()
2070                });
2071                Self::add_views_from_leader(this.clone(), leader_id, panes, vec![view], &mut cx)
2072                    .await?;
2073                Ok(())
2074            }
2075        }
2076        .log_err();
2077
2078        Ok(())
2079    }
2080
2081    async fn add_views_from_leader(
2082        this: ViewHandle<Self>,
2083        leader_id: PeerId,
2084        panes: Vec<ViewHandle<Pane>>,
2085        views: Vec<proto::View>,
2086        cx: &mut AsyncAppContext,
2087    ) -> Result<()> {
2088        let project = this.read_with(cx, |this, _| this.project.clone());
2089        let replica_id = project
2090            .read_with(cx, |project, _| {
2091                project
2092                    .collaborators()
2093                    .get(&leader_id)
2094                    .map(|c| c.replica_id)
2095            })
2096            .ok_or_else(|| anyhow!("no such collaborator {}", leader_id))?;
2097
2098        let item_builders = cx.update(|cx| {
2099            cx.default_global::<FollowableItemBuilders>()
2100                .values()
2101                .map(|b| b.0)
2102                .collect::<Vec<_>>()
2103                .clone()
2104        });
2105
2106        let mut item_tasks_by_pane = HashMap::default();
2107        for pane in panes {
2108            let mut item_tasks = Vec::new();
2109            let mut leader_view_ids = Vec::new();
2110            for view in &views {
2111                let mut variant = view.variant.clone();
2112                if variant.is_none() {
2113                    Err(anyhow!("missing variant"))?;
2114                }
2115                for build_item in &item_builders {
2116                    let task =
2117                        cx.update(|cx| build_item(pane.clone(), project.clone(), &mut variant, cx));
2118                    if let Some(task) = task {
2119                        item_tasks.push(task);
2120                        leader_view_ids.push(view.id);
2121                        break;
2122                    } else {
2123                        assert!(variant.is_some());
2124                    }
2125                }
2126            }
2127
2128            item_tasks_by_pane.insert(pane, (item_tasks, leader_view_ids));
2129        }
2130
2131        for (pane, (item_tasks, leader_view_ids)) in item_tasks_by_pane {
2132            let items = futures::future::try_join_all(item_tasks).await?;
2133            this.update(cx, |this, cx| {
2134                let state = this
2135                    .follower_states_by_leader
2136                    .get_mut(&leader_id)?
2137                    .get_mut(&pane)?;
2138
2139                for (id, item) in leader_view_ids.into_iter().zip(items) {
2140                    item.set_leader_replica_id(Some(replica_id), cx);
2141                    match state.items_by_leader_view_id.entry(id) {
2142                        hash_map::Entry::Occupied(e) => {
2143                            let e = e.into_mut();
2144                            if let FollowerItem::Loading(updates) = e {
2145                                for update in updates.drain(..) {
2146                                    item.apply_update_proto(update, cx)
2147                                        .context("failed to apply view update")
2148                                        .log_err();
2149                                }
2150                            }
2151                            *e = FollowerItem::Loaded(item);
2152                        }
2153                        hash_map::Entry::Vacant(e) => {
2154                            e.insert(FollowerItem::Loaded(item));
2155                        }
2156                    }
2157                }
2158
2159                Some(())
2160            });
2161        }
2162        this.update(cx, |this, cx| this.leader_updated(leader_id, cx));
2163
2164        Ok(())
2165    }
2166
2167    fn update_followers(
2168        &self,
2169        update: proto::update_followers::Variant,
2170        cx: &AppContext,
2171    ) -> Option<()> {
2172        let project_id = self.project.read(cx).remote_id()?;
2173        if !self.leader_state.followers.is_empty() {
2174            self.client
2175                .send(proto::UpdateFollowers {
2176                    project_id,
2177                    follower_ids: self.leader_state.followers.iter().map(|f| f.0).collect(),
2178                    variant: Some(update),
2179                })
2180                .log_err();
2181        }
2182        None
2183    }
2184
2185    pub fn leader_for_pane(&self, pane: &ViewHandle<Pane>) -> Option<PeerId> {
2186        self.follower_states_by_leader
2187            .iter()
2188            .find_map(|(leader_id, state)| {
2189                if state.contains_key(pane) {
2190                    Some(*leader_id)
2191                } else {
2192                    None
2193                }
2194            })
2195    }
2196
2197    fn update_leader_state(
2198        &mut self,
2199        leader_id: PeerId,
2200        cx: &mut ViewContext<Self>,
2201        mut update_fn: impl FnMut(&mut FollowerState, &mut ViewContext<Self>),
2202    ) {
2203        for (_, state) in self
2204            .follower_states_by_leader
2205            .get_mut(&leader_id)
2206            .into_iter()
2207            .flatten()
2208        {
2209            update_fn(state, cx);
2210        }
2211        self.leader_updated(leader_id, cx);
2212    }
2213
2214    fn leader_updated(&mut self, leader_id: PeerId, cx: &mut ViewContext<Self>) -> Option<()> {
2215        let mut items_to_add = Vec::new();
2216        for (pane, state) in self.follower_states_by_leader.get(&leader_id)? {
2217            if let Some(active_item) = state
2218                .active_view_id
2219                .and_then(|id| state.items_by_leader_view_id.get(&id))
2220            {
2221                if let FollowerItem::Loaded(item) = active_item {
2222                    items_to_add.push((pane.clone(), item.boxed_clone()));
2223                }
2224            }
2225        }
2226
2227        for (pane, item) in items_to_add {
2228            Pane::add_item(self, pane.clone(), item.boxed_clone(), false, false, cx);
2229            if pane == self.active_pane {
2230                pane.update(cx, |pane, cx| pane.focus_active_item(cx));
2231            }
2232            cx.notify();
2233        }
2234        None
2235    }
2236}
2237
2238impl Entity for Workspace {
2239    type Event = Event;
2240}
2241
2242impl View for Workspace {
2243    fn ui_name() -> &'static str {
2244        "Workspace"
2245    }
2246
2247    fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
2248        let theme = cx.global::<Settings>().theme.clone();
2249        Stack::new()
2250            .with_child(
2251                Flex::column()
2252                    .with_child(self.render_titlebar(&theme, cx))
2253                    .with_child(
2254                        Stack::new()
2255                            .with_child({
2256                                Flex::row()
2257                                    .with_children(
2258                                        if self.left_sidebar.read(cx).active_item().is_some() {
2259                                            Some(
2260                                                ChildView::new(&self.left_sidebar)
2261                                                    .flex(0.8, false)
2262                                                    .boxed(),
2263                                            )
2264                                        } else {
2265                                            None
2266                                        },
2267                                    )
2268                                    .with_child(
2269                                        FlexItem::new(self.center.render(
2270                                            &theme,
2271                                            &self.follower_states_by_leader,
2272                                            self.project.read(cx).collaborators(),
2273                                        ))
2274                                        .flex(1., true)
2275                                        .boxed(),
2276                                    )
2277                                    .with_children(
2278                                        if self.right_sidebar.read(cx).active_item().is_some() {
2279                                            Some(
2280                                                ChildView::new(&self.right_sidebar)
2281                                                    .flex(0.8, false)
2282                                                    .boxed(),
2283                                            )
2284                                        } else {
2285                                            None
2286                                        },
2287                                    )
2288                                    .boxed()
2289                            })
2290                            .with_children(self.modal.as_ref().map(|m| {
2291                                ChildView::new(m)
2292                                    .contained()
2293                                    .with_style(theme.workspace.modal)
2294                                    .aligned()
2295                                    .top()
2296                                    .boxed()
2297                            }))
2298                            .with_children(self.render_notifications(&theme.workspace))
2299                            .flex(1.0, true)
2300                            .boxed(),
2301                    )
2302                    .with_child(ChildView::new(&self.status_bar).boxed())
2303                    .contained()
2304                    .with_background_color(theme.workspace.background)
2305                    .boxed(),
2306            )
2307            .with_children(self.render_disconnected_overlay(cx))
2308            .named("workspace")
2309    }
2310
2311    fn on_focus(&mut self, cx: &mut ViewContext<Self>) {
2312        cx.focus(&self.active_pane);
2313    }
2314}
2315
2316pub trait WorkspaceHandle {
2317    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath>;
2318}
2319
2320impl WorkspaceHandle for ViewHandle<Workspace> {
2321    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath> {
2322        self.read(cx)
2323            .worktrees(cx)
2324            .flat_map(|worktree| {
2325                let worktree_id = worktree.read(cx).id();
2326                worktree.read(cx).files(true, 0).map(move |f| ProjectPath {
2327                    worktree_id,
2328                    path: f.path.clone(),
2329                })
2330            })
2331            .collect::<Vec<_>>()
2332    }
2333}
2334
2335pub struct AvatarRibbon {
2336    color: Color,
2337}
2338
2339impl AvatarRibbon {
2340    pub fn new(color: Color) -> AvatarRibbon {
2341        AvatarRibbon { color }
2342    }
2343}
2344
2345impl Element for AvatarRibbon {
2346    type LayoutState = ();
2347
2348    type PaintState = ();
2349
2350    fn layout(
2351        &mut self,
2352        constraint: gpui::SizeConstraint,
2353        _: &mut gpui::LayoutContext,
2354    ) -> (gpui::geometry::vector::Vector2F, Self::LayoutState) {
2355        (constraint.max, ())
2356    }
2357
2358    fn paint(
2359        &mut self,
2360        bounds: gpui::geometry::rect::RectF,
2361        _: gpui::geometry::rect::RectF,
2362        _: &mut Self::LayoutState,
2363        cx: &mut gpui::PaintContext,
2364    ) -> Self::PaintState {
2365        let mut path = PathBuilder::new();
2366        path.reset(bounds.lower_left());
2367        path.curve_to(
2368            bounds.origin() + vec2f(bounds.height(), 0.),
2369            bounds.origin(),
2370        );
2371        path.line_to(bounds.upper_right() - vec2f(bounds.height(), 0.));
2372        path.curve_to(bounds.lower_right(), bounds.upper_right());
2373        path.line_to(bounds.lower_left());
2374        cx.scene.push_path(path.build(self.color, None));
2375    }
2376
2377    fn dispatch_event(
2378        &mut self,
2379        _: &gpui::Event,
2380        _: RectF,
2381        _: RectF,
2382        _: &mut Self::LayoutState,
2383        _: &mut Self::PaintState,
2384        _: &mut gpui::EventContext,
2385    ) -> bool {
2386        false
2387    }
2388
2389    fn debug(
2390        &self,
2391        bounds: gpui::geometry::rect::RectF,
2392        _: &Self::LayoutState,
2393        _: &Self::PaintState,
2394        _: &gpui::DebugContext,
2395    ) -> gpui::json::Value {
2396        json::json!({
2397            "type": "AvatarRibbon",
2398            "bounds": bounds.to_json(),
2399            "color": self.color.to_json(),
2400        })
2401    }
2402}
2403
2404impl std::fmt::Debug for OpenPaths {
2405    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2406        f.debug_struct("OpenPaths")
2407            .field("paths", &self.paths)
2408            .finish()
2409    }
2410}
2411
2412fn open(_: &Open, cx: &mut MutableAppContext) {
2413    let mut paths = cx.prompt_for_paths(PathPromptOptions {
2414        files: true,
2415        directories: true,
2416        multiple: true,
2417    });
2418    cx.spawn(|mut cx| async move {
2419        if let Some(paths) = paths.recv().await.flatten() {
2420            cx.update(|cx| cx.dispatch_global_action(OpenPaths { paths }));
2421        }
2422    })
2423    .detach();
2424}
2425
2426pub struct WorkspaceCreated(WeakViewHandle<Workspace>);
2427
2428pub fn activate_workspace_for_project(
2429    cx: &mut MutableAppContext,
2430    predicate: impl Fn(&mut Project, &mut ModelContext<Project>) -> bool,
2431) -> Option<ViewHandle<Workspace>> {
2432    for window_id in cx.window_ids().collect::<Vec<_>>() {
2433        if let Some(workspace_handle) = cx.root_view::<Workspace>(window_id) {
2434            let project = workspace_handle.read(cx).project.clone();
2435            if project.update(cx, &predicate) {
2436                cx.activate_window(window_id);
2437                return Some(workspace_handle);
2438            }
2439        }
2440    }
2441    None
2442}
2443
2444pub fn open_paths(
2445    abs_paths: &[PathBuf],
2446    app_state: &Arc<AppState>,
2447    cx: &mut MutableAppContext,
2448) -> Task<(
2449    ViewHandle<Workspace>,
2450    Vec<Option<Result<Box<dyn ItemHandle>, Arc<anyhow::Error>>>>,
2451)> {
2452    log::info!("open paths {:?}", abs_paths);
2453
2454    // Open paths in existing workspace if possible
2455    let existing =
2456        activate_workspace_for_project(cx, |project, cx| project.contains_paths(abs_paths, cx));
2457
2458    let app_state = app_state.clone();
2459    let abs_paths = abs_paths.to_vec();
2460    cx.spawn(|mut cx| async move {
2461        let mut new_project = None;
2462        let workspace = if let Some(existing) = existing {
2463            existing
2464        } else {
2465            let contains_directory =
2466                futures::future::join_all(abs_paths.iter().map(|path| app_state.fs.is_file(path)))
2467                    .await
2468                    .contains(&false);
2469
2470            cx.add_window((app_state.build_window_options)(), |cx| {
2471                let project = Project::local(
2472                    false,
2473                    app_state.client.clone(),
2474                    app_state.user_store.clone(),
2475                    app_state.project_store.clone(),
2476                    app_state.languages.clone(),
2477                    app_state.fs.clone(),
2478                    cx,
2479                );
2480                new_project = Some(project.clone());
2481                let mut workspace = Workspace::new(project, cx);
2482                (app_state.initialize_workspace)(&mut workspace, &app_state, cx);
2483                if contains_directory {
2484                    workspace.toggle_sidebar_item(
2485                        &ToggleSidebarItem {
2486                            side: Side::Left,
2487                            item_index: 0,
2488                        },
2489                        cx,
2490                    );
2491                }
2492                workspace
2493            })
2494            .1
2495        };
2496
2497        let items = workspace
2498            .update(&mut cx, |workspace, cx| {
2499                workspace.open_paths(abs_paths, true, cx)
2500            })
2501            .await;
2502
2503        if let Some(project) = new_project {
2504            project
2505                .update(&mut cx, |project, cx| project.restore_state(cx))
2506                .await
2507                .log_err();
2508        }
2509
2510        (workspace, items)
2511    })
2512}
2513
2514pub fn join_project(
2515    contact: Arc<Contact>,
2516    project_index: usize,
2517    app_state: &Arc<AppState>,
2518    cx: &mut MutableAppContext,
2519) {
2520    let project_id = contact.projects[project_index].id;
2521
2522    for window_id in cx.window_ids().collect::<Vec<_>>() {
2523        if let Some(workspace) = cx.root_view::<Workspace>(window_id) {
2524            if workspace.read(cx).project().read(cx).remote_id() == Some(project_id) {
2525                cx.activate_window(window_id);
2526                return;
2527            }
2528        }
2529    }
2530
2531    cx.add_window((app_state.build_window_options)(), |cx| {
2532        WaitingRoom::new(contact, project_index, app_state.clone(), cx)
2533    });
2534}
2535
2536fn open_new(app_state: &Arc<AppState>, cx: &mut MutableAppContext) {
2537    let (window_id, workspace) = cx.add_window((app_state.build_window_options)(), |cx| {
2538        let mut workspace = Workspace::new(
2539            Project::local(
2540                false,
2541                app_state.client.clone(),
2542                app_state.user_store.clone(),
2543                app_state.project_store.clone(),
2544                app_state.languages.clone(),
2545                app_state.fs.clone(),
2546                cx,
2547            ),
2548            cx,
2549        );
2550        (app_state.initialize_workspace)(&mut workspace, app_state, cx);
2551        workspace
2552    });
2553    cx.dispatch_action(window_id, vec![workspace.id()], &NewFile);
2554}
2555
2556#[cfg(test)]
2557mod tests {
2558    use super::*;
2559    use gpui::{ModelHandle, TestAppContext, ViewContext};
2560    use project::{FakeFs, Project, ProjectEntryId};
2561    use serde_json::json;
2562
2563    #[gpui::test]
2564    async fn test_tracking_active_path(cx: &mut TestAppContext) {
2565        cx.foreground().forbid_parking();
2566        Settings::test_async(cx);
2567        let fs = FakeFs::new(cx.background());
2568        fs.insert_tree(
2569            "/root1",
2570            json!({
2571                "one.txt": "",
2572                "two.txt": "",
2573            }),
2574        )
2575        .await;
2576        fs.insert_tree(
2577            "/root2",
2578            json!({
2579                "three.txt": "",
2580            }),
2581        )
2582        .await;
2583
2584        let project = Project::test(fs, ["root1".as_ref()], cx).await;
2585        let (window_id, workspace) = cx.add_window(|cx| Workspace::new(project.clone(), cx));
2586        let worktree_id = project.read_with(cx, |project, cx| {
2587            project.worktrees(cx).next().unwrap().read(cx).id()
2588        });
2589
2590        let item1 = cx.add_view(window_id, |_| {
2591            let mut item = TestItem::new();
2592            item.project_path = Some((worktree_id, "one.txt").into());
2593            item
2594        });
2595        let item2 = cx.add_view(window_id, |_| {
2596            let mut item = TestItem::new();
2597            item.project_path = Some((worktree_id, "two.txt").into());
2598            item
2599        });
2600
2601        // Add an item to an empty pane
2602        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item1), cx));
2603        project.read_with(cx, |project, cx| {
2604            assert_eq!(
2605                project.active_entry(),
2606                project.entry_for_path(&(worktree_id, "one.txt").into(), cx)
2607            );
2608        });
2609        assert_eq!(
2610            cx.current_window_title(window_id).as_deref(),
2611            Some("one.txt — root1")
2612        );
2613
2614        // Add a second item to a non-empty pane
2615        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item2), cx));
2616        assert_eq!(
2617            cx.current_window_title(window_id).as_deref(),
2618            Some("two.txt — root1")
2619        );
2620        project.read_with(cx, |project, cx| {
2621            assert_eq!(
2622                project.active_entry(),
2623                project.entry_for_path(&(worktree_id, "two.txt").into(), cx)
2624            );
2625        });
2626
2627        // Close the active item
2628        workspace
2629            .update(cx, |workspace, cx| {
2630                Pane::close_active_item(workspace, &Default::default(), cx).unwrap()
2631            })
2632            .await
2633            .unwrap();
2634        assert_eq!(
2635            cx.current_window_title(window_id).as_deref(),
2636            Some("one.txt — root1")
2637        );
2638        project.read_with(cx, |project, cx| {
2639            assert_eq!(
2640                project.active_entry(),
2641                project.entry_for_path(&(worktree_id, "one.txt").into(), cx)
2642            );
2643        });
2644
2645        // Add a project folder
2646        project
2647            .update(cx, |project, cx| {
2648                project.find_or_create_local_worktree("/root2", true, cx)
2649            })
2650            .await
2651            .unwrap();
2652        assert_eq!(
2653            cx.current_window_title(window_id).as_deref(),
2654            Some("one.txt — root1, root2")
2655        );
2656
2657        // Remove a project folder
2658        project.update(cx, |project, cx| {
2659            project.remove_worktree(worktree_id, cx);
2660        });
2661        assert_eq!(
2662            cx.current_window_title(window_id).as_deref(),
2663            Some("one.txt — root2")
2664        );
2665    }
2666
2667    #[gpui::test]
2668    async fn test_close_window(cx: &mut TestAppContext) {
2669        cx.foreground().forbid_parking();
2670        Settings::test_async(cx);
2671        let fs = FakeFs::new(cx.background());
2672        fs.insert_tree("/root", json!({ "one": "" })).await;
2673
2674        let project = Project::test(fs, ["root".as_ref()], cx).await;
2675        let (window_id, workspace) = cx.add_window(|cx| Workspace::new(project.clone(), cx));
2676
2677        // When there are no dirty items, there's nothing to do.
2678        let item1 = cx.add_view(window_id, |_| TestItem::new());
2679        workspace.update(cx, |w, cx| w.add_item(Box::new(item1.clone()), cx));
2680        let task = workspace.update(cx, |w, cx| w.prepare_to_close(cx));
2681        assert_eq!(task.await.unwrap(), true);
2682
2683        // When there are dirty untitled items, prompt to save each one. If the user
2684        // cancels any prompt, then abort.
2685        let item2 = cx.add_view(window_id, |_| {
2686            let mut item = TestItem::new();
2687            item.is_dirty = true;
2688            item
2689        });
2690        let item3 = cx.add_view(window_id, |_| {
2691            let mut item = TestItem::new();
2692            item.is_dirty = true;
2693            item.project_entry_ids = vec![ProjectEntryId::from_proto(1)];
2694            item
2695        });
2696        workspace.update(cx, |w, cx| {
2697            w.add_item(Box::new(item2.clone()), cx);
2698            w.add_item(Box::new(item3.clone()), cx);
2699        });
2700        let task = workspace.update(cx, |w, cx| w.prepare_to_close(cx));
2701        cx.foreground().run_until_parked();
2702        cx.simulate_prompt_answer(window_id, 2 /* cancel */);
2703        cx.foreground().run_until_parked();
2704        assert!(!cx.has_pending_prompt(window_id));
2705        assert_eq!(task.await.unwrap(), false);
2706    }
2707
2708    #[gpui::test]
2709    async fn test_close_pane_items(cx: &mut TestAppContext) {
2710        cx.foreground().forbid_parking();
2711        Settings::test_async(cx);
2712        let fs = FakeFs::new(cx.background());
2713
2714        let project = Project::test(fs, None, cx).await;
2715        let (window_id, workspace) = cx.add_window(|cx| Workspace::new(project, cx));
2716
2717        let item1 = cx.add_view(window_id, |_| {
2718            let mut item = TestItem::new();
2719            item.is_dirty = true;
2720            item.project_entry_ids = vec![ProjectEntryId::from_proto(1)];
2721            item
2722        });
2723        let item2 = cx.add_view(window_id, |_| {
2724            let mut item = TestItem::new();
2725            item.is_dirty = true;
2726            item.has_conflict = true;
2727            item.project_entry_ids = vec![ProjectEntryId::from_proto(2)];
2728            item
2729        });
2730        let item3 = cx.add_view(window_id, |_| {
2731            let mut item = TestItem::new();
2732            item.is_dirty = true;
2733            item.has_conflict = true;
2734            item.project_entry_ids = vec![ProjectEntryId::from_proto(3)];
2735            item
2736        });
2737        let item4 = cx.add_view(window_id, |_| {
2738            let mut item = TestItem::new();
2739            item.is_dirty = true;
2740            item
2741        });
2742        let pane = workspace.update(cx, |workspace, cx| {
2743            workspace.add_item(Box::new(item1.clone()), cx);
2744            workspace.add_item(Box::new(item2.clone()), cx);
2745            workspace.add_item(Box::new(item3.clone()), cx);
2746            workspace.add_item(Box::new(item4.clone()), cx);
2747            workspace.active_pane().clone()
2748        });
2749
2750        let close_items = workspace.update(cx, |workspace, cx| {
2751            pane.update(cx, |pane, cx| {
2752                pane.activate_item(1, true, true, cx);
2753                assert_eq!(pane.active_item().unwrap().id(), item2.id());
2754            });
2755
2756            let item1_id = item1.id();
2757            let item3_id = item3.id();
2758            let item4_id = item4.id();
2759            Pane::close_items(workspace, pane.clone(), cx, move |id| {
2760                [item1_id, item3_id, item4_id].contains(&id)
2761            })
2762        });
2763
2764        cx.foreground().run_until_parked();
2765        pane.read_with(cx, |pane, _| {
2766            assert_eq!(pane.items().count(), 4);
2767            assert_eq!(pane.active_item().unwrap().id(), item1.id());
2768        });
2769
2770        cx.simulate_prompt_answer(window_id, 0);
2771        cx.foreground().run_until_parked();
2772        pane.read_with(cx, |pane, cx| {
2773            assert_eq!(item1.read(cx).save_count, 1);
2774            assert_eq!(item1.read(cx).save_as_count, 0);
2775            assert_eq!(item1.read(cx).reload_count, 0);
2776            assert_eq!(pane.items().count(), 3);
2777            assert_eq!(pane.active_item().unwrap().id(), item3.id());
2778        });
2779
2780        cx.simulate_prompt_answer(window_id, 1);
2781        cx.foreground().run_until_parked();
2782        pane.read_with(cx, |pane, cx| {
2783            assert_eq!(item3.read(cx).save_count, 0);
2784            assert_eq!(item3.read(cx).save_as_count, 0);
2785            assert_eq!(item3.read(cx).reload_count, 1);
2786            assert_eq!(pane.items().count(), 2);
2787            assert_eq!(pane.active_item().unwrap().id(), item4.id());
2788        });
2789
2790        cx.simulate_prompt_answer(window_id, 0);
2791        cx.foreground().run_until_parked();
2792        cx.simulate_new_path_selection(|_| Some(Default::default()));
2793        close_items.await.unwrap();
2794        pane.read_with(cx, |pane, cx| {
2795            assert_eq!(item4.read(cx).save_count, 0);
2796            assert_eq!(item4.read(cx).save_as_count, 1);
2797            assert_eq!(item4.read(cx).reload_count, 0);
2798            assert_eq!(pane.items().count(), 1);
2799            assert_eq!(pane.active_item().unwrap().id(), item2.id());
2800        });
2801    }
2802
2803    #[gpui::test]
2804    async fn test_prompting_to_save_only_on_last_item_for_entry(cx: &mut TestAppContext) {
2805        cx.foreground().forbid_parking();
2806        Settings::test_async(cx);
2807        let fs = FakeFs::new(cx.background());
2808
2809        let project = Project::test(fs, [], cx).await;
2810        let (window_id, workspace) = cx.add_window(|cx| Workspace::new(project, cx));
2811
2812        // Create several workspace items with single project entries, and two
2813        // workspace items with multiple project entries.
2814        let single_entry_items = (0..=4)
2815            .map(|project_entry_id| {
2816                let mut item = TestItem::new();
2817                item.is_dirty = true;
2818                item.project_entry_ids = vec![ProjectEntryId::from_proto(project_entry_id)];
2819                item.is_singleton = true;
2820                item
2821            })
2822            .collect::<Vec<_>>();
2823        let item_2_3 = {
2824            let mut item = TestItem::new();
2825            item.is_dirty = true;
2826            item.is_singleton = false;
2827            item.project_entry_ids =
2828                vec![ProjectEntryId::from_proto(2), ProjectEntryId::from_proto(3)];
2829            item
2830        };
2831        let item_3_4 = {
2832            let mut item = TestItem::new();
2833            item.is_dirty = true;
2834            item.is_singleton = false;
2835            item.project_entry_ids =
2836                vec![ProjectEntryId::from_proto(3), ProjectEntryId::from_proto(4)];
2837            item
2838        };
2839
2840        // Create two panes that contain the following project entries:
2841        //   left pane:
2842        //     multi-entry items:   (2, 3)
2843        //     single-entry items:  0, 1, 2, 3, 4
2844        //   right pane:
2845        //     single-entry items:  1
2846        //     multi-entry items:   (3, 4)
2847        let left_pane = workspace.update(cx, |workspace, cx| {
2848            let left_pane = workspace.active_pane().clone();
2849            let right_pane = workspace.split_pane(left_pane.clone(), SplitDirection::Right, cx);
2850
2851            workspace.activate_pane(left_pane.clone(), cx);
2852            workspace.add_item(Box::new(cx.add_view(|_| item_2_3.clone())), cx);
2853            for item in &single_entry_items {
2854                workspace.add_item(Box::new(cx.add_view(|_| item.clone())), cx);
2855            }
2856
2857            workspace.activate_pane(right_pane.clone(), cx);
2858            workspace.add_item(Box::new(cx.add_view(|_| single_entry_items[1].clone())), cx);
2859            workspace.add_item(Box::new(cx.add_view(|_| item_3_4.clone())), cx);
2860
2861            left_pane
2862        });
2863
2864        // When closing all of the items in the left pane, we should be prompted twice:
2865        // once for project entry 0, and once for project entry 2. After those two
2866        // prompts, the task should complete.
2867        let close = workspace.update(cx, |workspace, cx| {
2868            workspace.activate_pane(left_pane.clone(), cx);
2869            Pane::close_items(workspace, left_pane.clone(), cx, |_| true)
2870        });
2871
2872        cx.foreground().run_until_parked();
2873        left_pane.read_with(cx, |pane, cx| {
2874            assert_eq!(
2875                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
2876                &[ProjectEntryId::from_proto(0)]
2877            );
2878        });
2879        cx.simulate_prompt_answer(window_id, 0);
2880
2881        cx.foreground().run_until_parked();
2882        left_pane.read_with(cx, |pane, cx| {
2883            assert_eq!(
2884                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
2885                &[ProjectEntryId::from_proto(2)]
2886            );
2887        });
2888        cx.simulate_prompt_answer(window_id, 0);
2889
2890        cx.foreground().run_until_parked();
2891        close.await.unwrap();
2892        left_pane.read_with(cx, |pane, _| {
2893            assert_eq!(pane.items().count(), 0);
2894        });
2895    }
2896
2897    #[derive(Clone)]
2898    struct TestItem {
2899        save_count: usize,
2900        save_as_count: usize,
2901        reload_count: usize,
2902        is_dirty: bool,
2903        has_conflict: bool,
2904        project_entry_ids: Vec<ProjectEntryId>,
2905        project_path: Option<ProjectPath>,
2906        is_singleton: bool,
2907    }
2908
2909    impl TestItem {
2910        fn new() -> Self {
2911            Self {
2912                save_count: 0,
2913                save_as_count: 0,
2914                reload_count: 0,
2915                is_dirty: false,
2916                has_conflict: false,
2917                project_entry_ids: Vec::new(),
2918                project_path: None,
2919                is_singleton: true,
2920            }
2921        }
2922    }
2923
2924    impl Entity for TestItem {
2925        type Event = ();
2926    }
2927
2928    impl View for TestItem {
2929        fn ui_name() -> &'static str {
2930            "TestItem"
2931        }
2932
2933        fn render(&mut self, _: &mut RenderContext<Self>) -> ElementBox {
2934            Empty::new().boxed()
2935        }
2936    }
2937
2938    impl Item for TestItem {
2939        fn tab_content(&self, _: &theme::Tab, _: &AppContext) -> ElementBox {
2940            Empty::new().boxed()
2941        }
2942
2943        fn project_path(&self, _: &AppContext) -> Option<ProjectPath> {
2944            self.project_path.clone()
2945        }
2946
2947        fn project_entry_ids(&self, _: &AppContext) -> SmallVec<[ProjectEntryId; 3]> {
2948            self.project_entry_ids.iter().copied().collect()
2949        }
2950
2951        fn is_singleton(&self, _: &AppContext) -> bool {
2952            self.is_singleton
2953        }
2954
2955        fn set_nav_history(&mut self, _: ItemNavHistory, _: &mut ViewContext<Self>) {}
2956
2957        fn clone_on_split(&self, _: &mut ViewContext<Self>) -> Option<Self>
2958        where
2959            Self: Sized,
2960        {
2961            Some(self.clone())
2962        }
2963
2964        fn is_dirty(&self, _: &AppContext) -> bool {
2965            self.is_dirty
2966        }
2967
2968        fn has_conflict(&self, _: &AppContext) -> bool {
2969            self.has_conflict
2970        }
2971
2972        fn can_save(&self, _: &AppContext) -> bool {
2973            self.project_entry_ids.len() > 0
2974        }
2975
2976        fn save(
2977            &mut self,
2978            _: ModelHandle<Project>,
2979            _: &mut ViewContext<Self>,
2980        ) -> Task<anyhow::Result<()>> {
2981            self.save_count += 1;
2982            Task::ready(Ok(()))
2983        }
2984
2985        fn save_as(
2986            &mut self,
2987            _: ModelHandle<Project>,
2988            _: std::path::PathBuf,
2989            _: &mut ViewContext<Self>,
2990        ) -> Task<anyhow::Result<()>> {
2991            self.save_as_count += 1;
2992            Task::ready(Ok(()))
2993        }
2994
2995        fn reload(
2996            &mut self,
2997            _: ModelHandle<Project>,
2998            _: &mut ViewContext<Self>,
2999        ) -> Task<anyhow::Result<()>> {
3000            self.reload_count += 1;
3001            Task::ready(Ok(()))
3002        }
3003
3004        fn should_update_tab_on_event(_: &Self::Event) -> bool {
3005            true
3006        }
3007    }
3008}