workspace.rs

   1/// NOTE: Focus only 'takes' after an update has flushed_effects. Pane sends an event in on_focus_in
   2/// which the workspace uses to change the activated pane.
   3///
   4/// This may cause issues when you're trying to write tests that use workspace focus to add items at
   5/// specific locations.
   6pub mod dock;
   7pub mod pane;
   8pub mod pane_group;
   9pub mod searchable;
  10pub mod sidebar;
  11mod status_bar;
  12mod toolbar;
  13mod waiting_room;
  14
  15use anyhow::{anyhow, Context, Result};
  16use client::{
  17    proto, Authenticate, Client, Contact, PeerId, Subscription, TypedEnvelope, User, UserStore,
  18};
  19use clock::ReplicaId;
  20use collections::{hash_map, HashMap, HashSet};
  21use dock::{DefaultItemFactory, Dock, ToggleDockButton};
  22use drag_and_drop::DragAndDrop;
  23use futures::{channel::oneshot, FutureExt};
  24use gpui::{
  25    actions,
  26    color::Color,
  27    elements::*,
  28    geometry::{rect::RectF, vector::vec2f, PathBuilder},
  29    impl_actions, impl_internal_actions,
  30    json::{self, ToJson},
  31    platform::{CursorStyle, WindowOptions},
  32    AnyModelHandle, AnyViewHandle, AppContext, AsyncAppContext, Border, Entity, ImageData,
  33    ModelContext, ModelHandle, MouseButton, MutableAppContext, PathPromptOptions, PromptLevel,
  34    RenderContext, Task, View, ViewContext, ViewHandle, WeakViewHandle,
  35};
  36use language::LanguageRegistry;
  37use log::{error, warn};
  38pub use pane::*;
  39pub use pane_group::*;
  40use postage::prelude::Stream;
  41use project::{fs, Fs, Project, ProjectEntryId, ProjectPath, ProjectStore, Worktree, WorktreeId};
  42use searchable::SearchableItemHandle;
  43use serde::Deserialize;
  44use settings::{Autosave, DockAnchor, Settings};
  45use sidebar::{Sidebar, SidebarButtons, SidebarSide, ToggleSidebarItem};
  46use smallvec::SmallVec;
  47use status_bar::StatusBar;
  48pub use status_bar::StatusItemView;
  49use std::{
  50    any::{Any, TypeId},
  51    borrow::Cow,
  52    cell::RefCell,
  53    fmt,
  54    future::Future,
  55    ops::Range,
  56    path::{Path, PathBuf},
  57    rc::Rc,
  58    sync::{
  59        atomic::{AtomicBool, Ordering::SeqCst},
  60        Arc,
  61    },
  62    time::Duration,
  63};
  64use theme::{Theme, ThemeRegistry};
  65pub use toolbar::{ToolbarItemLocation, ToolbarItemView};
  66use util::ResultExt;
  67use waiting_room::WaitingRoom;
  68
  69type ProjectItemBuilders = HashMap<
  70    TypeId,
  71    fn(ModelHandle<Project>, AnyModelHandle, &mut ViewContext<Pane>) -> Box<dyn ItemHandle>,
  72>;
  73
  74type FollowableItemBuilder = fn(
  75    ViewHandle<Pane>,
  76    ModelHandle<Project>,
  77    &mut Option<proto::view::Variant>,
  78    &mut MutableAppContext,
  79) -> Option<Task<Result<Box<dyn FollowableItemHandle>>>>;
  80type FollowableItemBuilders = HashMap<
  81    TypeId,
  82    (
  83        FollowableItemBuilder,
  84        fn(AnyViewHandle) -> Box<dyn FollowableItemHandle>,
  85    ),
  86>;
  87
  88#[derive(Clone, PartialEq)]
  89pub struct RemoveWorktreeFromProject(pub WorktreeId);
  90
  91actions!(
  92    workspace,
  93    [
  94        Open,
  95        NewFile,
  96        NewWindow,
  97        CloseWindow,
  98        AddFolderToProject,
  99        Unfollow,
 100        Save,
 101        SaveAs,
 102        SaveAll,
 103        ActivatePreviousPane,
 104        ActivateNextPane,
 105        FollowNextCollaborator,
 106        ToggleLeftSidebar,
 107        ToggleRightSidebar,
 108        NewTerminal,
 109        NewSearch
 110    ]
 111);
 112
 113#[derive(Clone, PartialEq)]
 114pub struct OpenPaths {
 115    pub paths: Vec<PathBuf>,
 116}
 117
 118#[derive(Clone, Deserialize, PartialEq)]
 119pub struct ToggleProjectOnline {
 120    #[serde(skip_deserializing)]
 121    pub project: Option<ModelHandle<Project>>,
 122}
 123
 124#[derive(Clone, Deserialize, PartialEq)]
 125pub struct ActivatePane(pub usize);
 126
 127#[derive(Clone, PartialEq)]
 128pub struct ToggleFollow(pub PeerId);
 129
 130#[derive(Clone, PartialEq)]
 131pub struct JoinProject {
 132    pub contact: Arc<Contact>,
 133    pub project_index: usize,
 134}
 135
 136impl_internal_actions!(
 137    workspace,
 138    [
 139        OpenPaths,
 140        ToggleFollow,
 141        JoinProject,
 142        RemoveWorktreeFromProject
 143    ]
 144);
 145impl_actions!(workspace, [ToggleProjectOnline, ActivatePane]);
 146
 147pub fn init(app_state: Arc<AppState>, cx: &mut MutableAppContext) {
 148    pane::init(cx);
 149    dock::init(cx);
 150
 151    cx.add_global_action(open);
 152    cx.add_global_action({
 153        let app_state = Arc::downgrade(&app_state);
 154        move |action: &OpenPaths, cx: &mut MutableAppContext| {
 155            if let Some(app_state) = app_state.upgrade() {
 156                open_paths(&action.paths, &app_state, cx).detach();
 157            }
 158        }
 159    });
 160    cx.add_global_action({
 161        let app_state = Arc::downgrade(&app_state);
 162        move |_: &NewFile, cx: &mut MutableAppContext| {
 163            if let Some(app_state) = app_state.upgrade() {
 164                open_new(&app_state, cx)
 165            }
 166        }
 167    });
 168    cx.add_global_action({
 169        let app_state = Arc::downgrade(&app_state);
 170        move |_: &NewWindow, cx: &mut MutableAppContext| {
 171            if let Some(app_state) = app_state.upgrade() {
 172                open_new(&app_state, cx)
 173            }
 174        }
 175    });
 176    cx.add_global_action({
 177        let app_state = Arc::downgrade(&app_state);
 178        move |action: &JoinProject, cx: &mut MutableAppContext| {
 179            if let Some(app_state) = app_state.upgrade() {
 180                join_project(action.contact.clone(), action.project_index, &app_state, cx);
 181            }
 182        }
 183    });
 184
 185    cx.add_async_action(Workspace::toggle_follow);
 186    cx.add_async_action(Workspace::follow_next_collaborator);
 187    cx.add_async_action(Workspace::close);
 188    cx.add_async_action(Workspace::save_all);
 189    cx.add_action(Workspace::add_folder_to_project);
 190    cx.add_action(Workspace::remove_folder_from_project);
 191    cx.add_action(Workspace::toggle_project_online);
 192    cx.add_action(
 193        |workspace: &mut Workspace, _: &Unfollow, cx: &mut ViewContext<Workspace>| {
 194            let pane = workspace.active_pane().clone();
 195            workspace.unfollow(&pane, cx);
 196        },
 197    );
 198    cx.add_action(
 199        |workspace: &mut Workspace, _: &Save, cx: &mut ViewContext<Workspace>| {
 200            workspace.save_active_item(false, cx).detach_and_log_err(cx);
 201        },
 202    );
 203    cx.add_action(
 204        |workspace: &mut Workspace, _: &SaveAs, cx: &mut ViewContext<Workspace>| {
 205            workspace.save_active_item(true, cx).detach_and_log_err(cx);
 206        },
 207    );
 208    cx.add_action(Workspace::toggle_sidebar_item);
 209    cx.add_action(Workspace::focus_center);
 210    cx.add_action(|workspace: &mut Workspace, _: &ActivatePreviousPane, cx| {
 211        workspace.activate_previous_pane(cx)
 212    });
 213    cx.add_action(|workspace: &mut Workspace, _: &ActivateNextPane, cx| {
 214        workspace.activate_next_pane(cx)
 215    });
 216    cx.add_action(|workspace: &mut Workspace, _: &ToggleLeftSidebar, cx| {
 217        workspace.toggle_sidebar(SidebarSide::Left, cx);
 218    });
 219    cx.add_action(|workspace: &mut Workspace, _: &ToggleRightSidebar, cx| {
 220        workspace.toggle_sidebar(SidebarSide::Right, cx);
 221    });
 222    cx.add_action(Workspace::activate_pane_at_index);
 223
 224    let client = &app_state.client;
 225    client.add_view_request_handler(Workspace::handle_follow);
 226    client.add_view_message_handler(Workspace::handle_unfollow);
 227    client.add_view_message_handler(Workspace::handle_update_followers);
 228}
 229
 230pub fn register_project_item<I: ProjectItem>(cx: &mut MutableAppContext) {
 231    cx.update_default_global(|builders: &mut ProjectItemBuilders, _| {
 232        builders.insert(TypeId::of::<I::Item>(), |project, model, cx| {
 233            let item = model.downcast::<I::Item>().unwrap();
 234            Box::new(cx.add_view(|cx| I::for_project_item(project, item, cx)))
 235        });
 236    });
 237}
 238
 239pub fn register_followable_item<I: FollowableItem>(cx: &mut MutableAppContext) {
 240    cx.update_default_global(|builders: &mut FollowableItemBuilders, _| {
 241        builders.insert(
 242            TypeId::of::<I>(),
 243            (
 244                |pane, project, state, cx| {
 245                    I::from_state_proto(pane, project, state, cx).map(|task| {
 246                        cx.foreground()
 247                            .spawn(async move { Ok(Box::new(task.await?) as Box<_>) })
 248                    })
 249                },
 250                |this| Box::new(this.downcast::<I>().unwrap()),
 251            ),
 252        );
 253    });
 254}
 255
 256pub struct AppState {
 257    pub languages: Arc<LanguageRegistry>,
 258    pub themes: Arc<ThemeRegistry>,
 259    pub client: Arc<client::Client>,
 260    pub user_store: ModelHandle<client::UserStore>,
 261    pub project_store: ModelHandle<ProjectStore>,
 262    pub fs: Arc<dyn fs::Fs>,
 263    pub build_window_options: fn() -> WindowOptions<'static>,
 264    pub initialize_workspace: fn(&mut Workspace, &Arc<AppState>, &mut ViewContext<Workspace>),
 265    pub default_item_factory: DefaultItemFactory,
 266}
 267
 268#[derive(Eq, PartialEq, Hash)]
 269pub enum ItemEvent {
 270    CloseItem,
 271    UpdateTab,
 272    UpdateBreadcrumbs,
 273    Edit,
 274}
 275
 276pub trait Item: View {
 277    fn deactivated(&mut self, _: &mut ViewContext<Self>) {}
 278    fn workspace_deactivated(&mut self, _: &mut ViewContext<Self>) {}
 279    fn navigate(&mut self, _: Box<dyn Any>, _: &mut ViewContext<Self>) -> bool {
 280        false
 281    }
 282    fn tab_description<'a>(&'a self, _: usize, _: &'a AppContext) -> Option<Cow<'a, str>> {
 283        None
 284    }
 285    fn tab_content(&self, detail: Option<usize>, style: &theme::Tab, cx: &AppContext)
 286        -> ElementBox;
 287    fn project_path(&self, cx: &AppContext) -> Option<ProjectPath>;
 288    fn project_entry_ids(&self, cx: &AppContext) -> SmallVec<[ProjectEntryId; 3]>;
 289    fn is_singleton(&self, cx: &AppContext) -> bool;
 290    fn set_nav_history(&mut self, _: ItemNavHistory, _: &mut ViewContext<Self>);
 291    fn clone_on_split(&self, _: &mut ViewContext<Self>) -> Option<Self>
 292    where
 293        Self: Sized,
 294    {
 295        None
 296    }
 297    fn is_dirty(&self, _: &AppContext) -> bool {
 298        false
 299    }
 300    fn has_conflict(&self, _: &AppContext) -> bool {
 301        false
 302    }
 303    fn can_save(&self, cx: &AppContext) -> bool;
 304    fn save(
 305        &mut self,
 306        project: ModelHandle<Project>,
 307        cx: &mut ViewContext<Self>,
 308    ) -> Task<Result<()>>;
 309    fn save_as(
 310        &mut self,
 311        project: ModelHandle<Project>,
 312        abs_path: PathBuf,
 313        cx: &mut ViewContext<Self>,
 314    ) -> Task<Result<()>>;
 315    fn reload(
 316        &mut self,
 317        project: ModelHandle<Project>,
 318        cx: &mut ViewContext<Self>,
 319    ) -> Task<Result<()>>;
 320    fn git_diff_recalc(
 321        &mut self,
 322        _project: ModelHandle<Project>,
 323        _cx: &mut ViewContext<Self>,
 324    ) -> Task<Result<()>> {
 325        Task::ready(Ok(()))
 326    }
 327    fn to_item_events(event: &Self::Event) -> Vec<ItemEvent>;
 328    fn should_close_item_on_event(_: &Self::Event) -> bool {
 329        false
 330    }
 331    fn should_update_tab_on_event(_: &Self::Event) -> bool {
 332        false
 333    }
 334    fn is_edit_event(_: &Self::Event) -> bool {
 335        false
 336    }
 337    fn act_as_type(
 338        &self,
 339        type_id: TypeId,
 340        self_handle: &ViewHandle<Self>,
 341        _: &AppContext,
 342    ) -> Option<AnyViewHandle> {
 343        if TypeId::of::<Self>() == type_id {
 344            Some(self_handle.into())
 345        } else {
 346            None
 347        }
 348    }
 349    fn as_searchable(&self, _: &ViewHandle<Self>) -> Option<Box<dyn SearchableItemHandle>> {
 350        None
 351    }
 352
 353    fn breadcrumb_location(&self) -> ToolbarItemLocation {
 354        ToolbarItemLocation::Hidden
 355    }
 356    fn breadcrumbs(&self, _theme: &Theme, _cx: &AppContext) -> Option<Vec<ElementBox>> {
 357        None
 358    }
 359}
 360
 361pub trait ProjectItem: Item {
 362    type Item: project::Item;
 363
 364    fn for_project_item(
 365        project: ModelHandle<Project>,
 366        item: ModelHandle<Self::Item>,
 367        cx: &mut ViewContext<Self>,
 368    ) -> Self;
 369}
 370
 371pub trait FollowableItem: Item {
 372    fn to_state_proto(&self, cx: &AppContext) -> Option<proto::view::Variant>;
 373    fn from_state_proto(
 374        pane: ViewHandle<Pane>,
 375        project: ModelHandle<Project>,
 376        state: &mut Option<proto::view::Variant>,
 377        cx: &mut MutableAppContext,
 378    ) -> Option<Task<Result<ViewHandle<Self>>>>;
 379    fn add_event_to_update_proto(
 380        &self,
 381        event: &Self::Event,
 382        update: &mut Option<proto::update_view::Variant>,
 383        cx: &AppContext,
 384    ) -> bool;
 385    fn apply_update_proto(
 386        &mut self,
 387        message: proto::update_view::Variant,
 388        cx: &mut ViewContext<Self>,
 389    ) -> Result<()>;
 390
 391    fn set_leader_replica_id(&mut self, leader_replica_id: Option<u16>, cx: &mut ViewContext<Self>);
 392    fn should_unfollow_on_event(event: &Self::Event, cx: &AppContext) -> bool;
 393}
 394
 395pub trait FollowableItemHandle: ItemHandle {
 396    fn set_leader_replica_id(&self, leader_replica_id: Option<u16>, cx: &mut MutableAppContext);
 397    fn to_state_proto(&self, cx: &AppContext) -> Option<proto::view::Variant>;
 398    fn add_event_to_update_proto(
 399        &self,
 400        event: &dyn Any,
 401        update: &mut Option<proto::update_view::Variant>,
 402        cx: &AppContext,
 403    ) -> bool;
 404    fn apply_update_proto(
 405        &self,
 406        message: proto::update_view::Variant,
 407        cx: &mut MutableAppContext,
 408    ) -> Result<()>;
 409    fn should_unfollow_on_event(&self, event: &dyn Any, cx: &AppContext) -> bool;
 410}
 411
 412impl<T: FollowableItem> FollowableItemHandle for ViewHandle<T> {
 413    fn set_leader_replica_id(&self, leader_replica_id: Option<u16>, cx: &mut MutableAppContext) {
 414        self.update(cx, |this, cx| {
 415            this.set_leader_replica_id(leader_replica_id, cx)
 416        })
 417    }
 418
 419    fn to_state_proto(&self, cx: &AppContext) -> Option<proto::view::Variant> {
 420        self.read(cx).to_state_proto(cx)
 421    }
 422
 423    fn add_event_to_update_proto(
 424        &self,
 425        event: &dyn Any,
 426        update: &mut Option<proto::update_view::Variant>,
 427        cx: &AppContext,
 428    ) -> bool {
 429        if let Some(event) = event.downcast_ref() {
 430            self.read(cx).add_event_to_update_proto(event, update, cx)
 431        } else {
 432            false
 433        }
 434    }
 435
 436    fn apply_update_proto(
 437        &self,
 438        message: proto::update_view::Variant,
 439        cx: &mut MutableAppContext,
 440    ) -> Result<()> {
 441        self.update(cx, |this, cx| this.apply_update_proto(message, cx))
 442    }
 443
 444    fn should_unfollow_on_event(&self, event: &dyn Any, cx: &AppContext) -> bool {
 445        if let Some(event) = event.downcast_ref() {
 446            T::should_unfollow_on_event(event, cx)
 447        } else {
 448            false
 449        }
 450    }
 451}
 452
 453struct DelayedDebouncedEditAction {
 454    task: Option<Task<()>>,
 455    cancel_channel: Option<oneshot::Sender<()>>,
 456}
 457
 458impl DelayedDebouncedEditAction {
 459    fn new() -> DelayedDebouncedEditAction {
 460        DelayedDebouncedEditAction {
 461            task: None,
 462            cancel_channel: None,
 463        }
 464    }
 465
 466    fn fire_new<F, Fut>(
 467        &mut self,
 468        delay: Duration,
 469        workspace: &Workspace,
 470        cx: &mut ViewContext<Workspace>,
 471        f: F,
 472    ) where
 473        F: FnOnce(ModelHandle<Project>, AsyncAppContext) -> Fut + 'static,
 474        Fut: 'static + Future<Output = ()>,
 475    {
 476        if let Some(channel) = self.cancel_channel.take() {
 477            _ = channel.send(());
 478        }
 479
 480        let project = workspace.project().downgrade();
 481
 482        let (sender, mut receiver) = oneshot::channel::<()>();
 483        self.cancel_channel = Some(sender);
 484
 485        let previous_task = self.task.take();
 486        self.task = Some(cx.spawn_weak(|_, cx| async move {
 487            let mut timer = cx.background().timer(delay).fuse();
 488            if let Some(previous_task) = previous_task {
 489                previous_task.await;
 490            }
 491
 492            futures::select_biased! {
 493                _ = receiver => return,
 494                _ = timer => {}
 495            }
 496
 497            if let Some(project) = project.upgrade(&cx) {
 498                (f)(project, cx).await;
 499            }
 500        }));
 501    }
 502}
 503
 504pub trait ItemHandle: 'static + fmt::Debug {
 505    fn subscribe_to_item_events(
 506        &self,
 507        cx: &mut MutableAppContext,
 508        handler: Box<dyn Fn(ItemEvent, &mut MutableAppContext)>,
 509    ) -> gpui::Subscription;
 510    fn tab_description<'a>(&self, detail: usize, cx: &'a AppContext) -> Option<Cow<'a, str>>;
 511    fn tab_content(&self, detail: Option<usize>, style: &theme::Tab, cx: &AppContext)
 512        -> ElementBox;
 513    fn project_path(&self, cx: &AppContext) -> Option<ProjectPath>;
 514    fn project_entry_ids(&self, cx: &AppContext) -> SmallVec<[ProjectEntryId; 3]>;
 515    fn is_singleton(&self, cx: &AppContext) -> bool;
 516    fn boxed_clone(&self) -> Box<dyn ItemHandle>;
 517    fn clone_on_split(&self, cx: &mut MutableAppContext) -> Option<Box<dyn ItemHandle>>;
 518    fn added_to_pane(
 519        &self,
 520        workspace: &mut Workspace,
 521        pane: ViewHandle<Pane>,
 522        cx: &mut ViewContext<Workspace>,
 523    );
 524    fn deactivated(&self, cx: &mut MutableAppContext);
 525    fn workspace_deactivated(&self, cx: &mut MutableAppContext);
 526    fn navigate(&self, data: Box<dyn Any>, cx: &mut MutableAppContext) -> bool;
 527    fn id(&self) -> usize;
 528    fn window_id(&self) -> usize;
 529    fn to_any(&self) -> AnyViewHandle;
 530    fn is_dirty(&self, cx: &AppContext) -> bool;
 531    fn has_conflict(&self, cx: &AppContext) -> bool;
 532    fn can_save(&self, cx: &AppContext) -> bool;
 533    fn save(&self, project: ModelHandle<Project>, cx: &mut MutableAppContext) -> Task<Result<()>>;
 534    fn save_as(
 535        &self,
 536        project: ModelHandle<Project>,
 537        abs_path: PathBuf,
 538        cx: &mut MutableAppContext,
 539    ) -> Task<Result<()>>;
 540    fn reload(&self, project: ModelHandle<Project>, cx: &mut MutableAppContext)
 541        -> Task<Result<()>>;
 542    fn git_diff_recalc(
 543        &self,
 544        project: ModelHandle<Project>,
 545        cx: &mut MutableAppContext,
 546    ) -> Task<Result<()>>;
 547    fn act_as_type(&self, type_id: TypeId, cx: &AppContext) -> Option<AnyViewHandle>;
 548    fn to_followable_item_handle(&self, cx: &AppContext) -> Option<Box<dyn FollowableItemHandle>>;
 549    fn on_release(
 550        &self,
 551        cx: &mut MutableAppContext,
 552        callback: Box<dyn FnOnce(&mut MutableAppContext)>,
 553    ) -> gpui::Subscription;
 554    fn to_searchable_item_handle(&self, cx: &AppContext) -> Option<Box<dyn SearchableItemHandle>>;
 555    fn breadcrumb_location(&self, cx: &AppContext) -> ToolbarItemLocation;
 556    fn breadcrumbs(&self, theme: &Theme, cx: &AppContext) -> Option<Vec<ElementBox>>;
 557}
 558
 559pub trait WeakItemHandle {
 560    fn id(&self) -> usize;
 561    fn window_id(&self) -> usize;
 562    fn upgrade(&self, cx: &AppContext) -> Option<Box<dyn ItemHandle>>;
 563}
 564
 565impl dyn ItemHandle {
 566    pub fn downcast<T: View>(&self) -> Option<ViewHandle<T>> {
 567        self.to_any().downcast()
 568    }
 569
 570    pub fn act_as<T: View>(&self, cx: &AppContext) -> Option<ViewHandle<T>> {
 571        self.act_as_type(TypeId::of::<T>(), cx)
 572            .and_then(|t| t.downcast())
 573    }
 574}
 575
 576impl<T: Item> ItemHandle for ViewHandle<T> {
 577    fn subscribe_to_item_events(
 578        &self,
 579        cx: &mut MutableAppContext,
 580        handler: Box<dyn Fn(ItemEvent, &mut MutableAppContext)>,
 581    ) -> gpui::Subscription {
 582        cx.subscribe(self, move |_, event, cx| {
 583            for item_event in T::to_item_events(event) {
 584                handler(item_event, cx)
 585            }
 586        })
 587    }
 588
 589    fn tab_description<'a>(&self, detail: usize, cx: &'a AppContext) -> Option<Cow<'a, str>> {
 590        self.read(cx).tab_description(detail, cx)
 591    }
 592
 593    fn tab_content(
 594        &self,
 595        detail: Option<usize>,
 596        style: &theme::Tab,
 597        cx: &AppContext,
 598    ) -> ElementBox {
 599        self.read(cx).tab_content(detail, style, cx)
 600    }
 601
 602    fn project_path(&self, cx: &AppContext) -> Option<ProjectPath> {
 603        self.read(cx).project_path(cx)
 604    }
 605
 606    fn project_entry_ids(&self, cx: &AppContext) -> SmallVec<[ProjectEntryId; 3]> {
 607        self.read(cx).project_entry_ids(cx)
 608    }
 609
 610    fn is_singleton(&self, cx: &AppContext) -> bool {
 611        self.read(cx).is_singleton(cx)
 612    }
 613
 614    fn boxed_clone(&self) -> Box<dyn ItemHandle> {
 615        Box::new(self.clone())
 616    }
 617
 618    fn clone_on_split(&self, cx: &mut MutableAppContext) -> Option<Box<dyn ItemHandle>> {
 619        self.update(cx, |item, cx| {
 620            cx.add_option_view(|cx| item.clone_on_split(cx))
 621        })
 622        .map(|handle| Box::new(handle) as Box<dyn ItemHandle>)
 623    }
 624
 625    fn added_to_pane(
 626        &self,
 627        workspace: &mut Workspace,
 628        pane: ViewHandle<Pane>,
 629        cx: &mut ViewContext<Workspace>,
 630    ) {
 631        let history = pane.read(cx).nav_history_for_item(self);
 632        self.update(cx, |this, cx| this.set_nav_history(history, cx));
 633
 634        if let Some(followed_item) = self.to_followable_item_handle(cx) {
 635            if let Some(message) = followed_item.to_state_proto(cx) {
 636                workspace.update_followers(
 637                    proto::update_followers::Variant::CreateView(proto::View {
 638                        id: followed_item.id() as u64,
 639                        variant: Some(message),
 640                        leader_id: workspace.leader_for_pane(&pane).map(|id| id.0),
 641                    }),
 642                    cx,
 643                );
 644            }
 645        }
 646
 647        if workspace
 648            .panes_by_item
 649            .insert(self.id(), pane.downgrade())
 650            .is_none()
 651        {
 652            let mut pending_autosave = DelayedDebouncedEditAction::new();
 653            let mut pending_git_update = DelayedDebouncedEditAction::new();
 654            let pending_update = Rc::new(RefCell::new(None));
 655            let pending_update_scheduled = Rc::new(AtomicBool::new(false));
 656
 657            let mut event_subscription =
 658                Some(cx.subscribe(self, move |workspace, item, event, cx| {
 659                    let pane = if let Some(pane) = workspace
 660                        .panes_by_item
 661                        .get(&item.id())
 662                        .and_then(|pane| pane.upgrade(cx))
 663                    {
 664                        pane
 665                    } else {
 666                        log::error!("unexpected item event after pane was dropped");
 667                        return;
 668                    };
 669
 670                    if let Some(item) = item.to_followable_item_handle(cx) {
 671                        let leader_id = workspace.leader_for_pane(&pane);
 672
 673                        if leader_id.is_some() && item.should_unfollow_on_event(event, cx) {
 674                            workspace.unfollow(&pane, cx);
 675                        }
 676
 677                        if item.add_event_to_update_proto(
 678                            event,
 679                            &mut *pending_update.borrow_mut(),
 680                            cx,
 681                        ) && !pending_update_scheduled.load(SeqCst)
 682                        {
 683                            pending_update_scheduled.store(true, SeqCst);
 684                            cx.after_window_update({
 685                                let pending_update = pending_update.clone();
 686                                let pending_update_scheduled = pending_update_scheduled.clone();
 687                                move |this, cx| {
 688                                    pending_update_scheduled.store(false, SeqCst);
 689                                    this.update_followers(
 690                                        proto::update_followers::Variant::UpdateView(
 691                                            proto::UpdateView {
 692                                                id: item.id() as u64,
 693                                                variant: pending_update.borrow_mut().take(),
 694                                                leader_id: leader_id.map(|id| id.0),
 695                                            },
 696                                        ),
 697                                        cx,
 698                                    );
 699                                }
 700                            });
 701                        }
 702                    }
 703
 704                    for item_event in T::to_item_events(event).into_iter() {
 705                        match item_event {
 706                            ItemEvent::CloseItem => {
 707                                Pane::close_item(workspace, pane, item.id(), cx)
 708                                    .detach_and_log_err(cx);
 709                                return;
 710                            }
 711
 712                            ItemEvent::UpdateTab => {
 713                                pane.update(cx, |_, cx| {
 714                                    cx.emit(pane::Event::ChangeItemTitle);
 715                                    cx.notify();
 716                                });
 717                            }
 718
 719                            ItemEvent::Edit => {
 720                                if let Autosave::AfterDelay { milliseconds } =
 721                                    cx.global::<Settings>().autosave
 722                                {
 723                                    let delay = Duration::from_millis(milliseconds);
 724                                    let item = item.clone();
 725                                    pending_autosave.fire_new(
 726                                        delay,
 727                                        workspace,
 728                                        cx,
 729                                        |project, mut cx| async move {
 730                                            cx.update(|cx| Pane::autosave_item(&item, project, cx))
 731                                                .await
 732                                                .log_err();
 733                                        },
 734                                    );
 735                                }
 736
 737                                let debounce_delay = cx
 738                                    .global::<Settings>()
 739                                    .editor_overrides
 740                                    .git_gutter
 741                                    .unwrap_or_default()
 742                                    .debounce_delay_millis;
 743                                let item = item.clone();
 744
 745                                if let Some(delay) = debounce_delay {
 746                                    const MIN_GIT_DELAY: u64 = 50;
 747
 748                                    let delay = delay.max(MIN_GIT_DELAY);
 749                                    let duration = Duration::from_millis(delay);
 750
 751                                    pending_git_update.fire_new(
 752                                        duration,
 753                                        workspace,
 754                                        cx,
 755                                        |project, mut cx| async move {
 756                                            cx.update(|cx| item.git_diff_recalc(project, cx))
 757                                                .await
 758                                                .log_err();
 759                                        },
 760                                    );
 761                                } else {
 762                                    let project = workspace.project().downgrade();
 763                                    cx.spawn_weak(|_, mut cx| async move {
 764                                        if let Some(project) = project.upgrade(&cx) {
 765                                            cx.update(|cx| item.git_diff_recalc(project, cx))
 766                                                .await
 767                                                .log_err();
 768                                        }
 769                                    })
 770                                    .detach();
 771                                }
 772                            }
 773
 774                            _ => {}
 775                        }
 776                    }
 777                }));
 778
 779            cx.observe_focus(self, move |workspace, item, focused, cx| {
 780                if !focused && cx.global::<Settings>().autosave == Autosave::OnFocusChange {
 781                    Pane::autosave_item(&item, workspace.project.clone(), cx)
 782                        .detach_and_log_err(cx);
 783                }
 784            })
 785            .detach();
 786
 787            let item_id = self.id();
 788            cx.observe_release(self, move |workspace, _, _| {
 789                workspace.panes_by_item.remove(&item_id);
 790                event_subscription.take();
 791            })
 792            .detach();
 793        }
 794    }
 795
 796    fn deactivated(&self, cx: &mut MutableAppContext) {
 797        self.update(cx, |this, cx| this.deactivated(cx));
 798    }
 799
 800    fn workspace_deactivated(&self, cx: &mut MutableAppContext) {
 801        self.update(cx, |this, cx| this.workspace_deactivated(cx));
 802    }
 803
 804    fn navigate(&self, data: Box<dyn Any>, cx: &mut MutableAppContext) -> bool {
 805        self.update(cx, |this, cx| this.navigate(data, cx))
 806    }
 807
 808    fn id(&self) -> usize {
 809        self.id()
 810    }
 811
 812    fn window_id(&self) -> usize {
 813        self.window_id()
 814    }
 815
 816    fn to_any(&self) -> AnyViewHandle {
 817        self.into()
 818    }
 819
 820    fn is_dirty(&self, cx: &AppContext) -> bool {
 821        self.read(cx).is_dirty(cx)
 822    }
 823
 824    fn has_conflict(&self, cx: &AppContext) -> bool {
 825        self.read(cx).has_conflict(cx)
 826    }
 827
 828    fn can_save(&self, cx: &AppContext) -> bool {
 829        self.read(cx).can_save(cx)
 830    }
 831
 832    fn save(&self, project: ModelHandle<Project>, cx: &mut MutableAppContext) -> Task<Result<()>> {
 833        self.update(cx, |item, cx| item.save(project, cx))
 834    }
 835
 836    fn save_as(
 837        &self,
 838        project: ModelHandle<Project>,
 839        abs_path: PathBuf,
 840        cx: &mut MutableAppContext,
 841    ) -> Task<anyhow::Result<()>> {
 842        self.update(cx, |item, cx| item.save_as(project, abs_path, cx))
 843    }
 844
 845    fn reload(
 846        &self,
 847        project: ModelHandle<Project>,
 848        cx: &mut MutableAppContext,
 849    ) -> Task<Result<()>> {
 850        self.update(cx, |item, cx| item.reload(project, cx))
 851    }
 852
 853    fn git_diff_recalc(
 854        &self,
 855        project: ModelHandle<Project>,
 856        cx: &mut MutableAppContext,
 857    ) -> Task<Result<()>> {
 858        self.update(cx, |item, cx| item.git_diff_recalc(project, cx))
 859    }
 860
 861    fn act_as_type(&self, type_id: TypeId, cx: &AppContext) -> Option<AnyViewHandle> {
 862        self.read(cx).act_as_type(type_id, self, cx)
 863    }
 864
 865    fn to_followable_item_handle(&self, cx: &AppContext) -> Option<Box<dyn FollowableItemHandle>> {
 866        if cx.has_global::<FollowableItemBuilders>() {
 867            let builders = cx.global::<FollowableItemBuilders>();
 868            let item = self.to_any();
 869            Some(builders.get(&item.view_type())?.1(item))
 870        } else {
 871            None
 872        }
 873    }
 874
 875    fn on_release(
 876        &self,
 877        cx: &mut MutableAppContext,
 878        callback: Box<dyn FnOnce(&mut MutableAppContext)>,
 879    ) -> gpui::Subscription {
 880        cx.observe_release(self, move |_, cx| callback(cx))
 881    }
 882
 883    fn to_searchable_item_handle(&self, cx: &AppContext) -> Option<Box<dyn SearchableItemHandle>> {
 884        self.read(cx).as_searchable(self)
 885    }
 886
 887    fn breadcrumb_location(&self, cx: &AppContext) -> ToolbarItemLocation {
 888        self.read(cx).breadcrumb_location()
 889    }
 890
 891    fn breadcrumbs(&self, theme: &Theme, cx: &AppContext) -> Option<Vec<ElementBox>> {
 892        self.read(cx).breadcrumbs(theme, cx)
 893    }
 894}
 895
 896impl From<Box<dyn ItemHandle>> for AnyViewHandle {
 897    fn from(val: Box<dyn ItemHandle>) -> Self {
 898        val.to_any()
 899    }
 900}
 901
 902impl From<&Box<dyn ItemHandle>> for AnyViewHandle {
 903    fn from(val: &Box<dyn ItemHandle>) -> Self {
 904        val.to_any()
 905    }
 906}
 907
 908impl Clone for Box<dyn ItemHandle> {
 909    fn clone(&self) -> Box<dyn ItemHandle> {
 910        self.boxed_clone()
 911    }
 912}
 913
 914impl<T: Item> WeakItemHandle for WeakViewHandle<T> {
 915    fn id(&self) -> usize {
 916        self.id()
 917    }
 918
 919    fn window_id(&self) -> usize {
 920        self.window_id()
 921    }
 922
 923    fn upgrade(&self, cx: &AppContext) -> Option<Box<dyn ItemHandle>> {
 924        self.upgrade(cx).map(|v| Box::new(v) as Box<dyn ItemHandle>)
 925    }
 926}
 927
 928pub trait Notification: View {
 929    fn should_dismiss_notification_on_event(&self, event: &<Self as Entity>::Event) -> bool;
 930}
 931
 932pub trait NotificationHandle {
 933    fn id(&self) -> usize;
 934    fn to_any(&self) -> AnyViewHandle;
 935}
 936
 937impl<T: Notification> NotificationHandle for ViewHandle<T> {
 938    fn id(&self) -> usize {
 939        self.id()
 940    }
 941
 942    fn to_any(&self) -> AnyViewHandle {
 943        self.into()
 944    }
 945}
 946
 947impl From<&dyn NotificationHandle> for AnyViewHandle {
 948    fn from(val: &dyn NotificationHandle) -> Self {
 949        val.to_any()
 950    }
 951}
 952
 953impl AppState {
 954    #[cfg(any(test, feature = "test-support"))]
 955    pub fn test(cx: &mut MutableAppContext) -> Arc<Self> {
 956        let settings = Settings::test(cx);
 957        cx.set_global(settings);
 958
 959        let fs = project::FakeFs::new(cx.background().clone());
 960        let languages = Arc::new(LanguageRegistry::test());
 961        let http_client = client::test::FakeHttpClient::with_404_response();
 962        let client = Client::new(http_client.clone(), cx);
 963        let project_store = cx.add_model(|_| ProjectStore::new(project::Db::open_fake()));
 964        let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx));
 965        let themes = ThemeRegistry::new((), cx.font_cache().clone());
 966        Arc::new(Self {
 967            client,
 968            themes,
 969            fs,
 970            languages,
 971            user_store,
 972            project_store,
 973            initialize_workspace: |_, _, _| {},
 974            build_window_options: Default::default,
 975            default_item_factory: |_, _| unimplemented!(),
 976        })
 977    }
 978}
 979
 980pub enum Event {
 981    DockAnchorChanged,
 982    PaneAdded(ViewHandle<Pane>),
 983    ContactRequestedJoin(u64),
 984}
 985
 986pub struct Workspace {
 987    weak_self: WeakViewHandle<Self>,
 988    client: Arc<Client>,
 989    user_store: ModelHandle<client::UserStore>,
 990    remote_entity_subscription: Option<Subscription>,
 991    fs: Arc<dyn Fs>,
 992    modal: Option<AnyViewHandle>,
 993    center: PaneGroup,
 994    left_sidebar: ViewHandle<Sidebar>,
 995    right_sidebar: ViewHandle<Sidebar>,
 996    panes: Vec<ViewHandle<Pane>>,
 997    panes_by_item: HashMap<usize, WeakViewHandle<Pane>>,
 998    active_pane: ViewHandle<Pane>,
 999    last_active_center_pane: Option<ViewHandle<Pane>>,
1000    status_bar: ViewHandle<StatusBar>,
1001    dock: Dock,
1002    notifications: Vec<(TypeId, usize, Box<dyn NotificationHandle>)>,
1003    project: ModelHandle<Project>,
1004    leader_state: LeaderState,
1005    follower_states_by_leader: FollowerStatesByLeader,
1006    last_leaders_by_pane: HashMap<WeakViewHandle<Pane>, PeerId>,
1007    window_edited: bool,
1008    _observe_current_user: Task<()>,
1009}
1010
1011#[derive(Default)]
1012struct LeaderState {
1013    followers: HashSet<PeerId>,
1014}
1015
1016type FollowerStatesByLeader = HashMap<PeerId, HashMap<ViewHandle<Pane>, FollowerState>>;
1017
1018#[derive(Default)]
1019struct FollowerState {
1020    active_view_id: Option<u64>,
1021    items_by_leader_view_id: HashMap<u64, FollowerItem>,
1022}
1023
1024#[derive(Debug)]
1025enum FollowerItem {
1026    Loading(Vec<proto::update_view::Variant>),
1027    Loaded(Box<dyn FollowableItemHandle>),
1028}
1029
1030impl Workspace {
1031    pub fn new(
1032        project: ModelHandle<Project>,
1033        dock_default_factory: DefaultItemFactory,
1034        cx: &mut ViewContext<Self>,
1035    ) -> Self {
1036        cx.observe_fullscreen(|_, _, cx| cx.notify()).detach();
1037
1038        cx.observe_window_activation(Self::on_window_activation_changed)
1039            .detach();
1040        cx.observe(&project, |_, _, cx| cx.notify()).detach();
1041        cx.subscribe(&project, move |this, _, event, cx| {
1042            match event {
1043                project::Event::RemoteIdChanged(remote_id) => {
1044                    this.project_remote_id_changed(*remote_id, cx);
1045                }
1046                project::Event::CollaboratorLeft(peer_id) => {
1047                    this.collaborator_left(*peer_id, cx);
1048                }
1049                project::Event::WorktreeRemoved(_) | project::Event::WorktreeAdded => {
1050                    this.update_window_title(cx);
1051                }
1052                project::Event::DisconnectedFromHost => {
1053                    this.update_window_edited(cx);
1054                    cx.blur();
1055                }
1056                _ => {}
1057            }
1058            cx.notify()
1059        })
1060        .detach();
1061
1062        let center_pane = cx.add_view(|cx| Pane::new(None, cx));
1063        let pane_id = center_pane.id();
1064        cx.subscribe(&center_pane, move |this, _, event, cx| {
1065            this.handle_pane_event(pane_id, event, cx)
1066        })
1067        .detach();
1068        cx.focus(&center_pane);
1069        cx.emit(Event::PaneAdded(center_pane.clone()));
1070
1071        let fs = project.read(cx).fs().clone();
1072        let user_store = project.read(cx).user_store();
1073        let client = project.read(cx).client();
1074        let mut current_user = user_store.read(cx).watch_current_user();
1075        let mut connection_status = client.status();
1076        let _observe_current_user = cx.spawn_weak(|this, mut cx| async move {
1077            current_user.recv().await;
1078            connection_status.recv().await;
1079            let mut stream =
1080                Stream::map(current_user, drop).merge(Stream::map(connection_status, drop));
1081
1082            while stream.recv().await.is_some() {
1083                cx.update(|cx| {
1084                    if let Some(this) = this.upgrade(cx) {
1085                        this.update(cx, |_, cx| cx.notify());
1086                    }
1087                })
1088            }
1089        });
1090
1091        let handle = cx.handle();
1092        let weak_handle = cx.weak_handle();
1093
1094        cx.emit_global(WorkspaceCreated(weak_handle.clone()));
1095
1096        let dock = Dock::new(cx, dock_default_factory);
1097        let dock_pane = dock.pane().clone();
1098
1099        let left_sidebar = cx.add_view(|_| Sidebar::new(SidebarSide::Left));
1100        let right_sidebar = cx.add_view(|_| Sidebar::new(SidebarSide::Right));
1101        let left_sidebar_buttons = cx.add_view(|cx| SidebarButtons::new(left_sidebar.clone(), cx));
1102        let toggle_dock = cx.add_view(|cx| ToggleDockButton::new(handle, cx));
1103        let right_sidebar_buttons =
1104            cx.add_view(|cx| SidebarButtons::new(right_sidebar.clone(), cx));
1105        let status_bar = cx.add_view(|cx| {
1106            let mut status_bar = StatusBar::new(&center_pane.clone(), cx);
1107            status_bar.add_left_item(left_sidebar_buttons, cx);
1108            status_bar.add_right_item(right_sidebar_buttons, cx);
1109            status_bar.add_right_item(toggle_dock, cx);
1110            status_bar
1111        });
1112
1113        cx.update_default_global::<DragAndDrop<Workspace>, _, _>(|drag_and_drop, _| {
1114            drag_and_drop.register_container(weak_handle.clone());
1115        });
1116
1117        let mut this = Workspace {
1118            modal: None,
1119            weak_self: weak_handle,
1120            center: PaneGroup::new(center_pane.clone()),
1121            dock,
1122            // When removing an item, the last element remaining in this array
1123            // is used to find where focus should fallback to. As such, the order
1124            // of these two variables is important.
1125            panes: vec![dock_pane, center_pane.clone()],
1126            panes_by_item: Default::default(),
1127            active_pane: center_pane.clone(),
1128            last_active_center_pane: Some(center_pane.clone()),
1129            status_bar,
1130            notifications: Default::default(),
1131            client,
1132            remote_entity_subscription: None,
1133            user_store,
1134            fs,
1135            left_sidebar,
1136            right_sidebar,
1137            project,
1138            leader_state: Default::default(),
1139            follower_states_by_leader: Default::default(),
1140            last_leaders_by_pane: Default::default(),
1141            window_edited: false,
1142            _observe_current_user,
1143        };
1144        this.project_remote_id_changed(this.project.read(cx).remote_id(), cx);
1145        cx.defer(|this, cx| this.update_window_title(cx));
1146
1147        this
1148    }
1149
1150    pub fn weak_handle(&self) -> WeakViewHandle<Self> {
1151        self.weak_self.clone()
1152    }
1153
1154    pub fn left_sidebar(&self) -> &ViewHandle<Sidebar> {
1155        &self.left_sidebar
1156    }
1157
1158    pub fn right_sidebar(&self) -> &ViewHandle<Sidebar> {
1159        &self.right_sidebar
1160    }
1161
1162    pub fn status_bar(&self) -> &ViewHandle<StatusBar> {
1163        &self.status_bar
1164    }
1165
1166    pub fn user_store(&self) -> &ModelHandle<UserStore> {
1167        &self.user_store
1168    }
1169
1170    pub fn project(&self) -> &ModelHandle<Project> {
1171        &self.project
1172    }
1173
1174    /// Call the given callback with a workspace whose project is local.
1175    ///
1176    /// If the given workspace has a local project, then it will be passed
1177    /// to the callback. Otherwise, a new empty window will be created.
1178    pub fn with_local_workspace<T, F>(
1179        &mut self,
1180        cx: &mut ViewContext<Self>,
1181        app_state: Arc<AppState>,
1182        callback: F,
1183    ) -> T
1184    where
1185        T: 'static,
1186        F: FnOnce(&mut Workspace, &mut ViewContext<Workspace>) -> T,
1187    {
1188        if self.project.read(cx).is_local() {
1189            callback(self, cx)
1190        } else {
1191            let (_, workspace) = cx.add_window((app_state.build_window_options)(), |cx| {
1192                let mut workspace = Workspace::new(
1193                    Project::local(
1194                        false,
1195                        app_state.client.clone(),
1196                        app_state.user_store.clone(),
1197                        app_state.project_store.clone(),
1198                        app_state.languages.clone(),
1199                        app_state.fs.clone(),
1200                        cx,
1201                    ),
1202                    app_state.default_item_factory,
1203                    cx,
1204                );
1205                (app_state.initialize_workspace)(&mut workspace, &app_state, cx);
1206                workspace
1207            });
1208            workspace.update(cx, callback)
1209        }
1210    }
1211
1212    pub fn worktrees<'a>(
1213        &self,
1214        cx: &'a AppContext,
1215    ) -> impl 'a + Iterator<Item = ModelHandle<Worktree>> {
1216        self.project.read(cx).worktrees(cx)
1217    }
1218
1219    pub fn visible_worktrees<'a>(
1220        &self,
1221        cx: &'a AppContext,
1222    ) -> impl 'a + Iterator<Item = ModelHandle<Worktree>> {
1223        self.project.read(cx).visible_worktrees(cx)
1224    }
1225
1226    pub fn worktree_scans_complete(&self, cx: &AppContext) -> impl Future<Output = ()> + 'static {
1227        let futures = self
1228            .worktrees(cx)
1229            .filter_map(|worktree| worktree.read(cx).as_local())
1230            .map(|worktree| worktree.scan_complete())
1231            .collect::<Vec<_>>();
1232        async move {
1233            for future in futures {
1234                future.await;
1235            }
1236        }
1237    }
1238
1239    pub fn close(
1240        &mut self,
1241        _: &CloseWindow,
1242        cx: &mut ViewContext<Self>,
1243    ) -> Option<Task<Result<()>>> {
1244        let prepare = self.prepare_to_close(cx);
1245        Some(cx.spawn(|this, mut cx| async move {
1246            if prepare.await? {
1247                this.update(&mut cx, |_, cx| {
1248                    let window_id = cx.window_id();
1249                    cx.remove_window(window_id);
1250                });
1251            }
1252            Ok(())
1253        }))
1254    }
1255
1256    pub fn prepare_to_close(&mut self, cx: &mut ViewContext<Self>) -> Task<Result<bool>> {
1257        self.save_all_internal(true, cx)
1258    }
1259
1260    fn save_all(&mut self, _: &SaveAll, cx: &mut ViewContext<Self>) -> Option<Task<Result<()>>> {
1261        let save_all = self.save_all_internal(false, cx);
1262        Some(cx.foreground().spawn(async move {
1263            save_all.await?;
1264            Ok(())
1265        }))
1266    }
1267
1268    fn save_all_internal(
1269        &mut self,
1270        should_prompt_to_save: bool,
1271        cx: &mut ViewContext<Self>,
1272    ) -> Task<Result<bool>> {
1273        if self.project.read(cx).is_read_only() {
1274            return Task::ready(Ok(true));
1275        }
1276
1277        let dirty_items = self
1278            .panes
1279            .iter()
1280            .flat_map(|pane| {
1281                pane.read(cx).items().filter_map(|item| {
1282                    if item.is_dirty(cx) {
1283                        Some((pane.clone(), item.boxed_clone()))
1284                    } else {
1285                        None
1286                    }
1287                })
1288            })
1289            .collect::<Vec<_>>();
1290
1291        let project = self.project.clone();
1292        cx.spawn_weak(|_, mut cx| async move {
1293            for (pane, item) in dirty_items {
1294                let (singleton, project_entry_ids) =
1295                    cx.read(|cx| (item.is_singleton(cx), item.project_entry_ids(cx)));
1296                if singleton || !project_entry_ids.is_empty() {
1297                    if let Some(ix) =
1298                        pane.read_with(&cx, |pane, _| pane.index_for_item(item.as_ref()))
1299                    {
1300                        if !Pane::save_item(
1301                            project.clone(),
1302                            &pane,
1303                            ix,
1304                            &*item,
1305                            should_prompt_to_save,
1306                            &mut cx,
1307                        )
1308                        .await?
1309                        {
1310                            return Ok(false);
1311                        }
1312                    }
1313                }
1314            }
1315            Ok(true)
1316        })
1317    }
1318
1319    #[allow(clippy::type_complexity)]
1320    pub fn open_paths(
1321        &mut self,
1322        mut abs_paths: Vec<PathBuf>,
1323        visible: bool,
1324        cx: &mut ViewContext<Self>,
1325    ) -> Task<Vec<Option<Result<Box<dyn ItemHandle>, Arc<anyhow::Error>>>>> {
1326        let fs = self.fs.clone();
1327
1328        // Sort the paths to ensure we add worktrees for parents before their children.
1329        abs_paths.sort_unstable();
1330        cx.spawn(|this, mut cx| async move {
1331            let mut project_paths = Vec::new();
1332            for path in &abs_paths {
1333                project_paths.push(
1334                    this.update(&mut cx, |this, cx| {
1335                        this.project_path_for_path(path, visible, cx)
1336                    })
1337                    .await
1338                    .log_err(),
1339                );
1340            }
1341
1342            let tasks = abs_paths
1343                .iter()
1344                .cloned()
1345                .zip(project_paths.into_iter())
1346                .map(|(abs_path, project_path)| {
1347                    let this = this.clone();
1348                    cx.spawn(|mut cx| {
1349                        let fs = fs.clone();
1350                        async move {
1351                            let (_worktree, project_path) = project_path?;
1352                            if fs.is_file(&abs_path).await {
1353                                Some(
1354                                    this.update(&mut cx, |this, cx| {
1355                                        this.open_path(project_path, true, cx)
1356                                    })
1357                                    .await,
1358                                )
1359                            } else {
1360                                None
1361                            }
1362                        }
1363                    })
1364                })
1365                .collect::<Vec<_>>();
1366
1367            futures::future::join_all(tasks).await
1368        })
1369    }
1370
1371    fn add_folder_to_project(&mut self, _: &AddFolderToProject, cx: &mut ViewContext<Self>) {
1372        let mut paths = cx.prompt_for_paths(PathPromptOptions {
1373            files: false,
1374            directories: true,
1375            multiple: true,
1376        });
1377        cx.spawn(|this, mut cx| async move {
1378            if let Some(paths) = paths.recv().await.flatten() {
1379                let results = this
1380                    .update(&mut cx, |this, cx| this.open_paths(paths, true, cx))
1381                    .await;
1382                for result in results.into_iter().flatten() {
1383                    result.log_err();
1384                }
1385            }
1386        })
1387        .detach();
1388    }
1389
1390    fn remove_folder_from_project(
1391        &mut self,
1392        RemoveWorktreeFromProject(worktree_id): &RemoveWorktreeFromProject,
1393        cx: &mut ViewContext<Self>,
1394    ) {
1395        self.project
1396            .update(cx, |project, cx| project.remove_worktree(*worktree_id, cx));
1397    }
1398
1399    fn toggle_project_online(&mut self, action: &ToggleProjectOnline, cx: &mut ViewContext<Self>) {
1400        let project = action
1401            .project
1402            .clone()
1403            .unwrap_or_else(|| self.project.clone());
1404        project.update(cx, |project, cx| {
1405            let public = !project.is_online();
1406            project.set_online(public, cx);
1407        });
1408    }
1409
1410    fn project_path_for_path(
1411        &self,
1412        abs_path: &Path,
1413        visible: bool,
1414        cx: &mut ViewContext<Self>,
1415    ) -> Task<Result<(ModelHandle<Worktree>, ProjectPath)>> {
1416        let entry = self.project().update(cx, |project, cx| {
1417            project.find_or_create_local_worktree(abs_path, visible, cx)
1418        });
1419        cx.spawn(|_, cx| async move {
1420            let (worktree, path) = entry.await?;
1421            let worktree_id = worktree.read_with(&cx, |t, _| t.id());
1422            Ok((
1423                worktree,
1424                ProjectPath {
1425                    worktree_id,
1426                    path: path.into(),
1427                },
1428            ))
1429        })
1430    }
1431
1432    /// Returns the modal that was toggled closed if it was open.
1433    pub fn toggle_modal<V, F>(
1434        &mut self,
1435        cx: &mut ViewContext<Self>,
1436        add_view: F,
1437    ) -> Option<ViewHandle<V>>
1438    where
1439        V: 'static + View,
1440        F: FnOnce(&mut Self, &mut ViewContext<Self>) -> ViewHandle<V>,
1441    {
1442        cx.notify();
1443        // Whatever modal was visible is getting clobbered. If its the same type as V, then return
1444        // it. Otherwise, create a new modal and set it as active.
1445        let already_open_modal = self.modal.take().and_then(|modal| modal.downcast::<V>());
1446        if let Some(already_open_modal) = already_open_modal {
1447            cx.focus_self();
1448            Some(already_open_modal)
1449        } else {
1450            let modal = add_view(self, cx);
1451            cx.focus(&modal);
1452            self.modal = Some(modal.into());
1453            None
1454        }
1455    }
1456
1457    pub fn modal<V: 'static + View>(&self) -> Option<ViewHandle<V>> {
1458        self.modal
1459            .as_ref()
1460            .and_then(|modal| modal.clone().downcast::<V>())
1461    }
1462
1463    pub fn dismiss_modal(&mut self, cx: &mut ViewContext<Self>) {
1464        if self.modal.take().is_some() {
1465            cx.focus(&self.active_pane);
1466            cx.notify();
1467        }
1468    }
1469
1470    pub fn show_notification<V: Notification>(
1471        &mut self,
1472        id: usize,
1473        cx: &mut ViewContext<Self>,
1474        build_notification: impl FnOnce(&mut ViewContext<Self>) -> ViewHandle<V>,
1475    ) {
1476        let type_id = TypeId::of::<V>();
1477        if self
1478            .notifications
1479            .iter()
1480            .all(|(existing_type_id, existing_id, _)| {
1481                (*existing_type_id, *existing_id) != (type_id, id)
1482            })
1483        {
1484            let notification = build_notification(cx);
1485            cx.subscribe(&notification, move |this, handle, event, cx| {
1486                if handle.read(cx).should_dismiss_notification_on_event(event) {
1487                    this.dismiss_notification(type_id, id, cx);
1488                }
1489            })
1490            .detach();
1491            self.notifications
1492                .push((type_id, id, Box::new(notification)));
1493            cx.notify();
1494        }
1495    }
1496
1497    fn dismiss_notification(&mut self, type_id: TypeId, id: usize, cx: &mut ViewContext<Self>) {
1498        self.notifications
1499            .retain(|(existing_type_id, existing_id, _)| {
1500                if (*existing_type_id, *existing_id) == (type_id, id) {
1501                    cx.notify();
1502                    false
1503                } else {
1504                    true
1505                }
1506            });
1507    }
1508
1509    pub fn items<'a>(
1510        &'a self,
1511        cx: &'a AppContext,
1512    ) -> impl 'a + Iterator<Item = &Box<dyn ItemHandle>> {
1513        self.panes.iter().flat_map(|pane| pane.read(cx).items())
1514    }
1515
1516    pub fn item_of_type<T: Item>(&self, cx: &AppContext) -> Option<ViewHandle<T>> {
1517        self.items_of_type(cx).max_by_key(|item| item.id())
1518    }
1519
1520    pub fn items_of_type<'a, T: Item>(
1521        &'a self,
1522        cx: &'a AppContext,
1523    ) -> impl 'a + Iterator<Item = ViewHandle<T>> {
1524        self.panes
1525            .iter()
1526            .flat_map(|pane| pane.read(cx).items_of_type())
1527    }
1528
1529    pub fn active_item(&self, cx: &AppContext) -> Option<Box<dyn ItemHandle>> {
1530        self.active_pane().read(cx).active_item()
1531    }
1532
1533    fn active_project_path(&self, cx: &ViewContext<Self>) -> Option<ProjectPath> {
1534        self.active_item(cx).and_then(|item| item.project_path(cx))
1535    }
1536
1537    pub fn save_active_item(
1538        &mut self,
1539        force_name_change: bool,
1540        cx: &mut ViewContext<Self>,
1541    ) -> Task<Result<()>> {
1542        let project = self.project.clone();
1543        if let Some(item) = self.active_item(cx) {
1544            if !force_name_change && item.can_save(cx) {
1545                if item.has_conflict(cx.as_ref()) {
1546                    const CONFLICT_MESSAGE: &str = "This file has changed on disk since you started editing it. Do you want to overwrite it?";
1547
1548                    let mut answer = cx.prompt(
1549                        PromptLevel::Warning,
1550                        CONFLICT_MESSAGE,
1551                        &["Overwrite", "Cancel"],
1552                    );
1553                    cx.spawn(|_, mut cx| async move {
1554                        let answer = answer.recv().await;
1555                        if answer == Some(0) {
1556                            cx.update(|cx| item.save(project, cx)).await?;
1557                        }
1558                        Ok(())
1559                    })
1560                } else {
1561                    item.save(project, cx)
1562                }
1563            } else if item.is_singleton(cx) {
1564                let worktree = self.worktrees(cx).next();
1565                let start_abs_path = worktree
1566                    .and_then(|w| w.read(cx).as_local())
1567                    .map_or(Path::new(""), |w| w.abs_path())
1568                    .to_path_buf();
1569                let mut abs_path = cx.prompt_for_new_path(&start_abs_path);
1570                cx.spawn(|_, mut cx| async move {
1571                    if let Some(abs_path) = abs_path.recv().await.flatten() {
1572                        cx.update(|cx| item.save_as(project, abs_path, cx)).await?;
1573                    }
1574                    Ok(())
1575                })
1576            } else {
1577                Task::ready(Ok(()))
1578            }
1579        } else {
1580            Task::ready(Ok(()))
1581        }
1582    }
1583
1584    pub fn toggle_sidebar(&mut self, sidebar_side: SidebarSide, cx: &mut ViewContext<Self>) {
1585        let sidebar = match sidebar_side {
1586            SidebarSide::Left => &mut self.left_sidebar,
1587            SidebarSide::Right => &mut self.right_sidebar,
1588        };
1589        let open = sidebar.update(cx, |sidebar, cx| {
1590            let open = !sidebar.is_open();
1591            sidebar.set_open(open, cx);
1592            open
1593        });
1594
1595        if open {
1596            Dock::hide_on_sidebar_shown(self, sidebar_side, cx);
1597        }
1598
1599        cx.focus_self();
1600        cx.notify();
1601    }
1602
1603    pub fn toggle_sidebar_item(&mut self, action: &ToggleSidebarItem, cx: &mut ViewContext<Self>) {
1604        let sidebar = match action.sidebar_side {
1605            SidebarSide::Left => &mut self.left_sidebar,
1606            SidebarSide::Right => &mut self.right_sidebar,
1607        };
1608        let active_item = sidebar.update(cx, move |sidebar, cx| {
1609            if sidebar.is_open() && sidebar.active_item_ix() == action.item_index {
1610                sidebar.set_open(false, cx);
1611                None
1612            } else {
1613                sidebar.set_open(true, cx);
1614                sidebar.activate_item(action.item_index, cx);
1615                sidebar.active_item().cloned()
1616            }
1617        });
1618
1619        if let Some(active_item) = active_item {
1620            Dock::hide_on_sidebar_shown(self, action.sidebar_side, cx);
1621
1622            if active_item.is_focused(cx) {
1623                cx.focus_self();
1624            } else {
1625                cx.focus(active_item.to_any());
1626            }
1627        } else {
1628            cx.focus_self();
1629        }
1630        cx.notify();
1631    }
1632
1633    pub fn toggle_sidebar_item_focus(
1634        &mut self,
1635        sidebar_side: SidebarSide,
1636        item_index: usize,
1637        cx: &mut ViewContext<Self>,
1638    ) {
1639        let sidebar = match sidebar_side {
1640            SidebarSide::Left => &mut self.left_sidebar,
1641            SidebarSide::Right => &mut self.right_sidebar,
1642        };
1643        let active_item = sidebar.update(cx, |sidebar, cx| {
1644            sidebar.set_open(true, cx);
1645            sidebar.activate_item(item_index, cx);
1646            sidebar.active_item().cloned()
1647        });
1648        if let Some(active_item) = active_item {
1649            Dock::hide_on_sidebar_shown(self, sidebar_side, cx);
1650
1651            if active_item.is_focused(cx) {
1652                cx.focus_self();
1653            } else {
1654                cx.focus(active_item.to_any());
1655            }
1656        }
1657        cx.notify();
1658    }
1659
1660    pub fn focus_center(&mut self, _: &menu::Cancel, cx: &mut ViewContext<Self>) {
1661        cx.focus_self();
1662        cx.notify();
1663    }
1664
1665    fn add_pane(&mut self, cx: &mut ViewContext<Self>) -> ViewHandle<Pane> {
1666        let pane = cx.add_view(|cx| Pane::new(None, cx));
1667        let pane_id = pane.id();
1668        cx.subscribe(&pane, move |this, _, event, cx| {
1669            this.handle_pane_event(pane_id, event, cx)
1670        })
1671        .detach();
1672        self.panes.push(pane.clone());
1673        cx.focus(pane.clone());
1674        cx.emit(Event::PaneAdded(pane.clone()));
1675        pane
1676    }
1677
1678    pub fn add_item(&mut self, item: Box<dyn ItemHandle>, cx: &mut ViewContext<Self>) {
1679        let active_pane = self.active_pane().clone();
1680        Pane::add_item(self, &active_pane, item, true, true, None, cx);
1681    }
1682
1683    pub fn open_path(
1684        &mut self,
1685        path: impl Into<ProjectPath>,
1686        focus_item: bool,
1687        cx: &mut ViewContext<Self>,
1688    ) -> Task<Result<Box<dyn ItemHandle>, Arc<anyhow::Error>>> {
1689        let pane = self.active_pane().downgrade();
1690        let task = self.load_path(path.into(), cx);
1691        cx.spawn(|this, mut cx| async move {
1692            let (project_entry_id, build_item) = task.await?;
1693            let pane = pane
1694                .upgrade(&cx)
1695                .ok_or_else(|| anyhow!("pane was closed"))?;
1696            this.update(&mut cx, |this, cx| {
1697                Ok(Pane::open_item(
1698                    this,
1699                    pane,
1700                    project_entry_id,
1701                    focus_item,
1702                    cx,
1703                    build_item,
1704                ))
1705            })
1706        })
1707    }
1708
1709    pub(crate) fn load_path(
1710        &mut self,
1711        path: ProjectPath,
1712        cx: &mut ViewContext<Self>,
1713    ) -> Task<
1714        Result<(
1715            ProjectEntryId,
1716            impl 'static + FnOnce(&mut ViewContext<Pane>) -> Box<dyn ItemHandle>,
1717        )>,
1718    > {
1719        let project = self.project().clone();
1720        let project_item = project.update(cx, |project, cx| project.open_path(path, cx));
1721        cx.as_mut().spawn(|mut cx| async move {
1722            let (project_entry_id, project_item) = project_item.await?;
1723            let build_item = cx.update(|cx| {
1724                cx.default_global::<ProjectItemBuilders>()
1725                    .get(&project_item.model_type())
1726                    .ok_or_else(|| anyhow!("no item builder for project item"))
1727                    .cloned()
1728            })?;
1729            let build_item =
1730                move |cx: &mut ViewContext<Pane>| build_item(project, project_item, cx);
1731            Ok((project_entry_id, build_item))
1732        })
1733    }
1734
1735    pub fn open_project_item<T>(
1736        &mut self,
1737        project_item: ModelHandle<T::Item>,
1738        cx: &mut ViewContext<Self>,
1739    ) -> ViewHandle<T>
1740    where
1741        T: ProjectItem,
1742    {
1743        use project::Item as _;
1744
1745        let entry_id = project_item.read(cx).entry_id(cx);
1746        if let Some(item) = entry_id
1747            .and_then(|entry_id| self.active_pane().read(cx).item_for_entry(entry_id, cx))
1748            .and_then(|item| item.downcast())
1749        {
1750            self.activate_item(&item, cx);
1751            return item;
1752        }
1753
1754        let item = cx.add_view(|cx| T::for_project_item(self.project().clone(), project_item, cx));
1755        self.add_item(Box::new(item.clone()), cx);
1756        item
1757    }
1758
1759    pub fn activate_item(&mut self, item: &dyn ItemHandle, cx: &mut ViewContext<Self>) -> bool {
1760        let result = self.panes.iter().find_map(|pane| {
1761            pane.read(cx)
1762                .index_for_item(item)
1763                .map(|ix| (pane.clone(), ix))
1764        });
1765        if let Some((pane, ix)) = result {
1766            pane.update(cx, |pane, cx| pane.activate_item(ix, true, true, cx));
1767            true
1768        } else {
1769            false
1770        }
1771    }
1772
1773    fn activate_pane_at_index(&mut self, action: &ActivatePane, cx: &mut ViewContext<Self>) {
1774        let panes = self.center.panes();
1775        if let Some(pane) = panes.get(action.0).map(|p| (*p).clone()) {
1776            cx.focus(pane);
1777        } else {
1778            self.split_pane(self.active_pane.clone(), SplitDirection::Right, cx);
1779        }
1780    }
1781
1782    pub fn activate_next_pane(&mut self, cx: &mut ViewContext<Self>) {
1783        let next_pane = {
1784            let panes = self.center.panes();
1785            let ix = panes
1786                .iter()
1787                .position(|pane| **pane == self.active_pane)
1788                .unwrap();
1789            let next_ix = (ix + 1) % panes.len();
1790            panes[next_ix].clone()
1791        };
1792        cx.focus(next_pane);
1793    }
1794
1795    pub fn activate_previous_pane(&mut self, cx: &mut ViewContext<Self>) {
1796        let prev_pane = {
1797            let panes = self.center.panes();
1798            let ix = panes
1799                .iter()
1800                .position(|pane| **pane == self.active_pane)
1801                .unwrap();
1802            let prev_ix = if ix == 0 { panes.len() - 1 } else { ix - 1 };
1803            panes[prev_ix].clone()
1804        };
1805        cx.focus(prev_pane);
1806    }
1807
1808    fn handle_pane_focused(&mut self, pane: ViewHandle<Pane>, cx: &mut ViewContext<Self>) {
1809        if self.active_pane != pane {
1810            self.active_pane
1811                .update(cx, |pane, cx| pane.set_active(false, cx));
1812            self.active_pane = pane.clone();
1813            self.active_pane
1814                .update(cx, |pane, cx| pane.set_active(true, cx));
1815            self.status_bar.update(cx, |status_bar, cx| {
1816                status_bar.set_active_pane(&self.active_pane, cx);
1817            });
1818            self.active_item_path_changed(cx);
1819
1820            if &pane == self.dock_pane() {
1821                Dock::show(self, cx);
1822            } else {
1823                self.last_active_center_pane = Some(pane.clone());
1824                if self.dock.is_anchored_at(DockAnchor::Expanded) {
1825                    Dock::hide(self, cx);
1826                }
1827            }
1828            cx.notify();
1829        }
1830
1831        self.update_followers(
1832            proto::update_followers::Variant::UpdateActiveView(proto::UpdateActiveView {
1833                id: self.active_item(cx).map(|item| item.id() as u64),
1834                leader_id: self.leader_for_pane(&pane).map(|id| id.0),
1835            }),
1836            cx,
1837        );
1838    }
1839
1840    fn handle_pane_event(
1841        &mut self,
1842        pane_id: usize,
1843        event: &pane::Event,
1844        cx: &mut ViewContext<Self>,
1845    ) {
1846        if let Some(pane) = self.pane(pane_id) {
1847            let is_dock = &pane == self.dock.pane();
1848            match event {
1849                pane::Event::Split(direction) if !is_dock => {
1850                    self.split_pane(pane, *direction, cx);
1851                }
1852                pane::Event::Remove if !is_dock => self.remove_pane(pane, cx),
1853                pane::Event::Remove if is_dock => Dock::hide(self, cx),
1854                pane::Event::Focused => self.handle_pane_focused(pane, cx),
1855                pane::Event::ActivateItem { local } => {
1856                    if *local {
1857                        self.unfollow(&pane, cx);
1858                    }
1859                    if &pane == self.active_pane() {
1860                        self.active_item_path_changed(cx);
1861                    }
1862                }
1863                pane::Event::ChangeItemTitle => {
1864                    if pane == self.active_pane {
1865                        self.active_item_path_changed(cx);
1866                    }
1867                    self.update_window_edited(cx);
1868                }
1869                pane::Event::RemoveItem { item_id } => {
1870                    self.update_window_edited(cx);
1871                    if let hash_map::Entry::Occupied(entry) = self.panes_by_item.entry(*item_id) {
1872                        if entry.get().id() == pane.id() {
1873                            entry.remove();
1874                        }
1875                    }
1876                }
1877                _ => {}
1878            }
1879        } else if self.dock.visible_pane().is_none() {
1880            error!("pane {} not found", pane_id);
1881        }
1882    }
1883
1884    pub fn split_pane(
1885        &mut self,
1886        pane: ViewHandle<Pane>,
1887        direction: SplitDirection,
1888        cx: &mut ViewContext<Self>,
1889    ) -> Option<ViewHandle<Pane>> {
1890        if &pane == self.dock_pane() {
1891            warn!("Can't split dock pane.");
1892            return None;
1893        }
1894
1895        pane.read(cx).active_item().map(|item| {
1896            let new_pane = self.add_pane(cx);
1897            if let Some(clone) = item.clone_on_split(cx.as_mut()) {
1898                Pane::add_item(self, &new_pane, clone, true, true, None, cx);
1899            }
1900            self.center.split(&pane, &new_pane, direction).unwrap();
1901            cx.notify();
1902            new_pane
1903        })
1904    }
1905
1906    fn remove_pane(&mut self, pane: ViewHandle<Pane>, cx: &mut ViewContext<Self>) {
1907        if self.center.remove(&pane).unwrap() {
1908            self.panes.retain(|p| p != &pane);
1909            cx.focus(self.panes.last().unwrap().clone());
1910            self.unfollow(&pane, cx);
1911            self.last_leaders_by_pane.remove(&pane.downgrade());
1912            for removed_item in pane.read(cx).items() {
1913                self.panes_by_item.remove(&removed_item.id());
1914            }
1915            if self.last_active_center_pane == Some(pane) {
1916                self.last_active_center_pane = None;
1917            }
1918
1919            cx.notify();
1920        } else {
1921            self.active_item_path_changed(cx);
1922        }
1923    }
1924
1925    pub fn panes(&self) -> &[ViewHandle<Pane>] {
1926        &self.panes
1927    }
1928
1929    fn pane(&self, pane_id: usize) -> Option<ViewHandle<Pane>> {
1930        self.panes.iter().find(|pane| pane.id() == pane_id).cloned()
1931    }
1932
1933    pub fn active_pane(&self) -> &ViewHandle<Pane> {
1934        &self.active_pane
1935    }
1936
1937    pub fn dock_pane(&self) -> &ViewHandle<Pane> {
1938        self.dock.pane()
1939    }
1940
1941    fn project_remote_id_changed(&mut self, remote_id: Option<u64>, cx: &mut ViewContext<Self>) {
1942        if let Some(remote_id) = remote_id {
1943            self.remote_entity_subscription =
1944                Some(self.client.add_view_for_remote_entity(remote_id, cx));
1945        } else {
1946            self.remote_entity_subscription.take();
1947        }
1948    }
1949
1950    fn collaborator_left(&mut self, peer_id: PeerId, cx: &mut ViewContext<Self>) {
1951        self.leader_state.followers.remove(&peer_id);
1952        if let Some(states_by_pane) = self.follower_states_by_leader.remove(&peer_id) {
1953            for state in states_by_pane.into_values() {
1954                for item in state.items_by_leader_view_id.into_values() {
1955                    if let FollowerItem::Loaded(item) = item {
1956                        item.set_leader_replica_id(None, cx);
1957                    }
1958                }
1959            }
1960        }
1961        cx.notify();
1962    }
1963
1964    pub fn toggle_follow(
1965        &mut self,
1966        ToggleFollow(leader_id): &ToggleFollow,
1967        cx: &mut ViewContext<Self>,
1968    ) -> Option<Task<Result<()>>> {
1969        let leader_id = *leader_id;
1970        let pane = self.active_pane().clone();
1971
1972        if let Some(prev_leader_id) = self.unfollow(&pane, cx) {
1973            if leader_id == prev_leader_id {
1974                return None;
1975            }
1976        }
1977
1978        self.last_leaders_by_pane
1979            .insert(pane.downgrade(), leader_id);
1980        self.follower_states_by_leader
1981            .entry(leader_id)
1982            .or_default()
1983            .insert(pane.clone(), Default::default());
1984        cx.notify();
1985
1986        let project_id = self.project.read(cx).remote_id()?;
1987        let request = self.client.request(proto::Follow {
1988            project_id,
1989            leader_id: leader_id.0,
1990        });
1991        Some(cx.spawn_weak(|this, mut cx| async move {
1992            let response = request.await?;
1993            if let Some(this) = this.upgrade(&cx) {
1994                this.update(&mut cx, |this, _| {
1995                    let state = this
1996                        .follower_states_by_leader
1997                        .get_mut(&leader_id)
1998                        .and_then(|states_by_pane| states_by_pane.get_mut(&pane))
1999                        .ok_or_else(|| anyhow!("following interrupted"))?;
2000                    state.active_view_id = response.active_view_id;
2001                    Ok::<_, anyhow::Error>(())
2002                })?;
2003                Self::add_views_from_leader(this, leader_id, vec![pane], response.views, &mut cx)
2004                    .await?;
2005            }
2006            Ok(())
2007        }))
2008    }
2009
2010    pub fn follow_next_collaborator(
2011        &mut self,
2012        _: &FollowNextCollaborator,
2013        cx: &mut ViewContext<Self>,
2014    ) -> Option<Task<Result<()>>> {
2015        let collaborators = self.project.read(cx).collaborators();
2016        let next_leader_id = if let Some(leader_id) = self.leader_for_pane(&self.active_pane) {
2017            let mut collaborators = collaborators.keys().copied();
2018            for peer_id in collaborators.by_ref() {
2019                if peer_id == leader_id {
2020                    break;
2021                }
2022            }
2023            collaborators.next()
2024        } else if let Some(last_leader_id) =
2025            self.last_leaders_by_pane.get(&self.active_pane.downgrade())
2026        {
2027            if collaborators.contains_key(last_leader_id) {
2028                Some(*last_leader_id)
2029            } else {
2030                None
2031            }
2032        } else {
2033            None
2034        };
2035
2036        next_leader_id
2037            .or_else(|| collaborators.keys().copied().next())
2038            .and_then(|leader_id| self.toggle_follow(&ToggleFollow(leader_id), cx))
2039    }
2040
2041    pub fn unfollow(
2042        &mut self,
2043        pane: &ViewHandle<Pane>,
2044        cx: &mut ViewContext<Self>,
2045    ) -> Option<PeerId> {
2046        for (leader_id, states_by_pane) in &mut self.follower_states_by_leader {
2047            let leader_id = *leader_id;
2048            if let Some(state) = states_by_pane.remove(pane) {
2049                for (_, item) in state.items_by_leader_view_id {
2050                    if let FollowerItem::Loaded(item) = item {
2051                        item.set_leader_replica_id(None, cx);
2052                    }
2053                }
2054
2055                if states_by_pane.is_empty() {
2056                    self.follower_states_by_leader.remove(&leader_id);
2057                    if let Some(project_id) = self.project.read(cx).remote_id() {
2058                        self.client
2059                            .send(proto::Unfollow {
2060                                project_id,
2061                                leader_id: leader_id.0,
2062                            })
2063                            .log_err();
2064                    }
2065                }
2066
2067                cx.notify();
2068                return Some(leader_id);
2069            }
2070        }
2071        None
2072    }
2073
2074    fn render_connection_status(&self, cx: &mut RenderContext<Self>) -> Option<ElementBox> {
2075        let theme = &cx.global::<Settings>().theme;
2076        match &*self.client.status().borrow() {
2077            client::Status::ConnectionError
2078            | client::Status::ConnectionLost
2079            | client::Status::Reauthenticating { .. }
2080            | client::Status::Reconnecting { .. }
2081            | client::Status::ReconnectionError { .. } => Some(
2082                Container::new(
2083                    Align::new(
2084                        ConstrainedBox::new(
2085                            Svg::new("icons/cloud_slash_12.svg")
2086                                .with_color(theme.workspace.titlebar.offline_icon.color)
2087                                .boxed(),
2088                        )
2089                        .with_width(theme.workspace.titlebar.offline_icon.width)
2090                        .boxed(),
2091                    )
2092                    .boxed(),
2093                )
2094                .with_style(theme.workspace.titlebar.offline_icon.container)
2095                .boxed(),
2096            ),
2097            client::Status::UpgradeRequired => Some(
2098                Label::new(
2099                    "Please update Zed to collaborate".to_string(),
2100                    theme.workspace.titlebar.outdated_warning.text.clone(),
2101                )
2102                .contained()
2103                .with_style(theme.workspace.titlebar.outdated_warning.container)
2104                .aligned()
2105                .boxed(),
2106            ),
2107            _ => None,
2108        }
2109    }
2110
2111    fn render_titlebar(&self, theme: &Theme, cx: &mut RenderContext<Self>) -> ElementBox {
2112        let project = &self.project.read(cx);
2113        let replica_id = project.replica_id();
2114        let mut worktree_root_names = String::new();
2115        for (i, name) in project.worktree_root_names(cx).enumerate() {
2116            if i > 0 {
2117                worktree_root_names.push_str(", ");
2118            }
2119            worktree_root_names.push_str(name);
2120        }
2121
2122        // TODO: There should be a better system in place for this
2123        // (https://github.com/zed-industries/zed/issues/1290)
2124        let is_fullscreen = cx.window_is_fullscreen(cx.window_id());
2125        let container_theme = if is_fullscreen {
2126            let mut container_theme = theme.workspace.titlebar.container;
2127            container_theme.padding.left = container_theme.padding.right;
2128            container_theme
2129        } else {
2130            theme.workspace.titlebar.container
2131        };
2132
2133        enum TitleBar {}
2134        ConstrainedBox::new(
2135            MouseEventHandler::<TitleBar>::new(0, cx, |_, cx| {
2136                Container::new(
2137                    Stack::new()
2138                        .with_child(
2139                            Label::new(worktree_root_names, theme.workspace.titlebar.title.clone())
2140                                .aligned()
2141                                .left()
2142                                .boxed(),
2143                        )
2144                        .with_child(
2145                            Align::new(
2146                                Flex::row()
2147                                    .with_children(self.render_collaborators(theme, cx))
2148                                    .with_children(self.render_current_user(
2149                                        self.user_store.read(cx).current_user().as_ref(),
2150                                        replica_id,
2151                                        theme,
2152                                        cx,
2153                                    ))
2154                                    .with_children(self.render_connection_status(cx))
2155                                    .boxed(),
2156                            )
2157                            .right()
2158                            .boxed(),
2159                        )
2160                        .boxed(),
2161                )
2162                .with_style(container_theme)
2163                .boxed()
2164            })
2165            .on_click(MouseButton::Left, |event, cx| {
2166                if event.click_count == 2 {
2167                    cx.zoom_window(cx.window_id());
2168                }
2169            })
2170            .boxed(),
2171        )
2172        .with_height(theme.workspace.titlebar.height)
2173        .named("titlebar")
2174    }
2175
2176    fn active_item_path_changed(&mut self, cx: &mut ViewContext<Self>) {
2177        let active_entry = self.active_project_path(cx);
2178        self.project
2179            .update(cx, |project, cx| project.set_active_path(active_entry, cx));
2180        self.update_window_title(cx);
2181    }
2182
2183    fn update_window_title(&mut self, cx: &mut ViewContext<Self>) {
2184        let mut title = String::new();
2185        let project = self.project().read(cx);
2186        if let Some(path) = self.active_item(cx).and_then(|item| item.project_path(cx)) {
2187            let filename = path
2188                .path
2189                .file_name()
2190                .map(|s| s.to_string_lossy())
2191                .or_else(|| {
2192                    Some(Cow::Borrowed(
2193                        project
2194                            .worktree_for_id(path.worktree_id, cx)?
2195                            .read(cx)
2196                            .root_name(),
2197                    ))
2198                });
2199            if let Some(filename) = filename {
2200                title.push_str(filename.as_ref());
2201                title.push_str("");
2202            }
2203        }
2204        for (i, name) in project.worktree_root_names(cx).enumerate() {
2205            if i > 0 {
2206                title.push_str(", ");
2207            }
2208            title.push_str(name);
2209        }
2210        if title.is_empty() {
2211            title = "empty project".to_string();
2212        }
2213        cx.set_window_title(&title);
2214    }
2215
2216    fn update_window_edited(&mut self, cx: &mut ViewContext<Self>) {
2217        let is_edited = !self.project.read(cx).is_read_only()
2218            && self
2219                .items(cx)
2220                .any(|item| item.has_conflict(cx) || item.is_dirty(cx));
2221        if is_edited != self.window_edited {
2222            self.window_edited = is_edited;
2223            cx.set_window_edited(self.window_edited)
2224        }
2225    }
2226
2227    fn render_collaborators(&self, theme: &Theme, cx: &mut RenderContext<Self>) -> Vec<ElementBox> {
2228        let mut collaborators = self
2229            .project
2230            .read(cx)
2231            .collaborators()
2232            .values()
2233            .cloned()
2234            .collect::<Vec<_>>();
2235        collaborators.sort_unstable_by_key(|collaborator| collaborator.replica_id);
2236        collaborators
2237            .into_iter()
2238            .filter_map(|collaborator| {
2239                Some(self.render_avatar(
2240                    collaborator.user.avatar.clone()?,
2241                    collaborator.replica_id,
2242                    Some((collaborator.peer_id, &collaborator.user.github_login)),
2243                    theme,
2244                    cx,
2245                ))
2246            })
2247            .collect()
2248    }
2249
2250    fn render_current_user(
2251        &self,
2252        user: Option<&Arc<User>>,
2253        replica_id: ReplicaId,
2254        theme: &Theme,
2255        cx: &mut RenderContext<Self>,
2256    ) -> Option<ElementBox> {
2257        let status = *self.client.status().borrow();
2258        if let Some(avatar) = user.and_then(|user| user.avatar.clone()) {
2259            Some(self.render_avatar(avatar, replica_id, None, theme, cx))
2260        } else if matches!(status, client::Status::UpgradeRequired) {
2261            None
2262        } else {
2263            Some(
2264                MouseEventHandler::<Authenticate>::new(0, cx, |state, _| {
2265                    let style = theme
2266                        .workspace
2267                        .titlebar
2268                        .sign_in_prompt
2269                        .style_for(state, false);
2270                    Label::new("Sign in".to_string(), style.text.clone())
2271                        .contained()
2272                        .with_style(style.container)
2273                        .boxed()
2274                })
2275                .on_click(MouseButton::Left, |_, cx| cx.dispatch_action(Authenticate))
2276                .with_cursor_style(CursorStyle::PointingHand)
2277                .aligned()
2278                .boxed(),
2279            )
2280        }
2281    }
2282
2283    fn render_avatar(
2284        &self,
2285        avatar: Arc<ImageData>,
2286        replica_id: ReplicaId,
2287        peer: Option<(PeerId, &str)>,
2288        theme: &Theme,
2289        cx: &mut RenderContext<Self>,
2290    ) -> ElementBox {
2291        let replica_color = theme.editor.replica_selection_style(replica_id).cursor;
2292        let is_followed = peer.map_or(false, |(peer_id, _)| {
2293            self.follower_states_by_leader.contains_key(&peer_id)
2294        });
2295        let mut avatar_style = theme.workspace.titlebar.avatar;
2296        if is_followed {
2297            avatar_style.border = Border::all(1.0, replica_color);
2298        }
2299        let content = Stack::new()
2300            .with_child(
2301                Image::new(avatar)
2302                    .with_style(avatar_style)
2303                    .constrained()
2304                    .with_width(theme.workspace.titlebar.avatar_width)
2305                    .aligned()
2306                    .boxed(),
2307            )
2308            .with_child(
2309                AvatarRibbon::new(replica_color)
2310                    .constrained()
2311                    .with_width(theme.workspace.titlebar.avatar_ribbon.width)
2312                    .with_height(theme.workspace.titlebar.avatar_ribbon.height)
2313                    .aligned()
2314                    .bottom()
2315                    .boxed(),
2316            )
2317            .constrained()
2318            .with_width(theme.workspace.titlebar.avatar_width)
2319            .contained()
2320            .with_margin_left(theme.workspace.titlebar.avatar_margin)
2321            .boxed();
2322
2323        if let Some((peer_id, peer_github_login)) = peer {
2324            MouseEventHandler::<ToggleFollow>::new(replica_id.into(), cx, move |_, _| content)
2325                .with_cursor_style(CursorStyle::PointingHand)
2326                .on_click(MouseButton::Left, move |_, cx| {
2327                    cx.dispatch_action(ToggleFollow(peer_id))
2328                })
2329                .with_tooltip::<ToggleFollow, _>(
2330                    peer_id.0 as usize,
2331                    if is_followed {
2332                        format!("Unfollow {}", peer_github_login)
2333                    } else {
2334                        format!("Follow {}", peer_github_login)
2335                    },
2336                    Some(Box::new(FollowNextCollaborator)),
2337                    theme.tooltip.clone(),
2338                    cx,
2339                )
2340                .boxed()
2341        } else {
2342            content
2343        }
2344    }
2345
2346    fn render_disconnected_overlay(&self, cx: &mut RenderContext<Workspace>) -> Option<ElementBox> {
2347        if self.project.read(cx).is_read_only() {
2348            enum DisconnectedOverlay {}
2349            Some(
2350                MouseEventHandler::<DisconnectedOverlay>::new(0, cx, |_, cx| {
2351                    let theme = &cx.global::<Settings>().theme;
2352                    Label::new(
2353                        "Your connection to the remote project has been lost.".to_string(),
2354                        theme.workspace.disconnected_overlay.text.clone(),
2355                    )
2356                    .aligned()
2357                    .contained()
2358                    .with_style(theme.workspace.disconnected_overlay.container)
2359                    .boxed()
2360                })
2361                .with_cursor_style(CursorStyle::Arrow)
2362                .capture_all()
2363                .boxed(),
2364            )
2365        } else {
2366            None
2367        }
2368    }
2369
2370    fn render_notifications(&self, theme: &theme::Workspace) -> Option<ElementBox> {
2371        if self.notifications.is_empty() {
2372            None
2373        } else {
2374            Some(
2375                Flex::column()
2376                    .with_children(self.notifications.iter().map(|(_, _, notification)| {
2377                        ChildView::new(notification.as_ref())
2378                            .contained()
2379                            .with_style(theme.notification)
2380                            .boxed()
2381                    }))
2382                    .constrained()
2383                    .with_width(theme.notifications.width)
2384                    .contained()
2385                    .with_style(theme.notifications.container)
2386                    .aligned()
2387                    .bottom()
2388                    .right()
2389                    .boxed(),
2390            )
2391        }
2392    }
2393
2394    // RPC handlers
2395
2396    async fn handle_follow(
2397        this: ViewHandle<Self>,
2398        envelope: TypedEnvelope<proto::Follow>,
2399        _: Arc<Client>,
2400        mut cx: AsyncAppContext,
2401    ) -> Result<proto::FollowResponse> {
2402        this.update(&mut cx, |this, cx| {
2403            this.leader_state
2404                .followers
2405                .insert(envelope.original_sender_id()?);
2406
2407            let active_view_id = this
2408                .active_item(cx)
2409                .and_then(|i| i.to_followable_item_handle(cx))
2410                .map(|i| i.id() as u64);
2411            Ok(proto::FollowResponse {
2412                active_view_id,
2413                views: this
2414                    .panes()
2415                    .iter()
2416                    .flat_map(|pane| {
2417                        let leader_id = this.leader_for_pane(pane).map(|id| id.0);
2418                        pane.read(cx).items().filter_map({
2419                            let cx = &cx;
2420                            move |item| {
2421                                let id = item.id() as u64;
2422                                let item = item.to_followable_item_handle(cx)?;
2423                                let variant = item.to_state_proto(cx)?;
2424                                Some(proto::View {
2425                                    id,
2426                                    leader_id,
2427                                    variant: Some(variant),
2428                                })
2429                            }
2430                        })
2431                    })
2432                    .collect(),
2433            })
2434        })
2435    }
2436
2437    async fn handle_unfollow(
2438        this: ViewHandle<Self>,
2439        envelope: TypedEnvelope<proto::Unfollow>,
2440        _: Arc<Client>,
2441        mut cx: AsyncAppContext,
2442    ) -> Result<()> {
2443        this.update(&mut cx, |this, _| {
2444            this.leader_state
2445                .followers
2446                .remove(&envelope.original_sender_id()?);
2447            Ok(())
2448        })
2449    }
2450
2451    async fn handle_update_followers(
2452        this: ViewHandle<Self>,
2453        envelope: TypedEnvelope<proto::UpdateFollowers>,
2454        _: Arc<Client>,
2455        mut cx: AsyncAppContext,
2456    ) -> Result<()> {
2457        let leader_id = envelope.original_sender_id()?;
2458        match envelope
2459            .payload
2460            .variant
2461            .ok_or_else(|| anyhow!("invalid update"))?
2462        {
2463            proto::update_followers::Variant::UpdateActiveView(update_active_view) => {
2464                this.update(&mut cx, |this, cx| {
2465                    this.update_leader_state(leader_id, cx, |state, _| {
2466                        state.active_view_id = update_active_view.id;
2467                    });
2468                    Ok::<_, anyhow::Error>(())
2469                })
2470            }
2471            proto::update_followers::Variant::UpdateView(update_view) => {
2472                this.update(&mut cx, |this, cx| {
2473                    let variant = update_view
2474                        .variant
2475                        .ok_or_else(|| anyhow!("missing update view variant"))?;
2476                    this.update_leader_state(leader_id, cx, |state, cx| {
2477                        let variant = variant.clone();
2478                        match state
2479                            .items_by_leader_view_id
2480                            .entry(update_view.id)
2481                            .or_insert(FollowerItem::Loading(Vec::new()))
2482                        {
2483                            FollowerItem::Loaded(item) => {
2484                                item.apply_update_proto(variant, cx).log_err();
2485                            }
2486                            FollowerItem::Loading(updates) => updates.push(variant),
2487                        }
2488                    });
2489                    Ok(())
2490                })
2491            }
2492            proto::update_followers::Variant::CreateView(view) => {
2493                let panes = this.read_with(&cx, |this, _| {
2494                    this.follower_states_by_leader
2495                        .get(&leader_id)
2496                        .into_iter()
2497                        .flat_map(|states_by_pane| states_by_pane.keys())
2498                        .cloned()
2499                        .collect()
2500                });
2501                Self::add_views_from_leader(this.clone(), leader_id, panes, vec![view], &mut cx)
2502                    .await?;
2503                Ok(())
2504            }
2505        }
2506        .log_err();
2507
2508        Ok(())
2509    }
2510
2511    async fn add_views_from_leader(
2512        this: ViewHandle<Self>,
2513        leader_id: PeerId,
2514        panes: Vec<ViewHandle<Pane>>,
2515        views: Vec<proto::View>,
2516        cx: &mut AsyncAppContext,
2517    ) -> Result<()> {
2518        let project = this.read_with(cx, |this, _| this.project.clone());
2519        let replica_id = project
2520            .read_with(cx, |project, _| {
2521                project
2522                    .collaborators()
2523                    .get(&leader_id)
2524                    .map(|c| c.replica_id)
2525            })
2526            .ok_or_else(|| anyhow!("no such collaborator {}", leader_id))?;
2527
2528        let item_builders = cx.update(|cx| {
2529            cx.default_global::<FollowableItemBuilders>()
2530                .values()
2531                .map(|b| b.0)
2532                .collect::<Vec<_>>()
2533        });
2534
2535        let mut item_tasks_by_pane = HashMap::default();
2536        for pane in panes {
2537            let mut item_tasks = Vec::new();
2538            let mut leader_view_ids = Vec::new();
2539            for view in &views {
2540                let mut variant = view.variant.clone();
2541                if variant.is_none() {
2542                    Err(anyhow!("missing variant"))?;
2543                }
2544                for build_item in &item_builders {
2545                    let task =
2546                        cx.update(|cx| build_item(pane.clone(), project.clone(), &mut variant, cx));
2547                    if let Some(task) = task {
2548                        item_tasks.push(task);
2549                        leader_view_ids.push(view.id);
2550                        break;
2551                    } else {
2552                        assert!(variant.is_some());
2553                    }
2554                }
2555            }
2556
2557            item_tasks_by_pane.insert(pane, (item_tasks, leader_view_ids));
2558        }
2559
2560        for (pane, (item_tasks, leader_view_ids)) in item_tasks_by_pane {
2561            let items = futures::future::try_join_all(item_tasks).await?;
2562            this.update(cx, |this, cx| {
2563                let state = this
2564                    .follower_states_by_leader
2565                    .get_mut(&leader_id)?
2566                    .get_mut(&pane)?;
2567
2568                for (id, item) in leader_view_ids.into_iter().zip(items) {
2569                    item.set_leader_replica_id(Some(replica_id), cx);
2570                    match state.items_by_leader_view_id.entry(id) {
2571                        hash_map::Entry::Occupied(e) => {
2572                            let e = e.into_mut();
2573                            if let FollowerItem::Loading(updates) = e {
2574                                for update in updates.drain(..) {
2575                                    item.apply_update_proto(update, cx)
2576                                        .context("failed to apply view update")
2577                                        .log_err();
2578                                }
2579                            }
2580                            *e = FollowerItem::Loaded(item);
2581                        }
2582                        hash_map::Entry::Vacant(e) => {
2583                            e.insert(FollowerItem::Loaded(item));
2584                        }
2585                    }
2586                }
2587
2588                Some(())
2589            });
2590        }
2591        this.update(cx, |this, cx| this.leader_updated(leader_id, cx));
2592
2593        Ok(())
2594    }
2595
2596    fn update_followers(
2597        &self,
2598        update: proto::update_followers::Variant,
2599        cx: &AppContext,
2600    ) -> Option<()> {
2601        let project_id = self.project.read(cx).remote_id()?;
2602        if !self.leader_state.followers.is_empty() {
2603            self.client
2604                .send(proto::UpdateFollowers {
2605                    project_id,
2606                    follower_ids: self.leader_state.followers.iter().map(|f| f.0).collect(),
2607                    variant: Some(update),
2608                })
2609                .log_err();
2610        }
2611        None
2612    }
2613
2614    pub fn leader_for_pane(&self, pane: &ViewHandle<Pane>) -> Option<PeerId> {
2615        self.follower_states_by_leader
2616            .iter()
2617            .find_map(|(leader_id, state)| {
2618                if state.contains_key(pane) {
2619                    Some(*leader_id)
2620                } else {
2621                    None
2622                }
2623            })
2624    }
2625
2626    fn update_leader_state(
2627        &mut self,
2628        leader_id: PeerId,
2629        cx: &mut ViewContext<Self>,
2630        mut update_fn: impl FnMut(&mut FollowerState, &mut ViewContext<Self>),
2631    ) {
2632        for (_, state) in self
2633            .follower_states_by_leader
2634            .get_mut(&leader_id)
2635            .into_iter()
2636            .flatten()
2637        {
2638            update_fn(state, cx);
2639        }
2640        self.leader_updated(leader_id, cx);
2641    }
2642
2643    fn leader_updated(&mut self, leader_id: PeerId, cx: &mut ViewContext<Self>) -> Option<()> {
2644        let mut items_to_add = Vec::new();
2645        for (pane, state) in self.follower_states_by_leader.get(&leader_id)? {
2646            if let Some(FollowerItem::Loaded(item)) = state
2647                .active_view_id
2648                .and_then(|id| state.items_by_leader_view_id.get(&id))
2649            {
2650                items_to_add.push((pane.clone(), item.boxed_clone()));
2651            }
2652        }
2653
2654        for (pane, item) in items_to_add {
2655            Pane::add_item(self, &pane, item.boxed_clone(), false, false, None, cx);
2656            if pane == self.active_pane {
2657                pane.update(cx, |pane, cx| pane.focus_active_item(cx));
2658            }
2659            cx.notify();
2660        }
2661        None
2662    }
2663
2664    pub fn on_window_activation_changed(&mut self, active: bool, cx: &mut ViewContext<Self>) {
2665        if !active {
2666            for pane in &self.panes {
2667                pane.update(cx, |pane, cx| {
2668                    if let Some(item) = pane.active_item() {
2669                        item.workspace_deactivated(cx);
2670                    }
2671                    if matches!(
2672                        cx.global::<Settings>().autosave,
2673                        Autosave::OnWindowChange | Autosave::OnFocusChange
2674                    ) {
2675                        for item in pane.items() {
2676                            Pane::autosave_item(item.as_ref(), self.project.clone(), cx)
2677                                .detach_and_log_err(cx);
2678                        }
2679                    }
2680                });
2681            }
2682        }
2683    }
2684}
2685
2686impl Entity for Workspace {
2687    type Event = Event;
2688}
2689
2690impl View for Workspace {
2691    fn ui_name() -> &'static str {
2692        "Workspace"
2693    }
2694
2695    fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
2696        let theme = cx.global::<Settings>().theme.clone();
2697        Stack::new()
2698            .with_child(
2699                Flex::column()
2700                    .with_child(self.render_titlebar(&theme, cx))
2701                    .with_child(
2702                        Stack::new()
2703                            .with_child({
2704                                Flex::row()
2705                                    .with_children(
2706                                        if self.left_sidebar.read(cx).active_item().is_some() {
2707                                            Some(
2708                                                ChildView::new(&self.left_sidebar)
2709                                                    .flex(0.8, false)
2710                                                    .boxed(),
2711                                            )
2712                                        } else {
2713                                            None
2714                                        },
2715                                    )
2716                                    .with_child(
2717                                        FlexItem::new(
2718                                            Flex::column()
2719                                                .with_child(
2720                                                    FlexItem::new(self.center.render(
2721                                                        &theme,
2722                                                        &self.follower_states_by_leader,
2723                                                        self.project.read(cx).collaborators(),
2724                                                    ))
2725                                                    .flex(1., true)
2726                                                    .boxed(),
2727                                                )
2728                                                .with_children(self.dock.render(
2729                                                    &theme,
2730                                                    DockAnchor::Bottom,
2731                                                    cx,
2732                                                ))
2733                                                .boxed(),
2734                                        )
2735                                        .flex(1., true)
2736                                        .boxed(),
2737                                    )
2738                                    .with_children(self.dock.render(&theme, DockAnchor::Right, cx))
2739                                    .with_children(
2740                                        if self.right_sidebar.read(cx).active_item().is_some() {
2741                                            Some(
2742                                                ChildView::new(&self.right_sidebar)
2743                                                    .flex(0.8, false)
2744                                                    .boxed(),
2745                                            )
2746                                        } else {
2747                                            None
2748                                        },
2749                                    )
2750                                    .boxed()
2751                            })
2752                            .with_child(
2753                                Overlay::new(
2754                                    Stack::new()
2755                                        .with_children(self.dock.render(
2756                                            &theme,
2757                                            DockAnchor::Expanded,
2758                                            cx,
2759                                        ))
2760                                        .with_children(self.modal.as_ref().map(|m| {
2761                                            ChildView::new(m)
2762                                                .contained()
2763                                                .with_style(theme.workspace.modal)
2764                                                .aligned()
2765                                                .top()
2766                                                .boxed()
2767                                        }))
2768                                        .with_children(self.render_notifications(&theme.workspace))
2769                                        .boxed(),
2770                                )
2771                                .boxed(),
2772                            )
2773                            .flex(1.0, true)
2774                            .boxed(),
2775                    )
2776                    .with_child(ChildView::new(&self.status_bar).boxed())
2777                    .contained()
2778                    .with_background_color(theme.workspace.background)
2779                    .boxed(),
2780            )
2781            .with_children(DragAndDrop::render(cx))
2782            .with_children(self.render_disconnected_overlay(cx))
2783            .named("workspace")
2784    }
2785
2786    fn on_focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
2787        if cx.is_self_focused() {
2788            cx.focus(&self.active_pane);
2789        }
2790    }
2791
2792    fn keymap_context(&self, _: &AppContext) -> gpui::keymap::Context {
2793        let mut keymap = Self::default_keymap_context();
2794        if self.active_pane() == self.dock_pane() {
2795            keymap.set.insert("Dock".into());
2796        }
2797        keymap
2798    }
2799}
2800
2801pub trait WorkspaceHandle {
2802    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath>;
2803}
2804
2805impl WorkspaceHandle for ViewHandle<Workspace> {
2806    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath> {
2807        self.read(cx)
2808            .worktrees(cx)
2809            .flat_map(|worktree| {
2810                let worktree_id = worktree.read(cx).id();
2811                worktree.read(cx).files(true, 0).map(move |f| ProjectPath {
2812                    worktree_id,
2813                    path: f.path.clone(),
2814                })
2815            })
2816            .collect::<Vec<_>>()
2817    }
2818}
2819
2820pub struct AvatarRibbon {
2821    color: Color,
2822}
2823
2824impl AvatarRibbon {
2825    pub fn new(color: Color) -> AvatarRibbon {
2826        AvatarRibbon { color }
2827    }
2828}
2829
2830impl Element for AvatarRibbon {
2831    type LayoutState = ();
2832
2833    type PaintState = ();
2834
2835    fn layout(
2836        &mut self,
2837        constraint: gpui::SizeConstraint,
2838        _: &mut gpui::LayoutContext,
2839    ) -> (gpui::geometry::vector::Vector2F, Self::LayoutState) {
2840        (constraint.max, ())
2841    }
2842
2843    fn paint(
2844        &mut self,
2845        bounds: gpui::geometry::rect::RectF,
2846        _: gpui::geometry::rect::RectF,
2847        _: &mut Self::LayoutState,
2848        cx: &mut gpui::PaintContext,
2849    ) -> Self::PaintState {
2850        let mut path = PathBuilder::new();
2851        path.reset(bounds.lower_left());
2852        path.curve_to(
2853            bounds.origin() + vec2f(bounds.height(), 0.),
2854            bounds.origin(),
2855        );
2856        path.line_to(bounds.upper_right() - vec2f(bounds.height(), 0.));
2857        path.curve_to(bounds.lower_right(), bounds.upper_right());
2858        path.line_to(bounds.lower_left());
2859        cx.scene.push_path(path.build(self.color, None));
2860    }
2861
2862    fn dispatch_event(
2863        &mut self,
2864        _: &gpui::Event,
2865        _: RectF,
2866        _: RectF,
2867        _: &mut Self::LayoutState,
2868        _: &mut Self::PaintState,
2869        _: &mut gpui::EventContext,
2870    ) -> bool {
2871        false
2872    }
2873
2874    fn rect_for_text_range(
2875        &self,
2876        _: Range<usize>,
2877        _: RectF,
2878        _: RectF,
2879        _: &Self::LayoutState,
2880        _: &Self::PaintState,
2881        _: &gpui::MeasurementContext,
2882    ) -> Option<RectF> {
2883        None
2884    }
2885
2886    fn debug(
2887        &self,
2888        bounds: gpui::geometry::rect::RectF,
2889        _: &Self::LayoutState,
2890        _: &Self::PaintState,
2891        _: &gpui::DebugContext,
2892    ) -> gpui::json::Value {
2893        json::json!({
2894            "type": "AvatarRibbon",
2895            "bounds": bounds.to_json(),
2896            "color": self.color.to_json(),
2897        })
2898    }
2899}
2900
2901impl std::fmt::Debug for OpenPaths {
2902    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2903        f.debug_struct("OpenPaths")
2904            .field("paths", &self.paths)
2905            .finish()
2906    }
2907}
2908
2909fn open(_: &Open, cx: &mut MutableAppContext) {
2910    let mut paths = cx.prompt_for_paths(PathPromptOptions {
2911        files: true,
2912        directories: true,
2913        multiple: true,
2914    });
2915    cx.spawn(|mut cx| async move {
2916        if let Some(paths) = paths.recv().await.flatten() {
2917            cx.update(|cx| cx.dispatch_global_action(OpenPaths { paths }));
2918        }
2919    })
2920    .detach();
2921}
2922
2923pub struct WorkspaceCreated(WeakViewHandle<Workspace>);
2924
2925pub fn activate_workspace_for_project(
2926    cx: &mut MutableAppContext,
2927    predicate: impl Fn(&mut Project, &mut ModelContext<Project>) -> bool,
2928) -> Option<ViewHandle<Workspace>> {
2929    for window_id in cx.window_ids().collect::<Vec<_>>() {
2930        if let Some(workspace_handle) = cx.root_view::<Workspace>(window_id) {
2931            let project = workspace_handle.read(cx).project.clone();
2932            if project.update(cx, &predicate) {
2933                cx.activate_window(window_id);
2934                return Some(workspace_handle);
2935            }
2936        }
2937    }
2938    None
2939}
2940
2941#[allow(clippy::type_complexity)]
2942pub fn open_paths(
2943    abs_paths: &[PathBuf],
2944    app_state: &Arc<AppState>,
2945    cx: &mut MutableAppContext,
2946) -> Task<(
2947    ViewHandle<Workspace>,
2948    Vec<Option<Result<Box<dyn ItemHandle>, Arc<anyhow::Error>>>>,
2949)> {
2950    log::info!("open paths {:?}", abs_paths);
2951
2952    // Open paths in existing workspace if possible
2953    let existing =
2954        activate_workspace_for_project(cx, |project, cx| project.contains_paths(abs_paths, cx));
2955
2956    let app_state = app_state.clone();
2957    let abs_paths = abs_paths.to_vec();
2958    cx.spawn(|mut cx| async move {
2959        let mut new_project = None;
2960        let workspace = if let Some(existing) = existing {
2961            existing
2962        } else {
2963            let contains_directory =
2964                futures::future::join_all(abs_paths.iter().map(|path| app_state.fs.is_file(path)))
2965                    .await
2966                    .contains(&false);
2967
2968            cx.add_window((app_state.build_window_options)(), |cx| {
2969                let project = Project::local(
2970                    false,
2971                    app_state.client.clone(),
2972                    app_state.user_store.clone(),
2973                    app_state.project_store.clone(),
2974                    app_state.languages.clone(),
2975                    app_state.fs.clone(),
2976                    cx,
2977                );
2978                new_project = Some(project.clone());
2979                let mut workspace = Workspace::new(project, app_state.default_item_factory, cx);
2980                (app_state.initialize_workspace)(&mut workspace, &app_state, cx);
2981                if contains_directory {
2982                    workspace.toggle_sidebar(SidebarSide::Left, cx);
2983                }
2984                workspace
2985            })
2986            .1
2987        };
2988
2989        let items = workspace
2990            .update(&mut cx, |workspace, cx| {
2991                workspace.open_paths(abs_paths, true, cx)
2992            })
2993            .await;
2994
2995        if let Some(project) = new_project {
2996            project
2997                .update(&mut cx, |project, cx| project.restore_state(cx))
2998                .await
2999                .log_err();
3000        }
3001
3002        (workspace, items)
3003    })
3004}
3005
3006pub fn join_project(
3007    contact: Arc<Contact>,
3008    project_index: usize,
3009    app_state: &Arc<AppState>,
3010    cx: &mut MutableAppContext,
3011) {
3012    let project_id = contact.projects[project_index].id;
3013
3014    for window_id in cx.window_ids().collect::<Vec<_>>() {
3015        if let Some(workspace) = cx.root_view::<Workspace>(window_id) {
3016            if workspace.read(cx).project().read(cx).remote_id() == Some(project_id) {
3017                cx.activate_window(window_id);
3018                return;
3019            }
3020        }
3021    }
3022
3023    cx.add_window((app_state.build_window_options)(), |cx| {
3024        WaitingRoom::new(contact, project_index, app_state.clone(), cx)
3025    });
3026}
3027
3028fn open_new(app_state: &Arc<AppState>, cx: &mut MutableAppContext) {
3029    let (window_id, workspace) = cx.add_window((app_state.build_window_options)(), |cx| {
3030        let mut workspace = Workspace::new(
3031            Project::local(
3032                false,
3033                app_state.client.clone(),
3034                app_state.user_store.clone(),
3035                app_state.project_store.clone(),
3036                app_state.languages.clone(),
3037                app_state.fs.clone(),
3038                cx,
3039            ),
3040            app_state.default_item_factory,
3041            cx,
3042        );
3043        (app_state.initialize_workspace)(&mut workspace, app_state, cx);
3044        workspace
3045    });
3046    cx.dispatch_action_at(window_id, workspace.id(), NewFile);
3047}
3048
3049#[cfg(test)]
3050mod tests {
3051    use std::cell::Cell;
3052
3053    use crate::sidebar::SidebarItem;
3054
3055    use super::*;
3056    use gpui::{executor::Deterministic, ModelHandle, TestAppContext, ViewContext};
3057    use project::{FakeFs, Project, ProjectEntryId};
3058    use serde_json::json;
3059
3060    pub fn default_item_factory(
3061        _workspace: &mut Workspace,
3062        _cx: &mut ViewContext<Workspace>,
3063    ) -> Box<dyn ItemHandle> {
3064        unimplemented!();
3065    }
3066
3067    #[gpui::test]
3068    async fn test_tab_disambiguation(cx: &mut TestAppContext) {
3069        cx.foreground().forbid_parking();
3070        Settings::test_async(cx);
3071
3072        let fs = FakeFs::new(cx.background());
3073        let project = Project::test(fs, [], cx).await;
3074        let (_, workspace) =
3075            cx.add_window(|cx| Workspace::new(project.clone(), default_item_factory, cx));
3076
3077        // Adding an item with no ambiguity renders the tab without detail.
3078        let item1 = cx.add_view(&workspace, |_| {
3079            let mut item = TestItem::new();
3080            item.tab_descriptions = Some(vec!["c", "b1/c", "a/b1/c"]);
3081            item
3082        });
3083        workspace.update(cx, |workspace, cx| {
3084            workspace.add_item(Box::new(item1.clone()), cx);
3085        });
3086        item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), None));
3087
3088        // Adding an item that creates ambiguity increases the level of detail on
3089        // both tabs.
3090        let item2 = cx.add_view(&workspace, |_| {
3091            let mut item = TestItem::new();
3092            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
3093            item
3094        });
3095        workspace.update(cx, |workspace, cx| {
3096            workspace.add_item(Box::new(item2.clone()), cx);
3097        });
3098        item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
3099        item2.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
3100
3101        // Adding an item that creates ambiguity increases the level of detail only
3102        // on the ambiguous tabs. In this case, the ambiguity can't be resolved so
3103        // we stop at the highest detail available.
3104        let item3 = cx.add_view(&workspace, |_| {
3105            let mut item = TestItem::new();
3106            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
3107            item
3108        });
3109        workspace.update(cx, |workspace, cx| {
3110            workspace.add_item(Box::new(item3.clone()), cx);
3111        });
3112        item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
3113        item2.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
3114        item3.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
3115    }
3116
3117    #[gpui::test]
3118    async fn test_tracking_active_path(cx: &mut TestAppContext) {
3119        cx.foreground().forbid_parking();
3120        Settings::test_async(cx);
3121        let fs = FakeFs::new(cx.background());
3122        fs.insert_tree(
3123            "/root1",
3124            json!({
3125                "one.txt": "",
3126                "two.txt": "",
3127            }),
3128        )
3129        .await;
3130        fs.insert_tree(
3131            "/root2",
3132            json!({
3133                "three.txt": "",
3134            }),
3135        )
3136        .await;
3137
3138        let project = Project::test(fs, ["root1".as_ref()], cx).await;
3139        let (window_id, workspace) =
3140            cx.add_window(|cx| Workspace::new(project.clone(), default_item_factory, cx));
3141        let worktree_id = project.read_with(cx, |project, cx| {
3142            project.worktrees(cx).next().unwrap().read(cx).id()
3143        });
3144
3145        let item1 = cx.add_view(&workspace, |_| {
3146            let mut item = TestItem::new();
3147            item.project_path = Some((worktree_id, "one.txt").into());
3148            item
3149        });
3150        let item2 = cx.add_view(&workspace, |_| {
3151            let mut item = TestItem::new();
3152            item.project_path = Some((worktree_id, "two.txt").into());
3153            item
3154        });
3155
3156        // Add an item to an empty pane
3157        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item1), cx));
3158        project.read_with(cx, |project, cx| {
3159            assert_eq!(
3160                project.active_entry(),
3161                project
3162                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
3163                    .map(|e| e.id)
3164            );
3165        });
3166        assert_eq!(
3167            cx.current_window_title(window_id).as_deref(),
3168            Some("one.txt — root1")
3169        );
3170
3171        // Add a second item to a non-empty pane
3172        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item2), cx));
3173        assert_eq!(
3174            cx.current_window_title(window_id).as_deref(),
3175            Some("two.txt — root1")
3176        );
3177        project.read_with(cx, |project, cx| {
3178            assert_eq!(
3179                project.active_entry(),
3180                project
3181                    .entry_for_path(&(worktree_id, "two.txt").into(), cx)
3182                    .map(|e| e.id)
3183            );
3184        });
3185
3186        // Close the active item
3187        workspace
3188            .update(cx, |workspace, cx| {
3189                Pane::close_active_item(workspace, &Default::default(), cx).unwrap()
3190            })
3191            .await
3192            .unwrap();
3193        assert_eq!(
3194            cx.current_window_title(window_id).as_deref(),
3195            Some("one.txt — root1")
3196        );
3197        project.read_with(cx, |project, cx| {
3198            assert_eq!(
3199                project.active_entry(),
3200                project
3201                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
3202                    .map(|e| e.id)
3203            );
3204        });
3205
3206        // Add a project folder
3207        project
3208            .update(cx, |project, cx| {
3209                project.find_or_create_local_worktree("/root2", true, cx)
3210            })
3211            .await
3212            .unwrap();
3213        assert_eq!(
3214            cx.current_window_title(window_id).as_deref(),
3215            Some("one.txt — root1, root2")
3216        );
3217
3218        // Remove a project folder
3219        project.update(cx, |project, cx| {
3220            project.remove_worktree(worktree_id, cx);
3221        });
3222        assert_eq!(
3223            cx.current_window_title(window_id).as_deref(),
3224            Some("one.txt — root2")
3225        );
3226    }
3227
3228    #[gpui::test]
3229    async fn test_close_window(cx: &mut TestAppContext) {
3230        cx.foreground().forbid_parking();
3231        Settings::test_async(cx);
3232        let fs = FakeFs::new(cx.background());
3233        fs.insert_tree("/root", json!({ "one": "" })).await;
3234
3235        let project = Project::test(fs, ["root".as_ref()], cx).await;
3236        let (window_id, workspace) =
3237            cx.add_window(|cx| Workspace::new(project.clone(), default_item_factory, cx));
3238
3239        // When there are no dirty items, there's nothing to do.
3240        let item1 = cx.add_view(&workspace, |_| TestItem::new());
3241        workspace.update(cx, |w, cx| w.add_item(Box::new(item1.clone()), cx));
3242        let task = workspace.update(cx, |w, cx| w.prepare_to_close(cx));
3243        assert!(task.await.unwrap());
3244
3245        // When there are dirty untitled items, prompt to save each one. If the user
3246        // cancels any prompt, then abort.
3247        let item2 = cx.add_view(&workspace, |_| {
3248            let mut item = TestItem::new();
3249            item.is_dirty = true;
3250            item
3251        });
3252        let item3 = cx.add_view(&workspace, |_| {
3253            let mut item = TestItem::new();
3254            item.is_dirty = true;
3255            item.project_entry_ids = vec![ProjectEntryId::from_proto(1)];
3256            item
3257        });
3258        workspace.update(cx, |w, cx| {
3259            w.add_item(Box::new(item2.clone()), cx);
3260            w.add_item(Box::new(item3.clone()), cx);
3261        });
3262        let task = workspace.update(cx, |w, cx| w.prepare_to_close(cx));
3263        cx.foreground().run_until_parked();
3264        cx.simulate_prompt_answer(window_id, 2 /* cancel */);
3265        cx.foreground().run_until_parked();
3266        assert!(!cx.has_pending_prompt(window_id));
3267        assert!(!task.await.unwrap());
3268    }
3269
3270    #[gpui::test]
3271    async fn test_close_pane_items(cx: &mut TestAppContext) {
3272        cx.foreground().forbid_parking();
3273        Settings::test_async(cx);
3274        let fs = FakeFs::new(cx.background());
3275
3276        let project = Project::test(fs, None, cx).await;
3277        let (window_id, workspace) =
3278            cx.add_window(|cx| Workspace::new(project, default_item_factory, cx));
3279
3280        let item1 = cx.add_view(&workspace, |_| {
3281            let mut item = TestItem::new();
3282            item.is_dirty = true;
3283            item.project_entry_ids = vec![ProjectEntryId::from_proto(1)];
3284            item
3285        });
3286        let item2 = cx.add_view(&workspace, |_| {
3287            let mut item = TestItem::new();
3288            item.is_dirty = true;
3289            item.has_conflict = true;
3290            item.project_entry_ids = vec![ProjectEntryId::from_proto(2)];
3291            item
3292        });
3293        let item3 = cx.add_view(&workspace, |_| {
3294            let mut item = TestItem::new();
3295            item.is_dirty = true;
3296            item.has_conflict = true;
3297            item.project_entry_ids = vec![ProjectEntryId::from_proto(3)];
3298            item
3299        });
3300        let item4 = cx.add_view(&workspace, |_| {
3301            let mut item = TestItem::new();
3302            item.is_dirty = true;
3303            item
3304        });
3305        let pane = workspace.update(cx, |workspace, cx| {
3306            workspace.add_item(Box::new(item1.clone()), cx);
3307            workspace.add_item(Box::new(item2.clone()), cx);
3308            workspace.add_item(Box::new(item3.clone()), cx);
3309            workspace.add_item(Box::new(item4.clone()), cx);
3310            workspace.active_pane().clone()
3311        });
3312
3313        let close_items = workspace.update(cx, |workspace, cx| {
3314            pane.update(cx, |pane, cx| {
3315                pane.activate_item(1, true, true, cx);
3316                assert_eq!(pane.active_item().unwrap().id(), item2.id());
3317            });
3318
3319            let item1_id = item1.id();
3320            let item3_id = item3.id();
3321            let item4_id = item4.id();
3322            Pane::close_items(workspace, pane.clone(), cx, move |id| {
3323                [item1_id, item3_id, item4_id].contains(&id)
3324            })
3325        });
3326
3327        cx.foreground().run_until_parked();
3328        pane.read_with(cx, |pane, _| {
3329            assert_eq!(pane.items().count(), 4);
3330            assert_eq!(pane.active_item().unwrap().id(), item1.id());
3331        });
3332
3333        cx.simulate_prompt_answer(window_id, 0);
3334        cx.foreground().run_until_parked();
3335        pane.read_with(cx, |pane, cx| {
3336            assert_eq!(item1.read(cx).save_count, 1);
3337            assert_eq!(item1.read(cx).save_as_count, 0);
3338            assert_eq!(item1.read(cx).reload_count, 0);
3339            assert_eq!(pane.items().count(), 3);
3340            assert_eq!(pane.active_item().unwrap().id(), item3.id());
3341        });
3342
3343        cx.simulate_prompt_answer(window_id, 1);
3344        cx.foreground().run_until_parked();
3345        pane.read_with(cx, |pane, cx| {
3346            assert_eq!(item3.read(cx).save_count, 0);
3347            assert_eq!(item3.read(cx).save_as_count, 0);
3348            assert_eq!(item3.read(cx).reload_count, 1);
3349            assert_eq!(pane.items().count(), 2);
3350            assert_eq!(pane.active_item().unwrap().id(), item4.id());
3351        });
3352
3353        cx.simulate_prompt_answer(window_id, 0);
3354        cx.foreground().run_until_parked();
3355        cx.simulate_new_path_selection(|_| Some(Default::default()));
3356        close_items.await.unwrap();
3357        pane.read_with(cx, |pane, cx| {
3358            assert_eq!(item4.read(cx).save_count, 0);
3359            assert_eq!(item4.read(cx).save_as_count, 1);
3360            assert_eq!(item4.read(cx).reload_count, 0);
3361            assert_eq!(pane.items().count(), 1);
3362            assert_eq!(pane.active_item().unwrap().id(), item2.id());
3363        });
3364    }
3365
3366    #[gpui::test]
3367    async fn test_prompting_to_save_only_on_last_item_for_entry(cx: &mut TestAppContext) {
3368        cx.foreground().forbid_parking();
3369        Settings::test_async(cx);
3370        let fs = FakeFs::new(cx.background());
3371
3372        let project = Project::test(fs, [], cx).await;
3373        let (window_id, workspace) =
3374            cx.add_window(|cx| Workspace::new(project, default_item_factory, cx));
3375
3376        // Create several workspace items with single project entries, and two
3377        // workspace items with multiple project entries.
3378        let single_entry_items = (0..=4)
3379            .map(|project_entry_id| {
3380                let mut item = TestItem::new();
3381                item.is_dirty = true;
3382                item.project_entry_ids = vec![ProjectEntryId::from_proto(project_entry_id)];
3383                item.is_singleton = true;
3384                item
3385            })
3386            .collect::<Vec<_>>();
3387        let item_2_3 = {
3388            let mut item = TestItem::new();
3389            item.is_dirty = true;
3390            item.is_singleton = false;
3391            item.project_entry_ids =
3392                vec![ProjectEntryId::from_proto(2), ProjectEntryId::from_proto(3)];
3393            item
3394        };
3395        let item_3_4 = {
3396            let mut item = TestItem::new();
3397            item.is_dirty = true;
3398            item.is_singleton = false;
3399            item.project_entry_ids =
3400                vec![ProjectEntryId::from_proto(3), ProjectEntryId::from_proto(4)];
3401            item
3402        };
3403
3404        // Create two panes that contain the following project entries:
3405        //   left pane:
3406        //     multi-entry items:   (2, 3)
3407        //     single-entry items:  0, 1, 2, 3, 4
3408        //   right pane:
3409        //     single-entry items:  1
3410        //     multi-entry items:   (3, 4)
3411        let left_pane = workspace.update(cx, |workspace, cx| {
3412            let left_pane = workspace.active_pane().clone();
3413            workspace.add_item(Box::new(cx.add_view(|_| item_2_3.clone())), cx);
3414            for item in &single_entry_items {
3415                workspace.add_item(Box::new(cx.add_view(|_| item.clone())), cx);
3416            }
3417            left_pane.update(cx, |pane, cx| {
3418                pane.activate_item(2, true, true, cx);
3419            });
3420
3421            workspace
3422                .split_pane(left_pane.clone(), SplitDirection::Right, cx)
3423                .unwrap();
3424
3425            left_pane
3426        });
3427
3428        //Need to cause an effect flush in order to respect new focus
3429        workspace.update(cx, |workspace, cx| {
3430            workspace.add_item(Box::new(cx.add_view(|_| item_3_4.clone())), cx);
3431            cx.focus(left_pane.clone());
3432        });
3433
3434        // When closing all of the items in the left pane, we should be prompted twice:
3435        // once for project entry 0, and once for project entry 2. After those two
3436        // prompts, the task should complete.
3437
3438        let close = workspace.update(cx, |workspace, cx| {
3439            Pane::close_items(workspace, left_pane.clone(), cx, |_| true)
3440        });
3441
3442        cx.foreground().run_until_parked();
3443        left_pane.read_with(cx, |pane, cx| {
3444            assert_eq!(
3445                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
3446                &[ProjectEntryId::from_proto(0)]
3447            );
3448        });
3449        cx.simulate_prompt_answer(window_id, 0);
3450
3451        cx.foreground().run_until_parked();
3452        left_pane.read_with(cx, |pane, cx| {
3453            assert_eq!(
3454                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
3455                &[ProjectEntryId::from_proto(2)]
3456            );
3457        });
3458        cx.simulate_prompt_answer(window_id, 0);
3459
3460        cx.foreground().run_until_parked();
3461        close.await.unwrap();
3462        left_pane.read_with(cx, |pane, _| {
3463            assert_eq!(pane.items().count(), 0);
3464        });
3465    }
3466
3467    #[gpui::test]
3468    async fn test_autosave(deterministic: Arc<Deterministic>, cx: &mut gpui::TestAppContext) {
3469        deterministic.forbid_parking();
3470
3471        Settings::test_async(cx);
3472        let fs = FakeFs::new(cx.background());
3473
3474        let project = Project::test(fs, [], cx).await;
3475        let (window_id, workspace) =
3476            cx.add_window(|cx| Workspace::new(project, default_item_factory, cx));
3477
3478        let item = cx.add_view(&workspace, |_| {
3479            let mut item = TestItem::new();
3480            item.project_entry_ids = vec![ProjectEntryId::from_proto(1)];
3481            item
3482        });
3483        let item_id = item.id();
3484        workspace.update(cx, |workspace, cx| {
3485            workspace.add_item(Box::new(item.clone()), cx);
3486        });
3487
3488        // Autosave on window change.
3489        item.update(cx, |item, cx| {
3490            cx.update_global(|settings: &mut Settings, _| {
3491                settings.autosave = Autosave::OnWindowChange;
3492            });
3493            item.is_dirty = true;
3494        });
3495
3496        // Deactivating the window saves the file.
3497        cx.simulate_window_activation(None);
3498        deterministic.run_until_parked();
3499        item.read_with(cx, |item, _| assert_eq!(item.save_count, 1));
3500
3501        // Autosave on focus change.
3502        item.update(cx, |item, cx| {
3503            cx.focus_self();
3504            cx.update_global(|settings: &mut Settings, _| {
3505                settings.autosave = Autosave::OnFocusChange;
3506            });
3507            item.is_dirty = true;
3508        });
3509
3510        // Blurring the item saves the file.
3511        item.update(cx, |_, cx| cx.blur());
3512        deterministic.run_until_parked();
3513        item.read_with(cx, |item, _| assert_eq!(item.save_count, 2));
3514
3515        // Deactivating the window still saves the file.
3516        cx.simulate_window_activation(Some(window_id));
3517        item.update(cx, |item, cx| {
3518            cx.focus_self();
3519            item.is_dirty = true;
3520        });
3521        cx.simulate_window_activation(None);
3522
3523        deterministic.run_until_parked();
3524        item.read_with(cx, |item, _| assert_eq!(item.save_count, 3));
3525
3526        // Autosave after delay.
3527        item.update(cx, |item, cx| {
3528            cx.update_global(|settings: &mut Settings, _| {
3529                settings.autosave = Autosave::AfterDelay { milliseconds: 500 };
3530            });
3531            item.is_dirty = true;
3532            cx.emit(TestItemEvent::Edit);
3533        });
3534
3535        // Delay hasn't fully expired, so the file is still dirty and unsaved.
3536        deterministic.advance_clock(Duration::from_millis(250));
3537        item.read_with(cx, |item, _| assert_eq!(item.save_count, 3));
3538
3539        // After delay expires, the file is saved.
3540        deterministic.advance_clock(Duration::from_millis(250));
3541        item.read_with(cx, |item, _| assert_eq!(item.save_count, 4));
3542
3543        // Autosave on focus change, ensuring closing the tab counts as such.
3544        item.update(cx, |item, cx| {
3545            cx.update_global(|settings: &mut Settings, _| {
3546                settings.autosave = Autosave::OnFocusChange;
3547            });
3548            item.is_dirty = true;
3549        });
3550
3551        workspace
3552            .update(cx, |workspace, cx| {
3553                let pane = workspace.active_pane().clone();
3554                Pane::close_items(workspace, pane, cx, move |id| id == item_id)
3555            })
3556            .await
3557            .unwrap();
3558        assert!(!cx.has_pending_prompt(window_id));
3559        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
3560
3561        // Add the item again, ensuring autosave is prevented if the underlying file has been deleted.
3562        workspace.update(cx, |workspace, cx| {
3563            workspace.add_item(Box::new(item.clone()), cx);
3564        });
3565        item.update(cx, |item, cx| {
3566            item.project_entry_ids = Default::default();
3567            item.is_dirty = true;
3568            cx.blur();
3569        });
3570        deterministic.run_until_parked();
3571        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
3572
3573        // Ensure autosave is prevented for deleted files also when closing the buffer.
3574        let _close_items = workspace.update(cx, |workspace, cx| {
3575            let pane = workspace.active_pane().clone();
3576            Pane::close_items(workspace, pane, cx, move |id| id == item_id)
3577        });
3578        deterministic.run_until_parked();
3579        assert!(cx.has_pending_prompt(window_id));
3580        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
3581    }
3582
3583    #[gpui::test]
3584    async fn test_pane_navigation(
3585        deterministic: Arc<Deterministic>,
3586        cx: &mut gpui::TestAppContext,
3587    ) {
3588        deterministic.forbid_parking();
3589        Settings::test_async(cx);
3590        let fs = FakeFs::new(cx.background());
3591
3592        let project = Project::test(fs, [], cx).await;
3593        let (_, workspace) = cx.add_window(|cx| Workspace::new(project, default_item_factory, cx));
3594
3595        let item = cx.add_view(&workspace, |_| {
3596            let mut item = TestItem::new();
3597            item.project_entry_ids = vec![ProjectEntryId::from_proto(1)];
3598            item
3599        });
3600        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
3601        let toolbar = pane.read_with(cx, |pane, _| pane.toolbar().clone());
3602        let toolbar_notify_count = Rc::new(RefCell::new(0));
3603
3604        workspace.update(cx, |workspace, cx| {
3605            workspace.add_item(Box::new(item.clone()), cx);
3606            let toolbar_notification_count = toolbar_notify_count.clone();
3607            cx.observe(&toolbar, move |_, _, _| {
3608                *toolbar_notification_count.borrow_mut() += 1
3609            })
3610            .detach();
3611        });
3612
3613        pane.read_with(cx, |pane, _| {
3614            assert!(!pane.can_navigate_backward());
3615            assert!(!pane.can_navigate_forward());
3616        });
3617
3618        item.update(cx, |item, cx| {
3619            item.set_state("one".to_string(), cx);
3620        });
3621
3622        // Toolbar must be notified to re-render the navigation buttons
3623        assert_eq!(*toolbar_notify_count.borrow(), 1);
3624
3625        pane.read_with(cx, |pane, _| {
3626            assert!(pane.can_navigate_backward());
3627            assert!(!pane.can_navigate_forward());
3628        });
3629
3630        workspace
3631            .update(cx, |workspace, cx| {
3632                Pane::go_back(workspace, Some(pane.clone()), cx)
3633            })
3634            .await;
3635
3636        assert_eq!(*toolbar_notify_count.borrow(), 3);
3637        pane.read_with(cx, |pane, _| {
3638            assert!(!pane.can_navigate_backward());
3639            assert!(pane.can_navigate_forward());
3640        });
3641    }
3642
3643    pub struct TestItem {
3644        state: String,
3645        pub label: String,
3646        save_count: usize,
3647        save_as_count: usize,
3648        reload_count: usize,
3649        is_dirty: bool,
3650        is_singleton: bool,
3651        has_conflict: bool,
3652        project_entry_ids: Vec<ProjectEntryId>,
3653        project_path: Option<ProjectPath>,
3654        nav_history: Option<ItemNavHistory>,
3655        tab_descriptions: Option<Vec<&'static str>>,
3656        tab_detail: Cell<Option<usize>>,
3657    }
3658
3659    pub enum TestItemEvent {
3660        Edit,
3661    }
3662
3663    impl Clone for TestItem {
3664        fn clone(&self) -> Self {
3665            Self {
3666                state: self.state.clone(),
3667                label: self.label.clone(),
3668                save_count: self.save_count,
3669                save_as_count: self.save_as_count,
3670                reload_count: self.reload_count,
3671                is_dirty: self.is_dirty,
3672                is_singleton: self.is_singleton,
3673                has_conflict: self.has_conflict,
3674                project_entry_ids: self.project_entry_ids.clone(),
3675                project_path: self.project_path.clone(),
3676                nav_history: None,
3677                tab_descriptions: None,
3678                tab_detail: Default::default(),
3679            }
3680        }
3681    }
3682
3683    impl TestItem {
3684        pub fn new() -> Self {
3685            Self {
3686                state: String::new(),
3687                label: String::new(),
3688                save_count: 0,
3689                save_as_count: 0,
3690                reload_count: 0,
3691                is_dirty: false,
3692                has_conflict: false,
3693                project_entry_ids: Vec::new(),
3694                project_path: None,
3695                is_singleton: true,
3696                nav_history: None,
3697                tab_descriptions: None,
3698                tab_detail: Default::default(),
3699            }
3700        }
3701
3702        pub fn with_label(mut self, state: &str) -> Self {
3703            self.label = state.to_string();
3704            self
3705        }
3706
3707        pub fn with_singleton(mut self, singleton: bool) -> Self {
3708            self.is_singleton = singleton;
3709            self
3710        }
3711
3712        pub fn with_project_entry_ids(mut self, project_entry_ids: &[u64]) -> Self {
3713            self.project_entry_ids.extend(
3714                project_entry_ids
3715                    .iter()
3716                    .copied()
3717                    .map(ProjectEntryId::from_proto),
3718            );
3719            self
3720        }
3721
3722        fn set_state(&mut self, state: String, cx: &mut ViewContext<Self>) {
3723            self.push_to_nav_history(cx);
3724            self.state = state;
3725        }
3726
3727        fn push_to_nav_history(&mut self, cx: &mut ViewContext<Self>) {
3728            if let Some(history) = &mut self.nav_history {
3729                history.push(Some(Box::new(self.state.clone())), cx);
3730            }
3731        }
3732    }
3733
3734    impl Entity for TestItem {
3735        type Event = TestItemEvent;
3736    }
3737
3738    impl View for TestItem {
3739        fn ui_name() -> &'static str {
3740            "TestItem"
3741        }
3742
3743        fn render(&mut self, _: &mut RenderContext<Self>) -> ElementBox {
3744            Empty::new().boxed()
3745        }
3746    }
3747
3748    impl Item for TestItem {
3749        fn tab_description<'a>(&'a self, detail: usize, _: &'a AppContext) -> Option<Cow<'a, str>> {
3750            self.tab_descriptions.as_ref().and_then(|descriptions| {
3751                let description = *descriptions.get(detail).or_else(|| descriptions.last())?;
3752                Some(description.into())
3753            })
3754        }
3755
3756        fn tab_content(&self, detail: Option<usize>, _: &theme::Tab, _: &AppContext) -> ElementBox {
3757            self.tab_detail.set(detail);
3758            Empty::new().boxed()
3759        }
3760
3761        fn project_path(&self, _: &AppContext) -> Option<ProjectPath> {
3762            self.project_path.clone()
3763        }
3764
3765        fn project_entry_ids(&self, _: &AppContext) -> SmallVec<[ProjectEntryId; 3]> {
3766            self.project_entry_ids.iter().copied().collect()
3767        }
3768
3769        fn is_singleton(&self, _: &AppContext) -> bool {
3770            self.is_singleton
3771        }
3772
3773        fn set_nav_history(&mut self, history: ItemNavHistory, _: &mut ViewContext<Self>) {
3774            self.nav_history = Some(history);
3775        }
3776
3777        fn navigate(&mut self, state: Box<dyn Any>, _: &mut ViewContext<Self>) -> bool {
3778            let state = *state.downcast::<String>().unwrap_or_default();
3779            if state != self.state {
3780                self.state = state;
3781                true
3782            } else {
3783                false
3784            }
3785        }
3786
3787        fn deactivated(&mut self, cx: &mut ViewContext<Self>) {
3788            self.push_to_nav_history(cx);
3789        }
3790
3791        fn clone_on_split(&self, _: &mut ViewContext<Self>) -> Option<Self>
3792        where
3793            Self: Sized,
3794        {
3795            Some(self.clone())
3796        }
3797
3798        fn is_dirty(&self, _: &AppContext) -> bool {
3799            self.is_dirty
3800        }
3801
3802        fn has_conflict(&self, _: &AppContext) -> bool {
3803            self.has_conflict
3804        }
3805
3806        fn can_save(&self, _: &AppContext) -> bool {
3807            !self.project_entry_ids.is_empty()
3808        }
3809
3810        fn save(
3811            &mut self,
3812            _: ModelHandle<Project>,
3813            _: &mut ViewContext<Self>,
3814        ) -> Task<anyhow::Result<()>> {
3815            self.save_count += 1;
3816            self.is_dirty = false;
3817            Task::ready(Ok(()))
3818        }
3819
3820        fn save_as(
3821            &mut self,
3822            _: ModelHandle<Project>,
3823            _: std::path::PathBuf,
3824            _: &mut ViewContext<Self>,
3825        ) -> Task<anyhow::Result<()>> {
3826            self.save_as_count += 1;
3827            self.is_dirty = false;
3828            Task::ready(Ok(()))
3829        }
3830
3831        fn reload(
3832            &mut self,
3833            _: ModelHandle<Project>,
3834            _: &mut ViewContext<Self>,
3835        ) -> Task<anyhow::Result<()>> {
3836            self.reload_count += 1;
3837            self.is_dirty = false;
3838            Task::ready(Ok(()))
3839        }
3840
3841        fn to_item_events(_: &Self::Event) -> Vec<ItemEvent> {
3842            vec![ItemEvent::UpdateTab, ItemEvent::Edit]
3843        }
3844    }
3845
3846    impl SidebarItem for TestItem {}
3847}