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