workspace.rs

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