workspace.rs

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