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, PartialEq)]
  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, PartialEq)]
  98pub struct OpenPaths {
  99    pub paths: Vec<PathBuf>,
 100}
 101
 102#[derive(Clone, Deserialize, PartialEq)]
 103pub struct ToggleProjectOnline {
 104    #[serde(skip_deserializing)]
 105    pub project: Option<ModelHandle<Project>>,
 106}
 107
 108#[derive(Clone, PartialEq)]
 109pub struct ToggleFollow(pub PeerId);
 110
 111#[derive(Clone, PartialEq)]
 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, [ToggleProjectOnline]);
 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_online);
 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_online(&mut self, action: &ToggleProjectOnline, 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_online();
1059            project.set_online(public, cx);
1060        });
1061    }
1062
1063    fn project_path_for_path(
1064        &self,
1065        abs_path: &Path,
1066        cx: &mut ViewContext<Self>,
1067    ) -> Task<Result<ProjectPath>> {
1068        let entry = self.project().update(cx, |project, cx| {
1069            project.find_or_create_local_worktree(abs_path, true, cx)
1070        });
1071        cx.spawn(|_, cx| async move {
1072            let (worktree, path) = entry.await?;
1073            Ok(ProjectPath {
1074                worktree_id: worktree.read_with(&cx, |t, _| t.id()),
1075                path: path.into(),
1076            })
1077        })
1078    }
1079
1080    /// Returns the modal that was toggled closed if it was open.
1081    pub fn toggle_modal<V, F>(
1082        &mut self,
1083        cx: &mut ViewContext<Self>,
1084        add_view: F,
1085    ) -> Option<ViewHandle<V>>
1086    where
1087        V: 'static + View,
1088        F: FnOnce(&mut Self, &mut ViewContext<Self>) -> ViewHandle<V>,
1089    {
1090        cx.notify();
1091        // Whatever modal was visible is getting clobbered. If its the same type as V, then return
1092        // it. Otherwise, create a new modal and set it as active.
1093        let already_open_modal = self.modal.take().and_then(|modal| modal.downcast::<V>());
1094        if let Some(already_open_modal) = already_open_modal {
1095            cx.focus_self();
1096            Some(already_open_modal)
1097        } else {
1098            let modal = add_view(self, cx);
1099            cx.focus(&modal);
1100            self.modal = Some(modal.into());
1101            None
1102        }
1103    }
1104
1105    pub fn modal(&self) -> Option<&AnyViewHandle> {
1106        self.modal.as_ref()
1107    }
1108
1109    pub fn dismiss_modal(&mut self, cx: &mut ViewContext<Self>) {
1110        if self.modal.take().is_some() {
1111            cx.focus(&self.active_pane);
1112            cx.notify();
1113        }
1114    }
1115
1116    pub fn show_notification<V: Notification>(
1117        &mut self,
1118        id: usize,
1119        cx: &mut ViewContext<Self>,
1120        build_notification: impl FnOnce(&mut ViewContext<Self>) -> ViewHandle<V>,
1121    ) {
1122        let type_id = TypeId::of::<V>();
1123        if self
1124            .notifications
1125            .iter()
1126            .all(|(existing_type_id, existing_id, _)| {
1127                (*existing_type_id, *existing_id) != (type_id, id)
1128            })
1129        {
1130            let notification = build_notification(cx);
1131            cx.subscribe(&notification, move |this, handle, event, cx| {
1132                if handle.read(cx).should_dismiss_notification_on_event(event) {
1133                    this.dismiss_notification(type_id, id, cx);
1134                }
1135            })
1136            .detach();
1137            self.notifications
1138                .push((type_id, id, Box::new(notification)));
1139            cx.notify();
1140        }
1141    }
1142
1143    fn dismiss_notification(&mut self, type_id: TypeId, id: usize, cx: &mut ViewContext<Self>) {
1144        self.notifications
1145            .retain(|(existing_type_id, existing_id, _)| {
1146                if (*existing_type_id, *existing_id) == (type_id, id) {
1147                    cx.notify();
1148                    false
1149                } else {
1150                    true
1151                }
1152            });
1153    }
1154
1155    pub fn items<'a>(
1156        &'a self,
1157        cx: &'a AppContext,
1158    ) -> impl 'a + Iterator<Item = &Box<dyn ItemHandle>> {
1159        self.panes.iter().flat_map(|pane| pane.read(cx).items())
1160    }
1161
1162    pub fn item_of_type<T: Item>(&self, cx: &AppContext) -> Option<ViewHandle<T>> {
1163        self.items_of_type(cx).max_by_key(|item| item.id())
1164    }
1165
1166    pub fn items_of_type<'a, T: Item>(
1167        &'a self,
1168        cx: &'a AppContext,
1169    ) -> impl 'a + Iterator<Item = ViewHandle<T>> {
1170        self.panes
1171            .iter()
1172            .flat_map(|pane| pane.read(cx).items_of_type())
1173    }
1174
1175    pub fn active_item(&self, cx: &AppContext) -> Option<Box<dyn ItemHandle>> {
1176        self.active_pane().read(cx).active_item()
1177    }
1178
1179    fn active_project_path(&self, cx: &ViewContext<Self>) -> Option<ProjectPath> {
1180        self.active_item(cx).and_then(|item| item.project_path(cx))
1181    }
1182
1183    pub fn save_active_item(
1184        &mut self,
1185        force_name_change: bool,
1186        cx: &mut ViewContext<Self>,
1187    ) -> Task<Result<()>> {
1188        let project = self.project.clone();
1189        if let Some(item) = self.active_item(cx) {
1190            if !force_name_change && item.can_save(cx) {
1191                if item.has_conflict(cx.as_ref()) {
1192                    const CONFLICT_MESSAGE: &'static str = "This file has changed on disk since you started editing it. Do you want to overwrite it?";
1193
1194                    let mut answer = cx.prompt(
1195                        PromptLevel::Warning,
1196                        CONFLICT_MESSAGE,
1197                        &["Overwrite", "Cancel"],
1198                    );
1199                    cx.spawn(|_, mut cx| async move {
1200                        let answer = answer.recv().await;
1201                        if answer == Some(0) {
1202                            cx.update(|cx| item.save(project, cx)).await?;
1203                        }
1204                        Ok(())
1205                    })
1206                } else {
1207                    item.save(project, cx)
1208                }
1209            } else if item.is_singleton(cx) {
1210                let worktree = self.worktrees(cx).next();
1211                let start_abs_path = worktree
1212                    .and_then(|w| w.read(cx).as_local())
1213                    .map_or(Path::new(""), |w| w.abs_path())
1214                    .to_path_buf();
1215                let mut abs_path = cx.prompt_for_new_path(&start_abs_path);
1216                cx.spawn(|_, mut cx| async move {
1217                    if let Some(abs_path) = abs_path.recv().await.flatten() {
1218                        cx.update(|cx| item.save_as(project, abs_path, cx)).await?;
1219                    }
1220                    Ok(())
1221                })
1222            } else {
1223                Task::ready(Ok(()))
1224            }
1225        } else {
1226            Task::ready(Ok(()))
1227        }
1228    }
1229
1230    pub fn toggle_sidebar_item(&mut self, action: &ToggleSidebarItem, cx: &mut ViewContext<Self>) {
1231        let sidebar = match action.side {
1232            Side::Left => &mut self.left_sidebar,
1233            Side::Right => &mut self.right_sidebar,
1234        };
1235        let active_item = sidebar.update(cx, |sidebar, cx| {
1236            sidebar.toggle_item(action.item_index, cx);
1237            sidebar.active_item().map(|item| item.to_any())
1238        });
1239        if let Some(active_item) = active_item {
1240            cx.focus(active_item);
1241        } else {
1242            cx.focus_self();
1243        }
1244        cx.notify();
1245    }
1246
1247    pub fn toggle_sidebar_item_focus(
1248        &mut self,
1249        action: &ToggleSidebarItemFocus,
1250        cx: &mut ViewContext<Self>,
1251    ) {
1252        let sidebar = match action.side {
1253            Side::Left => &mut self.left_sidebar,
1254            Side::Right => &mut self.right_sidebar,
1255        };
1256        let active_item = sidebar.update(cx, |sidebar, cx| {
1257            sidebar.activate_item(action.item_index, cx);
1258            sidebar.active_item().cloned()
1259        });
1260        if let Some(active_item) = active_item {
1261            if active_item.is_focused(cx) {
1262                cx.focus_self();
1263            } else {
1264                cx.focus(active_item.to_any());
1265            }
1266        }
1267        cx.notify();
1268    }
1269
1270    fn add_pane(&mut self, cx: &mut ViewContext<Self>) -> ViewHandle<Pane> {
1271        let pane = cx.add_view(|cx| Pane::new(cx));
1272        let pane_id = pane.id();
1273        cx.subscribe(&pane, move |this, _, event, cx| {
1274            this.handle_pane_event(pane_id, event, cx)
1275        })
1276        .detach();
1277        self.panes.push(pane.clone());
1278        self.activate_pane(pane.clone(), cx);
1279        cx.emit(Event::PaneAdded(pane.clone()));
1280        pane
1281    }
1282
1283    pub fn add_item(&mut self, item: Box<dyn ItemHandle>, cx: &mut ViewContext<Self>) {
1284        let pane = self.active_pane().clone();
1285        Pane::add_item(self, pane, item, true, true, cx);
1286    }
1287
1288    pub fn open_path(
1289        &mut self,
1290        path: impl Into<ProjectPath>,
1291        focus_item: bool,
1292        cx: &mut ViewContext<Self>,
1293    ) -> Task<Result<Box<dyn ItemHandle>, Arc<anyhow::Error>>> {
1294        let pane = self.active_pane().downgrade();
1295        let task = self.load_path(path.into(), cx);
1296        cx.spawn(|this, mut cx| async move {
1297            let (project_entry_id, build_item) = task.await?;
1298            let pane = pane
1299                .upgrade(&cx)
1300                .ok_or_else(|| anyhow!("pane was closed"))?;
1301            this.update(&mut cx, |this, cx| {
1302                Ok(Pane::open_item(
1303                    this,
1304                    pane,
1305                    project_entry_id,
1306                    focus_item,
1307                    cx,
1308                    build_item,
1309                ))
1310            })
1311        })
1312    }
1313
1314    pub(crate) fn load_path(
1315        &mut self,
1316        path: ProjectPath,
1317        cx: &mut ViewContext<Self>,
1318    ) -> Task<
1319        Result<(
1320            ProjectEntryId,
1321            impl 'static + FnOnce(&mut MutableAppContext) -> Box<dyn ItemHandle>,
1322        )>,
1323    > {
1324        let project = self.project().clone();
1325        let project_item = project.update(cx, |project, cx| project.open_path(path, cx));
1326        let window_id = cx.window_id();
1327        cx.as_mut().spawn(|mut cx| async move {
1328            let (project_entry_id, project_item) = project_item.await?;
1329            let build_item = cx.update(|cx| {
1330                cx.default_global::<ProjectItemBuilders>()
1331                    .get(&project_item.model_type())
1332                    .ok_or_else(|| anyhow!("no item builder for project item"))
1333                    .cloned()
1334            })?;
1335            let build_item =
1336                move |cx: &mut MutableAppContext| build_item(window_id, project, project_item, cx);
1337            Ok((project_entry_id, build_item))
1338        })
1339    }
1340
1341    pub fn open_project_item<T>(
1342        &mut self,
1343        project_item: ModelHandle<T::Item>,
1344        cx: &mut ViewContext<Self>,
1345    ) -> ViewHandle<T>
1346    where
1347        T: ProjectItem,
1348    {
1349        use project::Item as _;
1350
1351        let entry_id = project_item.read(cx).entry_id(cx);
1352        if let Some(item) = entry_id
1353            .and_then(|entry_id| self.active_pane().read(cx).item_for_entry(entry_id, cx))
1354            .and_then(|item| item.downcast())
1355        {
1356            self.activate_item(&item, cx);
1357            return item;
1358        }
1359
1360        let item = cx.add_view(|cx| T::for_project_item(self.project().clone(), project_item, cx));
1361        self.add_item(Box::new(item.clone()), cx);
1362        item
1363    }
1364
1365    pub fn activate_item(&mut self, item: &dyn ItemHandle, cx: &mut ViewContext<Self>) -> bool {
1366        let result = self.panes.iter().find_map(|pane| {
1367            if let Some(ix) = pane.read(cx).index_for_item(item) {
1368                Some((pane.clone(), ix))
1369            } else {
1370                None
1371            }
1372        });
1373        if let Some((pane, ix)) = result {
1374            self.activate_pane(pane.clone(), cx);
1375            pane.update(cx, |pane, cx| pane.activate_item(ix, true, true, cx));
1376            true
1377        } else {
1378            false
1379        }
1380    }
1381
1382    pub fn activate_next_pane(&mut self, cx: &mut ViewContext<Self>) {
1383        let next_pane = {
1384            let panes = self.center.panes();
1385            let ix = panes
1386                .iter()
1387                .position(|pane| **pane == self.active_pane)
1388                .unwrap();
1389            let next_ix = (ix + 1) % panes.len();
1390            panes[next_ix].clone()
1391        };
1392        self.activate_pane(next_pane, cx);
1393    }
1394
1395    pub fn activate_previous_pane(&mut self, cx: &mut ViewContext<Self>) {
1396        let prev_pane = {
1397            let panes = self.center.panes();
1398            let ix = panes
1399                .iter()
1400                .position(|pane| **pane == self.active_pane)
1401                .unwrap();
1402            let prev_ix = if ix == 0 { panes.len() - 1 } else { ix - 1 };
1403            panes[prev_ix].clone()
1404        };
1405        self.activate_pane(prev_pane, cx);
1406    }
1407
1408    fn activate_pane(&mut self, pane: ViewHandle<Pane>, cx: &mut ViewContext<Self>) {
1409        if self.active_pane != pane {
1410            self.active_pane = pane.clone();
1411            self.status_bar.update(cx, |status_bar, cx| {
1412                status_bar.set_active_pane(&self.active_pane, cx);
1413            });
1414            self.active_item_path_changed(cx);
1415            cx.focus(&self.active_pane);
1416            cx.notify();
1417        }
1418
1419        self.update_followers(
1420            proto::update_followers::Variant::UpdateActiveView(proto::UpdateActiveView {
1421                id: self.active_item(cx).map(|item| item.id() as u64),
1422                leader_id: self.leader_for_pane(&pane).map(|id| id.0),
1423            }),
1424            cx,
1425        );
1426    }
1427
1428    fn handle_pane_event(
1429        &mut self,
1430        pane_id: usize,
1431        event: &pane::Event,
1432        cx: &mut ViewContext<Self>,
1433    ) {
1434        if let Some(pane) = self.pane(pane_id) {
1435            match event {
1436                pane::Event::Split(direction) => {
1437                    self.split_pane(pane, *direction, cx);
1438                }
1439                pane::Event::Remove => {
1440                    self.remove_pane(pane, cx);
1441                }
1442                pane::Event::Activate => {
1443                    self.activate_pane(pane, cx);
1444                }
1445                pane::Event::ActivateItem { local } => {
1446                    if *local {
1447                        self.unfollow(&pane, cx);
1448                    }
1449                    if pane == self.active_pane {
1450                        self.active_item_path_changed(cx);
1451                    }
1452                }
1453                pane::Event::ChangeItemTitle => {
1454                    if pane == self.active_pane {
1455                        self.active_item_path_changed(cx);
1456                    }
1457                }
1458            }
1459        } else {
1460            error!("pane {} not found", pane_id);
1461        }
1462    }
1463
1464    pub fn split_pane(
1465        &mut self,
1466        pane: ViewHandle<Pane>,
1467        direction: SplitDirection,
1468        cx: &mut ViewContext<Self>,
1469    ) -> ViewHandle<Pane> {
1470        let new_pane = self.add_pane(cx);
1471        self.activate_pane(new_pane.clone(), cx);
1472        if let Some(item) = pane.read(cx).active_item() {
1473            if let Some(clone) = item.clone_on_split(cx.as_mut()) {
1474                Pane::add_item(self, new_pane.clone(), clone, true, true, cx);
1475            }
1476        }
1477        self.center.split(&pane, &new_pane, direction).unwrap();
1478        cx.notify();
1479        new_pane
1480    }
1481
1482    fn remove_pane(&mut self, pane: ViewHandle<Pane>, cx: &mut ViewContext<Self>) {
1483        if self.center.remove(&pane).unwrap() {
1484            self.panes.retain(|p| p != &pane);
1485            self.activate_pane(self.panes.last().unwrap().clone(), cx);
1486            self.unfollow(&pane, cx);
1487            self.last_leaders_by_pane.remove(&pane.downgrade());
1488            cx.notify();
1489        } else {
1490            self.active_item_path_changed(cx);
1491        }
1492    }
1493
1494    pub fn panes(&self) -> &[ViewHandle<Pane>] {
1495        &self.panes
1496    }
1497
1498    fn pane(&self, pane_id: usize) -> Option<ViewHandle<Pane>> {
1499        self.panes.iter().find(|pane| pane.id() == pane_id).cloned()
1500    }
1501
1502    pub fn active_pane(&self) -> &ViewHandle<Pane> {
1503        &self.active_pane
1504    }
1505
1506    fn project_remote_id_changed(&mut self, remote_id: Option<u64>, cx: &mut ViewContext<Self>) {
1507        if let Some(remote_id) = remote_id {
1508            self.remote_entity_subscription =
1509                Some(self.client.add_view_for_remote_entity(remote_id, cx));
1510        } else {
1511            self.remote_entity_subscription.take();
1512        }
1513    }
1514
1515    fn collaborator_left(&mut self, peer_id: PeerId, cx: &mut ViewContext<Self>) {
1516        self.leader_state.followers.remove(&peer_id);
1517        if let Some(states_by_pane) = self.follower_states_by_leader.remove(&peer_id) {
1518            for state in states_by_pane.into_values() {
1519                for item in state.items_by_leader_view_id.into_values() {
1520                    if let FollowerItem::Loaded(item) = item {
1521                        item.set_leader_replica_id(None, cx);
1522                    }
1523                }
1524            }
1525        }
1526        cx.notify();
1527    }
1528
1529    pub fn toggle_follow(
1530        &mut self,
1531        ToggleFollow(leader_id): &ToggleFollow,
1532        cx: &mut ViewContext<Self>,
1533    ) -> Option<Task<Result<()>>> {
1534        let leader_id = *leader_id;
1535        let pane = self.active_pane().clone();
1536
1537        if let Some(prev_leader_id) = self.unfollow(&pane, cx) {
1538            if leader_id == prev_leader_id {
1539                return None;
1540            }
1541        }
1542
1543        self.last_leaders_by_pane
1544            .insert(pane.downgrade(), leader_id);
1545        self.follower_states_by_leader
1546            .entry(leader_id)
1547            .or_default()
1548            .insert(pane.clone(), Default::default());
1549        cx.notify();
1550
1551        let project_id = self.project.read(cx).remote_id()?;
1552        let request = self.client.request(proto::Follow {
1553            project_id,
1554            leader_id: leader_id.0,
1555        });
1556        Some(cx.spawn_weak(|this, mut cx| async move {
1557            let response = request.await?;
1558            if let Some(this) = this.upgrade(&cx) {
1559                this.update(&mut cx, |this, _| {
1560                    let state = this
1561                        .follower_states_by_leader
1562                        .get_mut(&leader_id)
1563                        .and_then(|states_by_pane| states_by_pane.get_mut(&pane))
1564                        .ok_or_else(|| anyhow!("following interrupted"))?;
1565                    state.active_view_id = response.active_view_id;
1566                    Ok::<_, anyhow::Error>(())
1567                })?;
1568                Self::add_views_from_leader(this, leader_id, vec![pane], response.views, &mut cx)
1569                    .await?;
1570            }
1571            Ok(())
1572        }))
1573    }
1574
1575    pub fn follow_next_collaborator(
1576        &mut self,
1577        _: &FollowNextCollaborator,
1578        cx: &mut ViewContext<Self>,
1579    ) -> Option<Task<Result<()>>> {
1580        let collaborators = self.project.read(cx).collaborators();
1581        let next_leader_id = if let Some(leader_id) = self.leader_for_pane(&self.active_pane) {
1582            let mut collaborators = collaborators.keys().copied();
1583            while let Some(peer_id) = collaborators.next() {
1584                if peer_id == leader_id {
1585                    break;
1586                }
1587            }
1588            collaborators.next()
1589        } else if let Some(last_leader_id) =
1590            self.last_leaders_by_pane.get(&self.active_pane.downgrade())
1591        {
1592            if collaborators.contains_key(last_leader_id) {
1593                Some(*last_leader_id)
1594            } else {
1595                None
1596            }
1597        } else {
1598            None
1599        };
1600
1601        next_leader_id
1602            .or_else(|| collaborators.keys().copied().next())
1603            .and_then(|leader_id| self.toggle_follow(&ToggleFollow(leader_id), cx))
1604    }
1605
1606    pub fn unfollow(
1607        &mut self,
1608        pane: &ViewHandle<Pane>,
1609        cx: &mut ViewContext<Self>,
1610    ) -> Option<PeerId> {
1611        for (leader_id, states_by_pane) in &mut self.follower_states_by_leader {
1612            let leader_id = *leader_id;
1613            if let Some(state) = states_by_pane.remove(&pane) {
1614                for (_, item) in state.items_by_leader_view_id {
1615                    if let FollowerItem::Loaded(item) = item {
1616                        item.set_leader_replica_id(None, cx);
1617                    }
1618                }
1619
1620                if states_by_pane.is_empty() {
1621                    self.follower_states_by_leader.remove(&leader_id);
1622                    if let Some(project_id) = self.project.read(cx).remote_id() {
1623                        self.client
1624                            .send(proto::Unfollow {
1625                                project_id,
1626                                leader_id: leader_id.0,
1627                            })
1628                            .log_err();
1629                    }
1630                }
1631
1632                cx.notify();
1633                return Some(leader_id);
1634            }
1635        }
1636        None
1637    }
1638
1639    fn render_connection_status(&self, cx: &mut RenderContext<Self>) -> Option<ElementBox> {
1640        let theme = &cx.global::<Settings>().theme;
1641        match &*self.client.status().borrow() {
1642            client::Status::ConnectionError
1643            | client::Status::ConnectionLost
1644            | client::Status::Reauthenticating
1645            | client::Status::Reconnecting { .. }
1646            | client::Status::ReconnectionError { .. } => Some(
1647                Container::new(
1648                    Align::new(
1649                        ConstrainedBox::new(
1650                            Svg::new("icons/offline-14.svg")
1651                                .with_color(theme.workspace.titlebar.offline_icon.color)
1652                                .boxed(),
1653                        )
1654                        .with_width(theme.workspace.titlebar.offline_icon.width)
1655                        .boxed(),
1656                    )
1657                    .boxed(),
1658                )
1659                .with_style(theme.workspace.titlebar.offline_icon.container)
1660                .boxed(),
1661            ),
1662            client::Status::UpgradeRequired => Some(
1663                Label::new(
1664                    "Please update Zed to collaborate".to_string(),
1665                    theme.workspace.titlebar.outdated_warning.text.clone(),
1666                )
1667                .contained()
1668                .with_style(theme.workspace.titlebar.outdated_warning.container)
1669                .aligned()
1670                .boxed(),
1671            ),
1672            _ => None,
1673        }
1674    }
1675
1676    fn render_titlebar(&self, theme: &Theme, cx: &mut RenderContext<Self>) -> ElementBox {
1677        let project = &self.project.read(cx);
1678        let replica_id = project.replica_id();
1679        let mut worktree_root_names = String::new();
1680        for (i, name) in project.worktree_root_names(cx).enumerate() {
1681            if i > 0 {
1682                worktree_root_names.push_str(", ");
1683            }
1684            worktree_root_names.push_str(name);
1685        }
1686
1687        ConstrainedBox::new(
1688            Container::new(
1689                Stack::new()
1690                    .with_child(
1691                        Label::new(worktree_root_names, theme.workspace.titlebar.title.clone())
1692                            .aligned()
1693                            .left()
1694                            .boxed(),
1695                    )
1696                    .with_child(
1697                        Align::new(
1698                            Flex::row()
1699                                .with_children(self.render_collaborators(theme, cx))
1700                                .with_children(self.render_current_user(
1701                                    self.user_store.read(cx).current_user().as_ref(),
1702                                    replica_id,
1703                                    theme,
1704                                    cx,
1705                                ))
1706                                .with_children(self.render_connection_status(cx))
1707                                .boxed(),
1708                        )
1709                        .right()
1710                        .boxed(),
1711                    )
1712                    .boxed(),
1713            )
1714            .with_style(theme.workspace.titlebar.container)
1715            .boxed(),
1716        )
1717        .with_height(theme.workspace.titlebar.height)
1718        .named("titlebar")
1719    }
1720
1721    fn active_item_path_changed(&mut self, cx: &mut ViewContext<Self>) {
1722        let active_entry = self.active_project_path(cx);
1723        self.project
1724            .update(cx, |project, cx| project.set_active_path(active_entry, cx));
1725        self.update_window_title(cx);
1726    }
1727
1728    fn update_window_title(&mut self, cx: &mut ViewContext<Self>) {
1729        let mut title = String::new();
1730        let project = self.project().read(cx);
1731        if let Some(path) = self.active_item(cx).and_then(|item| item.project_path(cx)) {
1732            let filename = path
1733                .path
1734                .file_name()
1735                .map(|s| s.to_string_lossy())
1736                .or_else(|| {
1737                    Some(Cow::Borrowed(
1738                        project
1739                            .worktree_for_id(path.worktree_id, cx)?
1740                            .read(cx)
1741                            .root_name(),
1742                    ))
1743                });
1744            if let Some(filename) = filename {
1745                title.push_str(filename.as_ref());
1746                title.push_str("");
1747            }
1748        }
1749        for (i, name) in project.worktree_root_names(cx).enumerate() {
1750            if i > 0 {
1751                title.push_str(", ");
1752            }
1753            title.push_str(name);
1754        }
1755        if title.is_empty() {
1756            title = "empty project".to_string();
1757        }
1758        cx.set_window_title(&title);
1759    }
1760
1761    fn render_collaborators(&self, theme: &Theme, cx: &mut RenderContext<Self>) -> Vec<ElementBox> {
1762        let mut collaborators = self
1763            .project
1764            .read(cx)
1765            .collaborators()
1766            .values()
1767            .cloned()
1768            .collect::<Vec<_>>();
1769        collaborators.sort_unstable_by_key(|collaborator| collaborator.replica_id);
1770        collaborators
1771            .into_iter()
1772            .filter_map(|collaborator| {
1773                Some(self.render_avatar(
1774                    collaborator.user.avatar.clone()?,
1775                    collaborator.replica_id,
1776                    Some(collaborator.peer_id),
1777                    theme,
1778                    cx,
1779                ))
1780            })
1781            .collect()
1782    }
1783
1784    fn render_current_user(
1785        &self,
1786        user: Option<&Arc<User>>,
1787        replica_id: ReplicaId,
1788        theme: &Theme,
1789        cx: &mut RenderContext<Self>,
1790    ) -> Option<ElementBox> {
1791        let status = *self.client.status().borrow();
1792        if let Some(avatar) = user.and_then(|user| user.avatar.clone()) {
1793            Some(self.render_avatar(avatar, replica_id, None, theme, cx))
1794        } else if matches!(status, client::Status::UpgradeRequired) {
1795            None
1796        } else {
1797            Some(
1798                MouseEventHandler::new::<Authenticate, _, _>(0, cx, |state, _| {
1799                    let style = theme
1800                        .workspace
1801                        .titlebar
1802                        .sign_in_prompt
1803                        .style_for(state, false);
1804                    Label::new("Sign in".to_string(), style.text.clone())
1805                        .contained()
1806                        .with_style(style.container)
1807                        .boxed()
1808                })
1809                .on_click(|_, _, cx| cx.dispatch_action(Authenticate))
1810                .with_cursor_style(CursorStyle::PointingHand)
1811                .aligned()
1812                .boxed(),
1813            )
1814        }
1815    }
1816
1817    fn render_avatar(
1818        &self,
1819        avatar: Arc<ImageData>,
1820        replica_id: ReplicaId,
1821        peer_id: Option<PeerId>,
1822        theme: &Theme,
1823        cx: &mut RenderContext<Self>,
1824    ) -> ElementBox {
1825        let replica_color = theme.editor.replica_selection_style(replica_id).cursor;
1826        let is_followed = peer_id.map_or(false, |peer_id| {
1827            self.follower_states_by_leader.contains_key(&peer_id)
1828        });
1829        let mut avatar_style = theme.workspace.titlebar.avatar;
1830        if is_followed {
1831            avatar_style.border = Border::all(1.0, replica_color);
1832        }
1833        let content = Stack::new()
1834            .with_child(
1835                Image::new(avatar)
1836                    .with_style(avatar_style)
1837                    .constrained()
1838                    .with_width(theme.workspace.titlebar.avatar_width)
1839                    .aligned()
1840                    .boxed(),
1841            )
1842            .with_child(
1843                AvatarRibbon::new(replica_color)
1844                    .constrained()
1845                    .with_width(theme.workspace.titlebar.avatar_ribbon.width)
1846                    .with_height(theme.workspace.titlebar.avatar_ribbon.height)
1847                    .aligned()
1848                    .bottom()
1849                    .boxed(),
1850            )
1851            .constrained()
1852            .with_width(theme.workspace.titlebar.avatar_width)
1853            .contained()
1854            .with_margin_left(theme.workspace.titlebar.avatar_margin)
1855            .boxed();
1856
1857        if let Some(peer_id) = peer_id {
1858            MouseEventHandler::new::<ToggleFollow, _, _>(replica_id.into(), cx, move |_, _| content)
1859                .with_cursor_style(CursorStyle::PointingHand)
1860                .on_click(move |_, _, cx| cx.dispatch_action(ToggleFollow(peer_id)))
1861                .boxed()
1862        } else {
1863            content
1864        }
1865    }
1866
1867    fn render_disconnected_overlay(&self, cx: &AppContext) -> Option<ElementBox> {
1868        if self.project.read(cx).is_read_only() {
1869            let theme = &cx.global::<Settings>().theme;
1870            Some(
1871                EventHandler::new(
1872                    Label::new(
1873                        "Your connection to the remote project has been lost.".to_string(),
1874                        theme.workspace.disconnected_overlay.text.clone(),
1875                    )
1876                    .aligned()
1877                    .contained()
1878                    .with_style(theme.workspace.disconnected_overlay.container)
1879                    .boxed(),
1880                )
1881                .capture_all::<Self>(0)
1882                .boxed(),
1883            )
1884        } else {
1885            None
1886        }
1887    }
1888
1889    fn render_notifications(&self, theme: &theme::Workspace) -> Option<ElementBox> {
1890        if self.notifications.is_empty() {
1891            None
1892        } else {
1893            Some(
1894                Flex::column()
1895                    .with_children(self.notifications.iter().map(|(_, _, notification)| {
1896                        ChildView::new(notification.as_ref())
1897                            .contained()
1898                            .with_style(theme.notification)
1899                            .boxed()
1900                    }))
1901                    .constrained()
1902                    .with_width(theme.notifications.width)
1903                    .contained()
1904                    .with_style(theme.notifications.container)
1905                    .aligned()
1906                    .bottom()
1907                    .right()
1908                    .boxed(),
1909            )
1910        }
1911    }
1912
1913    // RPC handlers
1914
1915    async fn handle_follow(
1916        this: ViewHandle<Self>,
1917        envelope: TypedEnvelope<proto::Follow>,
1918        _: Arc<Client>,
1919        mut cx: AsyncAppContext,
1920    ) -> Result<proto::FollowResponse> {
1921        this.update(&mut cx, |this, cx| {
1922            this.leader_state
1923                .followers
1924                .insert(envelope.original_sender_id()?);
1925
1926            let active_view_id = this
1927                .active_item(cx)
1928                .and_then(|i| i.to_followable_item_handle(cx))
1929                .map(|i| i.id() as u64);
1930            Ok(proto::FollowResponse {
1931                active_view_id,
1932                views: this
1933                    .panes()
1934                    .iter()
1935                    .flat_map(|pane| {
1936                        let leader_id = this.leader_for_pane(pane).map(|id| id.0);
1937                        pane.read(cx).items().filter_map({
1938                            let cx = &cx;
1939                            move |item| {
1940                                let id = item.id() as u64;
1941                                let item = item.to_followable_item_handle(cx)?;
1942                                let variant = item.to_state_proto(cx)?;
1943                                Some(proto::View {
1944                                    id,
1945                                    leader_id,
1946                                    variant: Some(variant),
1947                                })
1948                            }
1949                        })
1950                    })
1951                    .collect(),
1952            })
1953        })
1954    }
1955
1956    async fn handle_unfollow(
1957        this: ViewHandle<Self>,
1958        envelope: TypedEnvelope<proto::Unfollow>,
1959        _: Arc<Client>,
1960        mut cx: AsyncAppContext,
1961    ) -> Result<()> {
1962        this.update(&mut cx, |this, _| {
1963            this.leader_state
1964                .followers
1965                .remove(&envelope.original_sender_id()?);
1966            Ok(())
1967        })
1968    }
1969
1970    async fn handle_update_followers(
1971        this: ViewHandle<Self>,
1972        envelope: TypedEnvelope<proto::UpdateFollowers>,
1973        _: Arc<Client>,
1974        mut cx: AsyncAppContext,
1975    ) -> Result<()> {
1976        let leader_id = envelope.original_sender_id()?;
1977        match envelope
1978            .payload
1979            .variant
1980            .ok_or_else(|| anyhow!("invalid update"))?
1981        {
1982            proto::update_followers::Variant::UpdateActiveView(update_active_view) => {
1983                this.update(&mut cx, |this, cx| {
1984                    this.update_leader_state(leader_id, cx, |state, _| {
1985                        state.active_view_id = update_active_view.id;
1986                    });
1987                    Ok::<_, anyhow::Error>(())
1988                })
1989            }
1990            proto::update_followers::Variant::UpdateView(update_view) => {
1991                this.update(&mut cx, |this, cx| {
1992                    let variant = update_view
1993                        .variant
1994                        .ok_or_else(|| anyhow!("missing update view variant"))?;
1995                    this.update_leader_state(leader_id, cx, |state, cx| {
1996                        let variant = variant.clone();
1997                        match state
1998                            .items_by_leader_view_id
1999                            .entry(update_view.id)
2000                            .or_insert(FollowerItem::Loading(Vec::new()))
2001                        {
2002                            FollowerItem::Loaded(item) => {
2003                                item.apply_update_proto(variant, cx).log_err();
2004                            }
2005                            FollowerItem::Loading(updates) => updates.push(variant),
2006                        }
2007                    });
2008                    Ok(())
2009                })
2010            }
2011            proto::update_followers::Variant::CreateView(view) => {
2012                let panes = this.read_with(&cx, |this, _| {
2013                    this.follower_states_by_leader
2014                        .get(&leader_id)
2015                        .into_iter()
2016                        .flat_map(|states_by_pane| states_by_pane.keys())
2017                        .cloned()
2018                        .collect()
2019                });
2020                Self::add_views_from_leader(this.clone(), leader_id, panes, vec![view], &mut cx)
2021                    .await?;
2022                Ok(())
2023            }
2024        }
2025        .log_err();
2026
2027        Ok(())
2028    }
2029
2030    async fn add_views_from_leader(
2031        this: ViewHandle<Self>,
2032        leader_id: PeerId,
2033        panes: Vec<ViewHandle<Pane>>,
2034        views: Vec<proto::View>,
2035        cx: &mut AsyncAppContext,
2036    ) -> Result<()> {
2037        let project = this.read_with(cx, |this, _| this.project.clone());
2038        let replica_id = project
2039            .read_with(cx, |project, _| {
2040                project
2041                    .collaborators()
2042                    .get(&leader_id)
2043                    .map(|c| c.replica_id)
2044            })
2045            .ok_or_else(|| anyhow!("no such collaborator {}", leader_id))?;
2046
2047        let item_builders = cx.update(|cx| {
2048            cx.default_global::<FollowableItemBuilders>()
2049                .values()
2050                .map(|b| b.0)
2051                .collect::<Vec<_>>()
2052                .clone()
2053        });
2054
2055        let mut item_tasks_by_pane = HashMap::default();
2056        for pane in panes {
2057            let mut item_tasks = Vec::new();
2058            let mut leader_view_ids = Vec::new();
2059            for view in &views {
2060                let mut variant = view.variant.clone();
2061                if variant.is_none() {
2062                    Err(anyhow!("missing variant"))?;
2063                }
2064                for build_item in &item_builders {
2065                    let task =
2066                        cx.update(|cx| build_item(pane.clone(), project.clone(), &mut variant, cx));
2067                    if let Some(task) = task {
2068                        item_tasks.push(task);
2069                        leader_view_ids.push(view.id);
2070                        break;
2071                    } else {
2072                        assert!(variant.is_some());
2073                    }
2074                }
2075            }
2076
2077            item_tasks_by_pane.insert(pane, (item_tasks, leader_view_ids));
2078        }
2079
2080        for (pane, (item_tasks, leader_view_ids)) in item_tasks_by_pane {
2081            let items = futures::future::try_join_all(item_tasks).await?;
2082            this.update(cx, |this, cx| {
2083                let state = this
2084                    .follower_states_by_leader
2085                    .get_mut(&leader_id)?
2086                    .get_mut(&pane)?;
2087
2088                for (id, item) in leader_view_ids.into_iter().zip(items) {
2089                    item.set_leader_replica_id(Some(replica_id), cx);
2090                    match state.items_by_leader_view_id.entry(id) {
2091                        hash_map::Entry::Occupied(e) => {
2092                            let e = e.into_mut();
2093                            if let FollowerItem::Loading(updates) = e {
2094                                for update in updates.drain(..) {
2095                                    item.apply_update_proto(update, cx)
2096                                        .context("failed to apply view update")
2097                                        .log_err();
2098                                }
2099                            }
2100                            *e = FollowerItem::Loaded(item);
2101                        }
2102                        hash_map::Entry::Vacant(e) => {
2103                            e.insert(FollowerItem::Loaded(item));
2104                        }
2105                    }
2106                }
2107
2108                Some(())
2109            });
2110        }
2111        this.update(cx, |this, cx| this.leader_updated(leader_id, cx));
2112
2113        Ok(())
2114    }
2115
2116    fn update_followers(
2117        &self,
2118        update: proto::update_followers::Variant,
2119        cx: &AppContext,
2120    ) -> Option<()> {
2121        let project_id = self.project.read(cx).remote_id()?;
2122        if !self.leader_state.followers.is_empty() {
2123            self.client
2124                .send(proto::UpdateFollowers {
2125                    project_id,
2126                    follower_ids: self.leader_state.followers.iter().map(|f| f.0).collect(),
2127                    variant: Some(update),
2128                })
2129                .log_err();
2130        }
2131        None
2132    }
2133
2134    pub fn leader_for_pane(&self, pane: &ViewHandle<Pane>) -> Option<PeerId> {
2135        self.follower_states_by_leader
2136            .iter()
2137            .find_map(|(leader_id, state)| {
2138                if state.contains_key(pane) {
2139                    Some(*leader_id)
2140                } else {
2141                    None
2142                }
2143            })
2144    }
2145
2146    fn update_leader_state(
2147        &mut self,
2148        leader_id: PeerId,
2149        cx: &mut ViewContext<Self>,
2150        mut update_fn: impl FnMut(&mut FollowerState, &mut ViewContext<Self>),
2151    ) {
2152        for (_, state) in self
2153            .follower_states_by_leader
2154            .get_mut(&leader_id)
2155            .into_iter()
2156            .flatten()
2157        {
2158            update_fn(state, cx);
2159        }
2160        self.leader_updated(leader_id, cx);
2161    }
2162
2163    fn leader_updated(&mut self, leader_id: PeerId, cx: &mut ViewContext<Self>) -> Option<()> {
2164        let mut items_to_add = Vec::new();
2165        for (pane, state) in self.follower_states_by_leader.get(&leader_id)? {
2166            if let Some(active_item) = state
2167                .active_view_id
2168                .and_then(|id| state.items_by_leader_view_id.get(&id))
2169            {
2170                if let FollowerItem::Loaded(item) = active_item {
2171                    items_to_add.push((pane.clone(), item.boxed_clone()));
2172                }
2173            }
2174        }
2175
2176        for (pane, item) in items_to_add {
2177            Pane::add_item(self, pane.clone(), item.boxed_clone(), false, false, cx);
2178            if pane == self.active_pane {
2179                pane.update(cx, |pane, cx| pane.focus_active_item(cx));
2180            }
2181            cx.notify();
2182        }
2183        None
2184    }
2185}
2186
2187impl Entity for Workspace {
2188    type Event = Event;
2189}
2190
2191impl View for Workspace {
2192    fn ui_name() -> &'static str {
2193        "Workspace"
2194    }
2195
2196    fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
2197        let theme = cx.global::<Settings>().theme.clone();
2198        Stack::new()
2199            .with_child(
2200                Flex::column()
2201                    .with_child(self.render_titlebar(&theme, cx))
2202                    .with_child(
2203                        Stack::new()
2204                            .with_child({
2205                                Flex::row()
2206                                    .with_children(
2207                                        if self.left_sidebar.read(cx).active_item().is_some() {
2208                                            Some(
2209                                                ChildView::new(&self.left_sidebar)
2210                                                    .flex(0.8, false)
2211                                                    .boxed(),
2212                                            )
2213                                        } else {
2214                                            None
2215                                        },
2216                                    )
2217                                    .with_child(
2218                                        FlexItem::new(self.center.render(
2219                                            &theme,
2220                                            &self.follower_states_by_leader,
2221                                            self.project.read(cx).collaborators(),
2222                                        ))
2223                                        .flex(1., true)
2224                                        .boxed(),
2225                                    )
2226                                    .with_children(
2227                                        if self.right_sidebar.read(cx).active_item().is_some() {
2228                                            Some(
2229                                                ChildView::new(&self.right_sidebar)
2230                                                    .flex(0.8, false)
2231                                                    .boxed(),
2232                                            )
2233                                        } else {
2234                                            None
2235                                        },
2236                                    )
2237                                    .boxed()
2238                            })
2239                            .with_children(self.modal.as_ref().map(|m| {
2240                                ChildView::new(m)
2241                                    .contained()
2242                                    .with_style(theme.workspace.modal)
2243                                    .aligned()
2244                                    .top()
2245                                    .boxed()
2246                            }))
2247                            .with_children(self.render_notifications(&theme.workspace))
2248                            .flex(1.0, true)
2249                            .boxed(),
2250                    )
2251                    .with_child(ChildView::new(&self.status_bar).boxed())
2252                    .contained()
2253                    .with_background_color(theme.workspace.background)
2254                    .boxed(),
2255            )
2256            .with_children(self.render_disconnected_overlay(cx))
2257            .named("workspace")
2258    }
2259
2260    fn on_focus(&mut self, cx: &mut ViewContext<Self>) {
2261        cx.focus(&self.active_pane);
2262    }
2263}
2264
2265pub trait WorkspaceHandle {
2266    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath>;
2267}
2268
2269impl WorkspaceHandle for ViewHandle<Workspace> {
2270    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath> {
2271        self.read(cx)
2272            .worktrees(cx)
2273            .flat_map(|worktree| {
2274                let worktree_id = worktree.read(cx).id();
2275                worktree.read(cx).files(true, 0).map(move |f| ProjectPath {
2276                    worktree_id,
2277                    path: f.path.clone(),
2278                })
2279            })
2280            .collect::<Vec<_>>()
2281    }
2282}
2283
2284pub struct AvatarRibbon {
2285    color: Color,
2286}
2287
2288impl AvatarRibbon {
2289    pub fn new(color: Color) -> AvatarRibbon {
2290        AvatarRibbon { color }
2291    }
2292}
2293
2294impl Element for AvatarRibbon {
2295    type LayoutState = ();
2296
2297    type PaintState = ();
2298
2299    fn layout(
2300        &mut self,
2301        constraint: gpui::SizeConstraint,
2302        _: &mut gpui::LayoutContext,
2303    ) -> (gpui::geometry::vector::Vector2F, Self::LayoutState) {
2304        (constraint.max, ())
2305    }
2306
2307    fn paint(
2308        &mut self,
2309        bounds: gpui::geometry::rect::RectF,
2310        _: gpui::geometry::rect::RectF,
2311        _: &mut Self::LayoutState,
2312        cx: &mut gpui::PaintContext,
2313    ) -> Self::PaintState {
2314        let mut path = PathBuilder::new();
2315        path.reset(bounds.lower_left());
2316        path.curve_to(
2317            bounds.origin() + vec2f(bounds.height(), 0.),
2318            bounds.origin(),
2319        );
2320        path.line_to(bounds.upper_right() - vec2f(bounds.height(), 0.));
2321        path.curve_to(bounds.lower_right(), bounds.upper_right());
2322        path.line_to(bounds.lower_left());
2323        cx.scene.push_path(path.build(self.color, None));
2324    }
2325
2326    fn dispatch_event(
2327        &mut self,
2328        _: &gpui::Event,
2329        _: RectF,
2330        _: RectF,
2331        _: &mut Self::LayoutState,
2332        _: &mut Self::PaintState,
2333        _: &mut gpui::EventContext,
2334    ) -> bool {
2335        false
2336    }
2337
2338    fn debug(
2339        &self,
2340        bounds: gpui::geometry::rect::RectF,
2341        _: &Self::LayoutState,
2342        _: &Self::PaintState,
2343        _: &gpui::DebugContext,
2344    ) -> gpui::json::Value {
2345        json::json!({
2346            "type": "AvatarRibbon",
2347            "bounds": bounds.to_json(),
2348            "color": self.color.to_json(),
2349        })
2350    }
2351}
2352
2353impl std::fmt::Debug for OpenPaths {
2354    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2355        f.debug_struct("OpenPaths")
2356            .field("paths", &self.paths)
2357            .finish()
2358    }
2359}
2360
2361fn open(_: &Open, cx: &mut MutableAppContext) {
2362    let mut paths = cx.prompt_for_paths(PathPromptOptions {
2363        files: true,
2364        directories: true,
2365        multiple: true,
2366    });
2367    cx.spawn(|mut cx| async move {
2368        if let Some(paths) = paths.recv().await.flatten() {
2369            cx.update(|cx| cx.dispatch_global_action(OpenPaths { paths }));
2370        }
2371    })
2372    .detach();
2373}
2374
2375pub struct WorkspaceCreated(WeakViewHandle<Workspace>);
2376
2377pub fn activate_workspace_for_project(
2378    cx: &mut MutableAppContext,
2379    predicate: impl Fn(&mut Project, &mut ModelContext<Project>) -> bool,
2380) -> Option<ViewHandle<Workspace>> {
2381    for window_id in cx.window_ids().collect::<Vec<_>>() {
2382        if let Some(workspace_handle) = cx.root_view::<Workspace>(window_id) {
2383            let project = workspace_handle.read(cx).project.clone();
2384            if project.update(cx, &predicate) {
2385                cx.activate_window(window_id);
2386                return Some(workspace_handle);
2387            }
2388        }
2389    }
2390    None
2391}
2392
2393pub fn open_paths(
2394    abs_paths: &[PathBuf],
2395    app_state: &Arc<AppState>,
2396    cx: &mut MutableAppContext,
2397) -> Task<(
2398    ViewHandle<Workspace>,
2399    Vec<Option<Result<Box<dyn ItemHandle>, Arc<anyhow::Error>>>>,
2400)> {
2401    log::info!("open paths {:?}", abs_paths);
2402
2403    // Open paths in existing workspace if possible
2404    let existing =
2405        activate_workspace_for_project(cx, |project, cx| project.contains_paths(abs_paths, cx));
2406
2407    let app_state = app_state.clone();
2408    let abs_paths = abs_paths.to_vec();
2409    cx.spawn(|mut cx| async move {
2410        let mut new_project = None;
2411        let workspace = if let Some(existing) = existing {
2412            existing
2413        } else {
2414            let contains_directory =
2415                futures::future::join_all(abs_paths.iter().map(|path| app_state.fs.is_file(path)))
2416                    .await
2417                    .contains(&false);
2418
2419            cx.add_window((app_state.build_window_options)(), |cx| {
2420                let project = Project::local(
2421                    false,
2422                    app_state.client.clone(),
2423                    app_state.user_store.clone(),
2424                    app_state.project_store.clone(),
2425                    app_state.languages.clone(),
2426                    app_state.fs.clone(),
2427                    cx,
2428                );
2429                new_project = Some(project.clone());
2430                let mut workspace = Workspace::new(project, cx);
2431                (app_state.initialize_workspace)(&mut workspace, &app_state, cx);
2432                if contains_directory {
2433                    workspace.toggle_sidebar_item(
2434                        &ToggleSidebarItem {
2435                            side: Side::Left,
2436                            item_index: 0,
2437                        },
2438                        cx,
2439                    );
2440                }
2441                workspace
2442            })
2443            .1
2444        };
2445
2446        let items = workspace
2447            .update(&mut cx, |workspace, cx| workspace.open_paths(abs_paths, cx))
2448            .await;
2449
2450        if let Some(project) = new_project {
2451            project
2452                .update(&mut cx, |project, cx| project.restore_state(cx))
2453                .await
2454                .log_err();
2455        }
2456
2457        (workspace, items)
2458    })
2459}
2460
2461pub fn join_project(
2462    contact: Arc<Contact>,
2463    project_index: usize,
2464    app_state: &Arc<AppState>,
2465    cx: &mut MutableAppContext,
2466) {
2467    let project_id = contact.projects[project_index].id;
2468
2469    for window_id in cx.window_ids().collect::<Vec<_>>() {
2470        if let Some(workspace) = cx.root_view::<Workspace>(window_id) {
2471            if workspace.read(cx).project().read(cx).remote_id() == Some(project_id) {
2472                cx.activate_window(window_id);
2473                return;
2474            }
2475        }
2476    }
2477
2478    cx.add_window((app_state.build_window_options)(), |cx| {
2479        WaitingRoom::new(contact, project_index, app_state.clone(), cx)
2480    });
2481}
2482
2483fn open_new(app_state: &Arc<AppState>, cx: &mut MutableAppContext) {
2484    let (window_id, workspace) = cx.add_window((app_state.build_window_options)(), |cx| {
2485        let mut workspace = Workspace::new(
2486            Project::local(
2487                false,
2488                app_state.client.clone(),
2489                app_state.user_store.clone(),
2490                app_state.project_store.clone(),
2491                app_state.languages.clone(),
2492                app_state.fs.clone(),
2493                cx,
2494            ),
2495            cx,
2496        );
2497        (app_state.initialize_workspace)(&mut workspace, app_state, cx);
2498        workspace
2499    });
2500    cx.dispatch_action(window_id, vec![workspace.id()], &NewFile);
2501}
2502
2503#[cfg(test)]
2504mod tests {
2505    use super::*;
2506    use gpui::{ModelHandle, TestAppContext, ViewContext};
2507    use project::{FakeFs, Project, ProjectEntryId};
2508    use serde_json::json;
2509
2510    #[gpui::test]
2511    async fn test_tracking_active_path(cx: &mut TestAppContext) {
2512        cx.foreground().forbid_parking();
2513        Settings::test_async(cx);
2514        let fs = FakeFs::new(cx.background());
2515        fs.insert_tree(
2516            "/root1",
2517            json!({
2518                "one.txt": "",
2519                "two.txt": "",
2520            }),
2521        )
2522        .await;
2523        fs.insert_tree(
2524            "/root2",
2525            json!({
2526                "three.txt": "",
2527            }),
2528        )
2529        .await;
2530
2531        let project = Project::test(fs, ["root1".as_ref()], cx).await;
2532        let (window_id, workspace) = cx.add_window(|cx| Workspace::new(project.clone(), cx));
2533        let worktree_id = project.read_with(cx, |project, cx| {
2534            project.worktrees(cx).next().unwrap().read(cx).id()
2535        });
2536
2537        let item1 = cx.add_view(window_id, |_| {
2538            let mut item = TestItem::new();
2539            item.project_path = Some((worktree_id, "one.txt").into());
2540            item
2541        });
2542        let item2 = cx.add_view(window_id, |_| {
2543            let mut item = TestItem::new();
2544            item.project_path = Some((worktree_id, "two.txt").into());
2545            item
2546        });
2547
2548        // Add an item to an empty pane
2549        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item1), cx));
2550        project.read_with(cx, |project, cx| {
2551            assert_eq!(
2552                project.active_entry(),
2553                project.entry_for_path(&(worktree_id, "one.txt").into(), cx)
2554            );
2555        });
2556        assert_eq!(
2557            cx.current_window_title(window_id).as_deref(),
2558            Some("one.txt — root1")
2559        );
2560
2561        // Add a second item to a non-empty pane
2562        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item2), cx));
2563        assert_eq!(
2564            cx.current_window_title(window_id).as_deref(),
2565            Some("two.txt — root1")
2566        );
2567        project.read_with(cx, |project, cx| {
2568            assert_eq!(
2569                project.active_entry(),
2570                project.entry_for_path(&(worktree_id, "two.txt").into(), cx)
2571            );
2572        });
2573
2574        // Close the active item
2575        workspace
2576            .update(cx, |workspace, cx| {
2577                Pane::close_active_item(workspace, &Default::default(), cx).unwrap()
2578            })
2579            .await
2580            .unwrap();
2581        assert_eq!(
2582            cx.current_window_title(window_id).as_deref(),
2583            Some("one.txt — root1")
2584        );
2585        project.read_with(cx, |project, cx| {
2586            assert_eq!(
2587                project.active_entry(),
2588                project.entry_for_path(&(worktree_id, "one.txt").into(), cx)
2589            );
2590        });
2591
2592        // Add a project folder
2593        project
2594            .update(cx, |project, cx| {
2595                project.find_or_create_local_worktree("/root2", true, cx)
2596            })
2597            .await
2598            .unwrap();
2599        assert_eq!(
2600            cx.current_window_title(window_id).as_deref(),
2601            Some("one.txt — root1, root2")
2602        );
2603
2604        // Remove a project folder
2605        project.update(cx, |project, cx| {
2606            project.remove_worktree(worktree_id, cx);
2607        });
2608        assert_eq!(
2609            cx.current_window_title(window_id).as_deref(),
2610            Some("one.txt — root2")
2611        );
2612    }
2613
2614    #[gpui::test]
2615    async fn test_close_window(cx: &mut TestAppContext) {
2616        cx.foreground().forbid_parking();
2617        Settings::test_async(cx);
2618        let fs = FakeFs::new(cx.background());
2619        fs.insert_tree("/root", json!({ "one": "" })).await;
2620
2621        let project = Project::test(fs, ["root".as_ref()], cx).await;
2622        let (window_id, workspace) = cx.add_window(|cx| Workspace::new(project.clone(), cx));
2623
2624        // When there are no dirty items, there's nothing to do.
2625        let item1 = cx.add_view(window_id, |_| TestItem::new());
2626        workspace.update(cx, |w, cx| w.add_item(Box::new(item1.clone()), cx));
2627        let task = workspace.update(cx, |w, cx| w.prepare_to_close(cx));
2628        assert_eq!(task.await.unwrap(), true);
2629
2630        // When there are dirty untitled items, prompt to save each one. If the user
2631        // cancels any prompt, then abort.
2632        let item2 = cx.add_view(window_id, |_| {
2633            let mut item = TestItem::new();
2634            item.is_dirty = true;
2635            item
2636        });
2637        let item3 = cx.add_view(window_id, |_| {
2638            let mut item = TestItem::new();
2639            item.is_dirty = true;
2640            item.project_entry_ids = vec![ProjectEntryId::from_proto(1)];
2641            item
2642        });
2643        workspace.update(cx, |w, cx| {
2644            w.add_item(Box::new(item2.clone()), cx);
2645            w.add_item(Box::new(item3.clone()), cx);
2646        });
2647        let task = workspace.update(cx, |w, cx| w.prepare_to_close(cx));
2648        cx.foreground().run_until_parked();
2649        cx.simulate_prompt_answer(window_id, 2 /* cancel */);
2650        cx.foreground().run_until_parked();
2651        assert!(!cx.has_pending_prompt(window_id));
2652        assert_eq!(task.await.unwrap(), false);
2653    }
2654
2655    #[gpui::test]
2656    async fn test_close_pane_items(cx: &mut TestAppContext) {
2657        cx.foreground().forbid_parking();
2658        Settings::test_async(cx);
2659        let fs = FakeFs::new(cx.background());
2660
2661        let project = Project::test(fs, None, cx).await;
2662        let (window_id, workspace) = cx.add_window(|cx| Workspace::new(project, cx));
2663
2664        let item1 = cx.add_view(window_id, |_| {
2665            let mut item = TestItem::new();
2666            item.is_dirty = true;
2667            item.project_entry_ids = vec![ProjectEntryId::from_proto(1)];
2668            item
2669        });
2670        let item2 = cx.add_view(window_id, |_| {
2671            let mut item = TestItem::new();
2672            item.is_dirty = true;
2673            item.has_conflict = true;
2674            item.project_entry_ids = vec![ProjectEntryId::from_proto(2)];
2675            item
2676        });
2677        let item3 = cx.add_view(window_id, |_| {
2678            let mut item = TestItem::new();
2679            item.is_dirty = true;
2680            item.has_conflict = true;
2681            item.project_entry_ids = vec![ProjectEntryId::from_proto(3)];
2682            item
2683        });
2684        let item4 = cx.add_view(window_id, |_| {
2685            let mut item = TestItem::new();
2686            item.is_dirty = true;
2687            item
2688        });
2689        let pane = workspace.update(cx, |workspace, cx| {
2690            workspace.add_item(Box::new(item1.clone()), cx);
2691            workspace.add_item(Box::new(item2.clone()), cx);
2692            workspace.add_item(Box::new(item3.clone()), cx);
2693            workspace.add_item(Box::new(item4.clone()), cx);
2694            workspace.active_pane().clone()
2695        });
2696
2697        let close_items = workspace.update(cx, |workspace, cx| {
2698            pane.update(cx, |pane, cx| {
2699                pane.activate_item(1, true, true, cx);
2700                assert_eq!(pane.active_item().unwrap().id(), item2.id());
2701            });
2702
2703            let item1_id = item1.id();
2704            let item3_id = item3.id();
2705            let item4_id = item4.id();
2706            Pane::close_items(workspace, pane.clone(), cx, move |id| {
2707                [item1_id, item3_id, item4_id].contains(&id)
2708            })
2709        });
2710
2711        cx.foreground().run_until_parked();
2712        pane.read_with(cx, |pane, _| {
2713            assert_eq!(pane.items().count(), 4);
2714            assert_eq!(pane.active_item().unwrap().id(), item1.id());
2715        });
2716
2717        cx.simulate_prompt_answer(window_id, 0);
2718        cx.foreground().run_until_parked();
2719        pane.read_with(cx, |pane, cx| {
2720            assert_eq!(item1.read(cx).save_count, 1);
2721            assert_eq!(item1.read(cx).save_as_count, 0);
2722            assert_eq!(item1.read(cx).reload_count, 0);
2723            assert_eq!(pane.items().count(), 3);
2724            assert_eq!(pane.active_item().unwrap().id(), item3.id());
2725        });
2726
2727        cx.simulate_prompt_answer(window_id, 1);
2728        cx.foreground().run_until_parked();
2729        pane.read_with(cx, |pane, cx| {
2730            assert_eq!(item3.read(cx).save_count, 0);
2731            assert_eq!(item3.read(cx).save_as_count, 0);
2732            assert_eq!(item3.read(cx).reload_count, 1);
2733            assert_eq!(pane.items().count(), 2);
2734            assert_eq!(pane.active_item().unwrap().id(), item4.id());
2735        });
2736
2737        cx.simulate_prompt_answer(window_id, 0);
2738        cx.foreground().run_until_parked();
2739        cx.simulate_new_path_selection(|_| Some(Default::default()));
2740        close_items.await.unwrap();
2741        pane.read_with(cx, |pane, cx| {
2742            assert_eq!(item4.read(cx).save_count, 0);
2743            assert_eq!(item4.read(cx).save_as_count, 1);
2744            assert_eq!(item4.read(cx).reload_count, 0);
2745            assert_eq!(pane.items().count(), 1);
2746            assert_eq!(pane.active_item().unwrap().id(), item2.id());
2747        });
2748    }
2749
2750    #[gpui::test]
2751    async fn test_prompting_to_save_only_on_last_item_for_entry(cx: &mut TestAppContext) {
2752        cx.foreground().forbid_parking();
2753        Settings::test_async(cx);
2754        let fs = FakeFs::new(cx.background());
2755
2756        let project = Project::test(fs, [], cx).await;
2757        let (window_id, workspace) = cx.add_window(|cx| Workspace::new(project, cx));
2758
2759        // Create several workspace items with single project entries, and two
2760        // workspace items with multiple project entries.
2761        let single_entry_items = (0..=4)
2762            .map(|project_entry_id| {
2763                let mut item = TestItem::new();
2764                item.is_dirty = true;
2765                item.project_entry_ids = vec![ProjectEntryId::from_proto(project_entry_id)];
2766                item.is_singleton = true;
2767                item
2768            })
2769            .collect::<Vec<_>>();
2770        let item_2_3 = {
2771            let mut item = TestItem::new();
2772            item.is_dirty = true;
2773            item.is_singleton = false;
2774            item.project_entry_ids =
2775                vec![ProjectEntryId::from_proto(2), ProjectEntryId::from_proto(3)];
2776            item
2777        };
2778        let item_3_4 = {
2779            let mut item = TestItem::new();
2780            item.is_dirty = true;
2781            item.is_singleton = false;
2782            item.project_entry_ids =
2783                vec![ProjectEntryId::from_proto(3), ProjectEntryId::from_proto(4)];
2784            item
2785        };
2786
2787        // Create two panes that contain the following project entries:
2788        //   left pane:
2789        //     multi-entry items:   (2, 3)
2790        //     single-entry items:  0, 1, 2, 3, 4
2791        //   right pane:
2792        //     single-entry items:  1
2793        //     multi-entry items:   (3, 4)
2794        let left_pane = workspace.update(cx, |workspace, cx| {
2795            let left_pane = workspace.active_pane().clone();
2796            let right_pane = workspace.split_pane(left_pane.clone(), SplitDirection::Right, cx);
2797
2798            workspace.activate_pane(left_pane.clone(), cx);
2799            workspace.add_item(Box::new(cx.add_view(|_| item_2_3.clone())), cx);
2800            for item in &single_entry_items {
2801                workspace.add_item(Box::new(cx.add_view(|_| item.clone())), cx);
2802            }
2803
2804            workspace.activate_pane(right_pane.clone(), cx);
2805            workspace.add_item(Box::new(cx.add_view(|_| single_entry_items[1].clone())), cx);
2806            workspace.add_item(Box::new(cx.add_view(|_| item_3_4.clone())), cx);
2807
2808            left_pane
2809        });
2810
2811        // When closing all of the items in the left pane, we should be prompted twice:
2812        // once for project entry 0, and once for project entry 2. After those two
2813        // prompts, the task should complete.
2814        let close = workspace.update(cx, |workspace, cx| {
2815            workspace.activate_pane(left_pane.clone(), cx);
2816            Pane::close_items(workspace, left_pane.clone(), cx, |_| true)
2817        });
2818
2819        cx.foreground().run_until_parked();
2820        left_pane.read_with(cx, |pane, cx| {
2821            assert_eq!(
2822                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
2823                &[ProjectEntryId::from_proto(0)]
2824            );
2825        });
2826        cx.simulate_prompt_answer(window_id, 0);
2827
2828        cx.foreground().run_until_parked();
2829        left_pane.read_with(cx, |pane, cx| {
2830            assert_eq!(
2831                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
2832                &[ProjectEntryId::from_proto(2)]
2833            );
2834        });
2835        cx.simulate_prompt_answer(window_id, 0);
2836
2837        cx.foreground().run_until_parked();
2838        close.await.unwrap();
2839        left_pane.read_with(cx, |pane, _| {
2840            assert_eq!(pane.items().count(), 0);
2841        });
2842    }
2843
2844    #[derive(Clone)]
2845    struct TestItem {
2846        save_count: usize,
2847        save_as_count: usize,
2848        reload_count: usize,
2849        is_dirty: bool,
2850        has_conflict: bool,
2851        project_entry_ids: Vec<ProjectEntryId>,
2852        project_path: Option<ProjectPath>,
2853        is_singleton: bool,
2854    }
2855
2856    impl TestItem {
2857        fn new() -> Self {
2858            Self {
2859                save_count: 0,
2860                save_as_count: 0,
2861                reload_count: 0,
2862                is_dirty: false,
2863                has_conflict: false,
2864                project_entry_ids: Vec::new(),
2865                project_path: None,
2866                is_singleton: true,
2867            }
2868        }
2869    }
2870
2871    impl Entity for TestItem {
2872        type Event = ();
2873    }
2874
2875    impl View for TestItem {
2876        fn ui_name() -> &'static str {
2877            "TestItem"
2878        }
2879
2880        fn render(&mut self, _: &mut RenderContext<Self>) -> ElementBox {
2881            Empty::new().boxed()
2882        }
2883    }
2884
2885    impl Item for TestItem {
2886        fn tab_content(&self, _: &theme::Tab, _: &AppContext) -> ElementBox {
2887            Empty::new().boxed()
2888        }
2889
2890        fn project_path(&self, _: &AppContext) -> Option<ProjectPath> {
2891            self.project_path.clone()
2892        }
2893
2894        fn project_entry_ids(&self, _: &AppContext) -> SmallVec<[ProjectEntryId; 3]> {
2895            self.project_entry_ids.iter().copied().collect()
2896        }
2897
2898        fn is_singleton(&self, _: &AppContext) -> bool {
2899            self.is_singleton
2900        }
2901
2902        fn set_nav_history(&mut self, _: ItemNavHistory, _: &mut ViewContext<Self>) {}
2903
2904        fn clone_on_split(&self, _: &mut ViewContext<Self>) -> Option<Self>
2905        where
2906            Self: Sized,
2907        {
2908            Some(self.clone())
2909        }
2910
2911        fn is_dirty(&self, _: &AppContext) -> bool {
2912            self.is_dirty
2913        }
2914
2915        fn has_conflict(&self, _: &AppContext) -> bool {
2916            self.has_conflict
2917        }
2918
2919        fn can_save(&self, _: &AppContext) -> bool {
2920            self.project_entry_ids.len() > 0
2921        }
2922
2923        fn save(
2924            &mut self,
2925            _: ModelHandle<Project>,
2926            _: &mut ViewContext<Self>,
2927        ) -> Task<anyhow::Result<()>> {
2928            self.save_count += 1;
2929            Task::ready(Ok(()))
2930        }
2931
2932        fn save_as(
2933            &mut self,
2934            _: ModelHandle<Project>,
2935            _: std::path::PathBuf,
2936            _: &mut ViewContext<Self>,
2937        ) -> Task<anyhow::Result<()>> {
2938            self.save_as_count += 1;
2939            Task::ready(Ok(()))
2940        }
2941
2942        fn reload(
2943            &mut self,
2944            _: ModelHandle<Project>,
2945            _: &mut ViewContext<Self>,
2946        ) -> Task<anyhow::Result<()>> {
2947            self.reload_count += 1;
2948            Task::ready(Ok(()))
2949        }
2950
2951        fn should_update_tab_on_event(_: &Self::Event) -> bool {
2952            true
2953        }
2954    }
2955}