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