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        let _ = self
1535            .project
1536            .update(cx, |project, cx| project.remove_worktree(*worktree_id, cx));
1537    }
1538
1539    fn project_path_for_path(
1540        &self,
1541        abs_path: &Path,
1542        visible: bool,
1543        cx: &mut ViewContext<Self>,
1544    ) -> Task<Result<(ModelHandle<Worktree>, ProjectPath)>> {
1545        let entry = self.project().update(cx, |project, cx| {
1546            project.find_or_create_local_worktree(abs_path, visible, cx)
1547        });
1548        cx.spawn(|_, cx| async move {
1549            let (worktree, path) = entry.await?;
1550            let worktree_id = worktree.read_with(&cx, |t, _| t.id());
1551            Ok((
1552                worktree,
1553                ProjectPath {
1554                    worktree_id,
1555                    path: path.into(),
1556                },
1557            ))
1558        })
1559    }
1560
1561    /// Returns the modal that was toggled closed if it was open.
1562    pub fn toggle_modal<V, F>(
1563        &mut self,
1564        cx: &mut ViewContext<Self>,
1565        add_view: F,
1566    ) -> Option<ViewHandle<V>>
1567    where
1568        V: 'static + View,
1569        F: FnOnce(&mut Self, &mut ViewContext<Self>) -> ViewHandle<V>,
1570    {
1571        cx.notify();
1572        // Whatever modal was visible is getting clobbered. If its the same type as V, then return
1573        // it. Otherwise, create a new modal and set it as active.
1574        let already_open_modal = self.modal.take().and_then(|modal| modal.downcast::<V>());
1575        if let Some(already_open_modal) = already_open_modal {
1576            cx.focus_self();
1577            Some(already_open_modal)
1578        } else {
1579            let modal = add_view(self, cx);
1580            cx.focus(&modal);
1581            self.modal = Some(modal.into());
1582            None
1583        }
1584    }
1585
1586    pub fn modal<V: 'static + View>(&self) -> Option<ViewHandle<V>> {
1587        self.modal
1588            .as_ref()
1589            .and_then(|modal| modal.clone().downcast::<V>())
1590    }
1591
1592    pub fn dismiss_modal(&mut self, cx: &mut ViewContext<Self>) {
1593        if self.modal.take().is_some() {
1594            cx.focus(&self.active_pane);
1595            cx.notify();
1596        }
1597    }
1598
1599    pub fn show_notification<V: Notification>(
1600        &mut self,
1601        id: usize,
1602        cx: &mut ViewContext<Self>,
1603        build_notification: impl FnOnce(&mut ViewContext<Self>) -> ViewHandle<V>,
1604    ) {
1605        let type_id = TypeId::of::<V>();
1606        if self
1607            .notifications
1608            .iter()
1609            .all(|(existing_type_id, existing_id, _)| {
1610                (*existing_type_id, *existing_id) != (type_id, id)
1611            })
1612        {
1613            let notification = build_notification(cx);
1614            cx.subscribe(&notification, move |this, handle, event, cx| {
1615                if handle.read(cx).should_dismiss_notification_on_event(event) {
1616                    this.dismiss_notification(type_id, id, cx);
1617                }
1618            })
1619            .detach();
1620            self.notifications
1621                .push((type_id, id, Box::new(notification)));
1622            cx.notify();
1623        }
1624    }
1625
1626    fn dismiss_notification(&mut self, type_id: TypeId, id: usize, cx: &mut ViewContext<Self>) {
1627        self.notifications
1628            .retain(|(existing_type_id, existing_id, _)| {
1629                if (*existing_type_id, *existing_id) == (type_id, id) {
1630                    cx.notify();
1631                    false
1632                } else {
1633                    true
1634                }
1635            });
1636    }
1637
1638    pub fn items<'a>(
1639        &'a self,
1640        cx: &'a AppContext,
1641    ) -> impl 'a + Iterator<Item = &Box<dyn ItemHandle>> {
1642        self.panes.iter().flat_map(|pane| pane.read(cx).items())
1643    }
1644
1645    pub fn item_of_type<T: Item>(&self, cx: &AppContext) -> Option<ViewHandle<T>> {
1646        self.items_of_type(cx).max_by_key(|item| item.id())
1647    }
1648
1649    pub fn items_of_type<'a, T: Item>(
1650        &'a self,
1651        cx: &'a AppContext,
1652    ) -> impl 'a + Iterator<Item = ViewHandle<T>> {
1653        self.panes
1654            .iter()
1655            .flat_map(|pane| pane.read(cx).items_of_type())
1656    }
1657
1658    pub fn active_item(&self, cx: &AppContext) -> Option<Box<dyn ItemHandle>> {
1659        self.active_pane().read(cx).active_item()
1660    }
1661
1662    fn active_project_path(&self, cx: &ViewContext<Self>) -> Option<ProjectPath> {
1663        self.active_item(cx).and_then(|item| item.project_path(cx))
1664    }
1665
1666    pub fn save_active_item(
1667        &mut self,
1668        force_name_change: bool,
1669        cx: &mut ViewContext<Self>,
1670    ) -> Task<Result<()>> {
1671        let project = self.project.clone();
1672        if let Some(item) = self.active_item(cx) {
1673            if !force_name_change && item.can_save(cx) {
1674                if item.has_conflict(cx.as_ref()) {
1675                    const CONFLICT_MESSAGE: &str = "This file has changed on disk since you started editing it. Do you want to overwrite it?";
1676
1677                    let mut answer = cx.prompt(
1678                        PromptLevel::Warning,
1679                        CONFLICT_MESSAGE,
1680                        &["Overwrite", "Cancel"],
1681                    );
1682                    cx.spawn(|_, mut cx| async move {
1683                        let answer = answer.recv().await;
1684                        if answer == Some(0) {
1685                            cx.update(|cx| item.save(project, cx)).await?;
1686                        }
1687                        Ok(())
1688                    })
1689                } else {
1690                    item.save(project, cx)
1691                }
1692            } else if item.is_singleton(cx) {
1693                let worktree = self.worktrees(cx).next();
1694                let start_abs_path = worktree
1695                    .and_then(|w| w.read(cx).as_local())
1696                    .map_or(Path::new(""), |w| w.abs_path())
1697                    .to_path_buf();
1698                let mut abs_path = cx.prompt_for_new_path(&start_abs_path);
1699                cx.spawn(|_, mut cx| async move {
1700                    if let Some(abs_path) = abs_path.recv().await.flatten() {
1701                        cx.update(|cx| item.save_as(project, abs_path, cx)).await?;
1702                    }
1703                    Ok(())
1704                })
1705            } else {
1706                Task::ready(Ok(()))
1707            }
1708        } else {
1709            Task::ready(Ok(()))
1710        }
1711    }
1712
1713    pub fn toggle_sidebar(&mut self, sidebar_side: SidebarSide, cx: &mut ViewContext<Self>) {
1714        let sidebar = match sidebar_side {
1715            SidebarSide::Left => &mut self.left_sidebar,
1716            SidebarSide::Right => &mut self.right_sidebar,
1717        };
1718        let open = sidebar.update(cx, |sidebar, cx| {
1719            let open = !sidebar.is_open();
1720            sidebar.set_open(open, cx);
1721            open
1722        });
1723
1724        if open {
1725            Dock::hide_on_sidebar_shown(self, sidebar_side, cx);
1726        }
1727
1728        cx.focus_self();
1729        cx.notify();
1730    }
1731
1732    pub fn toggle_sidebar_item(&mut self, action: &ToggleSidebarItem, cx: &mut ViewContext<Self>) {
1733        let sidebar = match action.sidebar_side {
1734            SidebarSide::Left => &mut self.left_sidebar,
1735            SidebarSide::Right => &mut self.right_sidebar,
1736        };
1737        let active_item = sidebar.update(cx, move |sidebar, cx| {
1738            if sidebar.is_open() && sidebar.active_item_ix() == action.item_index {
1739                sidebar.set_open(false, cx);
1740                None
1741            } else {
1742                sidebar.set_open(true, cx);
1743                sidebar.activate_item(action.item_index, cx);
1744                sidebar.active_item().cloned()
1745            }
1746        });
1747
1748        if let Some(active_item) = active_item {
1749            Dock::hide_on_sidebar_shown(self, action.sidebar_side, cx);
1750
1751            if active_item.is_focused(cx) {
1752                cx.focus_self();
1753            } else {
1754                cx.focus(active_item.to_any());
1755            }
1756        } else {
1757            cx.focus_self();
1758        }
1759        cx.notify();
1760    }
1761
1762    pub fn toggle_sidebar_item_focus(
1763        &mut self,
1764        sidebar_side: SidebarSide,
1765        item_index: usize,
1766        cx: &mut ViewContext<Self>,
1767    ) {
1768        let sidebar = match sidebar_side {
1769            SidebarSide::Left => &mut self.left_sidebar,
1770            SidebarSide::Right => &mut self.right_sidebar,
1771        };
1772        let active_item = sidebar.update(cx, |sidebar, cx| {
1773            sidebar.set_open(true, cx);
1774            sidebar.activate_item(item_index, cx);
1775            sidebar.active_item().cloned()
1776        });
1777        if let Some(active_item) = active_item {
1778            Dock::hide_on_sidebar_shown(self, sidebar_side, cx);
1779
1780            if active_item.is_focused(cx) {
1781                cx.focus_self();
1782            } else {
1783                cx.focus(active_item.to_any());
1784            }
1785        }
1786        cx.notify();
1787    }
1788
1789    pub fn focus_center(&mut self, _: &menu::Cancel, cx: &mut ViewContext<Self>) {
1790        cx.focus_self();
1791        cx.notify();
1792    }
1793
1794    fn add_pane(&mut self, cx: &mut ViewContext<Self>) -> ViewHandle<Pane> {
1795        let pane = cx.add_view(|cx| Pane::new(None, cx));
1796        let pane_id = pane.id();
1797        cx.subscribe(&pane, move |this, _, event, cx| {
1798            this.handle_pane_event(pane_id, event, cx)
1799        })
1800        .detach();
1801        self.panes.push(pane.clone());
1802        cx.focus(pane.clone());
1803        cx.emit(Event::PaneAdded(pane.clone()));
1804        pane
1805    }
1806
1807    pub fn add_item(&mut self, item: Box<dyn ItemHandle>, cx: &mut ViewContext<Self>) {
1808        let active_pane = self.active_pane().clone();
1809        Pane::add_item(self, &active_pane, item, true, true, None, cx);
1810    }
1811
1812    pub fn open_path(
1813        &mut self,
1814        path: impl Into<ProjectPath>,
1815        pane: Option<WeakViewHandle<Pane>>,
1816        focus_item: bool,
1817        cx: &mut ViewContext<Self>,
1818    ) -> Task<Result<Box<dyn ItemHandle>, anyhow::Error>> {
1819        let pane = pane.unwrap_or_else(|| self.active_pane().downgrade());
1820        let task = self.load_path(path.into(), cx);
1821        cx.spawn(|this, mut cx| async move {
1822            let (project_entry_id, build_item) = task.await?;
1823            let pane = pane
1824                .upgrade(&cx)
1825                .ok_or_else(|| anyhow!("pane was closed"))?;
1826            this.update(&mut cx, |this, cx| {
1827                Ok(Pane::open_item(
1828                    this,
1829                    pane,
1830                    project_entry_id,
1831                    focus_item,
1832                    cx,
1833                    build_item,
1834                ))
1835            })
1836        })
1837    }
1838
1839    pub(crate) fn load_path(
1840        &mut self,
1841        path: ProjectPath,
1842        cx: &mut ViewContext<Self>,
1843    ) -> Task<
1844        Result<(
1845            ProjectEntryId,
1846            impl 'static + FnOnce(&mut ViewContext<Pane>) -> Box<dyn ItemHandle>,
1847        )>,
1848    > {
1849        let project = self.project().clone();
1850        let project_item = project.update(cx, |project, cx| project.open_path(path, cx));
1851        cx.as_mut().spawn(|mut cx| async move {
1852            let (project_entry_id, project_item) = project_item.await?;
1853            let build_item = cx.update(|cx| {
1854                cx.default_global::<ProjectItemBuilders>()
1855                    .get(&project_item.model_type())
1856                    .ok_or_else(|| anyhow!("no item builder for project item"))
1857                    .cloned()
1858            })?;
1859            let build_item =
1860                move |cx: &mut ViewContext<Pane>| build_item(project, project_item, cx);
1861            Ok((project_entry_id, build_item))
1862        })
1863    }
1864
1865    pub fn open_project_item<T>(
1866        &mut self,
1867        project_item: ModelHandle<T::Item>,
1868        cx: &mut ViewContext<Self>,
1869    ) -> ViewHandle<T>
1870    where
1871        T: ProjectItem,
1872    {
1873        use project::Item as _;
1874
1875        let entry_id = project_item.read(cx).entry_id(cx);
1876        if let Some(item) = entry_id
1877            .and_then(|entry_id| self.active_pane().read(cx).item_for_entry(entry_id, cx))
1878            .and_then(|item| item.downcast())
1879        {
1880            self.activate_item(&item, cx);
1881            return item;
1882        }
1883
1884        let item = cx.add_view(|cx| T::for_project_item(self.project().clone(), project_item, cx));
1885        self.add_item(Box::new(item.clone()), cx);
1886        item
1887    }
1888
1889    pub fn open_shared_screen(&mut self, action: &OpenSharedScreen, cx: &mut ViewContext<Self>) {
1890        if let Some(shared_screen) =
1891            self.shared_screen_for_peer(action.peer_id, &self.active_pane, cx)
1892        {
1893            let pane = self.active_pane.clone();
1894            Pane::add_item(self, &pane, Box::new(shared_screen), false, true, None, cx);
1895        }
1896    }
1897
1898    pub fn activate_item(&mut self, item: &dyn ItemHandle, cx: &mut ViewContext<Self>) -> bool {
1899        let result = self.panes.iter().find_map(|pane| {
1900            pane.read(cx)
1901                .index_for_item(item)
1902                .map(|ix| (pane.clone(), ix))
1903        });
1904        if let Some((pane, ix)) = result {
1905            pane.update(cx, |pane, cx| pane.activate_item(ix, true, true, cx));
1906            true
1907        } else {
1908            false
1909        }
1910    }
1911
1912    fn activate_pane_at_index(&mut self, action: &ActivatePane, cx: &mut ViewContext<Self>) {
1913        let panes = self.center.panes();
1914        if let Some(pane) = panes.get(action.0).map(|p| (*p).clone()) {
1915            cx.focus(pane);
1916        } else {
1917            self.split_pane(self.active_pane.clone(), SplitDirection::Right, cx);
1918        }
1919    }
1920
1921    pub fn activate_next_pane(&mut self, cx: &mut ViewContext<Self>) {
1922        let next_pane = {
1923            let panes = self.center.panes();
1924            let ix = panes
1925                .iter()
1926                .position(|pane| **pane == self.active_pane)
1927                .unwrap();
1928            let next_ix = (ix + 1) % panes.len();
1929            panes[next_ix].clone()
1930        };
1931        cx.focus(next_pane);
1932    }
1933
1934    pub fn activate_previous_pane(&mut self, cx: &mut ViewContext<Self>) {
1935        let prev_pane = {
1936            let panes = self.center.panes();
1937            let ix = panes
1938                .iter()
1939                .position(|pane| **pane == self.active_pane)
1940                .unwrap();
1941            let prev_ix = if ix == 0 { panes.len() - 1 } else { ix - 1 };
1942            panes[prev_ix].clone()
1943        };
1944        cx.focus(prev_pane);
1945    }
1946
1947    fn handle_pane_focused(&mut self, pane: ViewHandle<Pane>, cx: &mut ViewContext<Self>) {
1948        if self.active_pane != pane {
1949            self.active_pane
1950                .update(cx, |pane, cx| pane.set_active(false, cx));
1951            self.active_pane = pane.clone();
1952            self.active_pane
1953                .update(cx, |pane, cx| pane.set_active(true, cx));
1954            self.status_bar.update(cx, |status_bar, cx| {
1955                status_bar.set_active_pane(&self.active_pane, cx);
1956            });
1957            self.active_item_path_changed(cx);
1958
1959            if &pane == self.dock_pane() {
1960                Dock::show(self, cx);
1961            } else {
1962                self.last_active_center_pane = Some(pane.downgrade());
1963                if self.dock.is_anchored_at(DockAnchor::Expanded) {
1964                    Dock::hide(self, cx);
1965                }
1966            }
1967            cx.notify();
1968        }
1969
1970        self.update_followers(
1971            proto::update_followers::Variant::UpdateActiveView(proto::UpdateActiveView {
1972                id: self.active_item(cx).map(|item| item.id() as u64),
1973                leader_id: self.leader_for_pane(&pane).map(|id| id.0),
1974            }),
1975            cx,
1976        );
1977    }
1978
1979    fn handle_pane_event(
1980        &mut self,
1981        pane_id: usize,
1982        event: &pane::Event,
1983        cx: &mut ViewContext<Self>,
1984    ) {
1985        if let Some(pane) = self.pane(pane_id) {
1986            let is_dock = &pane == self.dock.pane();
1987            match event {
1988                pane::Event::Split(direction) if !is_dock => {
1989                    self.split_pane(pane, *direction, cx);
1990                }
1991                pane::Event::Remove if !is_dock => self.remove_pane(pane, cx),
1992                pane::Event::Remove if is_dock => Dock::hide(self, cx),
1993                pane::Event::ActivateItem { local } => {
1994                    if *local {
1995                        self.unfollow(&pane, cx);
1996                    }
1997                    if &pane == self.active_pane() {
1998                        self.active_item_path_changed(cx);
1999                    }
2000                }
2001                pane::Event::ChangeItemTitle => {
2002                    if pane == self.active_pane {
2003                        self.active_item_path_changed(cx);
2004                    }
2005                    self.update_window_edited(cx);
2006                }
2007                pane::Event::RemoveItem { item_id } => {
2008                    self.update_window_edited(cx);
2009                    if let hash_map::Entry::Occupied(entry) = self.panes_by_item.entry(*item_id) {
2010                        if entry.get().id() == pane.id() {
2011                            entry.remove();
2012                        }
2013                    }
2014                }
2015                _ => {}
2016            }
2017        } else if self.dock.visible_pane().is_none() {
2018            error!("pane {} not found", pane_id);
2019        }
2020    }
2021
2022    pub fn split_pane(
2023        &mut self,
2024        pane: ViewHandle<Pane>,
2025        direction: SplitDirection,
2026        cx: &mut ViewContext<Self>,
2027    ) -> Option<ViewHandle<Pane>> {
2028        if &pane == self.dock_pane() {
2029            warn!("Can't split dock pane.");
2030            return None;
2031        }
2032
2033        pane.read(cx).active_item().map(|item| {
2034            let new_pane = self.add_pane(cx);
2035            if let Some(clone) = item.clone_on_split(cx.as_mut()) {
2036                Pane::add_item(self, &new_pane, clone, true, true, None, cx);
2037            }
2038            self.center.split(&pane, &new_pane, direction).unwrap();
2039            cx.notify();
2040            new_pane
2041        })
2042    }
2043
2044    pub fn split_pane_with_item(
2045        &mut self,
2046        from: WeakViewHandle<Pane>,
2047        pane_to_split: WeakViewHandle<Pane>,
2048        item_id_to_move: usize,
2049        split_direction: SplitDirection,
2050        cx: &mut ViewContext<Self>,
2051    ) {
2052        if let Some((pane_to_split, from)) = pane_to_split.upgrade(cx).zip(from.upgrade(cx)) {
2053            if &pane_to_split == self.dock_pane() {
2054                warn!("Can't split dock pane.");
2055                return;
2056            }
2057
2058            let new_pane = self.add_pane(cx);
2059            Pane::move_item(self, from.clone(), new_pane.clone(), item_id_to_move, 0, cx);
2060            self.center
2061                .split(&pane_to_split, &new_pane, split_direction)
2062                .unwrap();
2063            cx.notify();
2064        }
2065    }
2066
2067    fn remove_pane(&mut self, pane: ViewHandle<Pane>, cx: &mut ViewContext<Self>) {
2068        if self.center.remove(&pane).unwrap() {
2069            self.panes.retain(|p| p != &pane);
2070            cx.focus(self.panes.last().unwrap().clone());
2071            self.unfollow(&pane, cx);
2072            self.last_leaders_by_pane.remove(&pane.downgrade());
2073            for removed_item in pane.read(cx).items() {
2074                self.panes_by_item.remove(&removed_item.id());
2075            }
2076            if self.last_active_center_pane == Some(pane.downgrade()) {
2077                self.last_active_center_pane = None;
2078            }
2079
2080            cx.notify();
2081        } else {
2082            self.active_item_path_changed(cx);
2083        }
2084    }
2085
2086    pub fn panes(&self) -> &[ViewHandle<Pane>] {
2087        &self.panes
2088    }
2089
2090    fn pane(&self, pane_id: usize) -> Option<ViewHandle<Pane>> {
2091        self.panes.iter().find(|pane| pane.id() == pane_id).cloned()
2092    }
2093
2094    pub fn active_pane(&self) -> &ViewHandle<Pane> {
2095        &self.active_pane
2096    }
2097
2098    pub fn dock_pane(&self) -> &ViewHandle<Pane> {
2099        self.dock.pane()
2100    }
2101
2102    fn project_remote_id_changed(&mut self, remote_id: Option<u64>, cx: &mut ViewContext<Self>) {
2103        if let Some(remote_id) = remote_id {
2104            self.remote_entity_subscription =
2105                Some(self.client.add_view_for_remote_entity(remote_id, cx));
2106        } else {
2107            self.remote_entity_subscription.take();
2108        }
2109    }
2110
2111    fn collaborator_left(&mut self, peer_id: PeerId, cx: &mut ViewContext<Self>) {
2112        self.leader_state.followers.remove(&peer_id);
2113        if let Some(states_by_pane) = self.follower_states_by_leader.remove(&peer_id) {
2114            for state in states_by_pane.into_values() {
2115                for item in state.items_by_leader_view_id.into_values() {
2116                    if let FollowerItem::Loaded(item) = item {
2117                        item.set_leader_replica_id(None, cx);
2118                    }
2119                }
2120            }
2121        }
2122        cx.notify();
2123    }
2124
2125    pub fn toggle_follow(
2126        &mut self,
2127        ToggleFollow(leader_id): &ToggleFollow,
2128        cx: &mut ViewContext<Self>,
2129    ) -> Option<Task<Result<()>>> {
2130        let leader_id = *leader_id;
2131        let pane = self.active_pane().clone();
2132
2133        if let Some(prev_leader_id) = self.unfollow(&pane, cx) {
2134            if leader_id == prev_leader_id {
2135                return None;
2136            }
2137        }
2138
2139        self.last_leaders_by_pane
2140            .insert(pane.downgrade(), leader_id);
2141        self.follower_states_by_leader
2142            .entry(leader_id)
2143            .or_default()
2144            .insert(pane.clone(), Default::default());
2145        cx.notify();
2146
2147        let project_id = self.project.read(cx).remote_id()?;
2148        let request = self.client.request(proto::Follow {
2149            project_id,
2150            leader_id: leader_id.0,
2151        });
2152        Some(cx.spawn_weak(|this, mut cx| async move {
2153            let response = request.await?;
2154            if let Some(this) = this.upgrade(&cx) {
2155                this.update(&mut cx, |this, _| {
2156                    let state = this
2157                        .follower_states_by_leader
2158                        .get_mut(&leader_id)
2159                        .and_then(|states_by_pane| states_by_pane.get_mut(&pane))
2160                        .ok_or_else(|| anyhow!("following interrupted"))?;
2161                    state.active_view_id = response.active_view_id;
2162                    Ok::<_, anyhow::Error>(())
2163                })?;
2164                Self::add_views_from_leader(this, leader_id, vec![pane], response.views, &mut cx)
2165                    .await?;
2166            }
2167            Ok(())
2168        }))
2169    }
2170
2171    pub fn follow_next_collaborator(
2172        &mut self,
2173        _: &FollowNextCollaborator,
2174        cx: &mut ViewContext<Self>,
2175    ) -> Option<Task<Result<()>>> {
2176        let collaborators = self.project.read(cx).collaborators();
2177        let next_leader_id = if let Some(leader_id) = self.leader_for_pane(&self.active_pane) {
2178            let mut collaborators = collaborators.keys().copied();
2179            for peer_id in collaborators.by_ref() {
2180                if peer_id == leader_id {
2181                    break;
2182                }
2183            }
2184            collaborators.next()
2185        } else if let Some(last_leader_id) =
2186            self.last_leaders_by_pane.get(&self.active_pane.downgrade())
2187        {
2188            if collaborators.contains_key(last_leader_id) {
2189                Some(*last_leader_id)
2190            } else {
2191                None
2192            }
2193        } else {
2194            None
2195        };
2196
2197        next_leader_id
2198            .or_else(|| collaborators.keys().copied().next())
2199            .and_then(|leader_id| self.toggle_follow(&ToggleFollow(leader_id), cx))
2200    }
2201
2202    pub fn unfollow(
2203        &mut self,
2204        pane: &ViewHandle<Pane>,
2205        cx: &mut ViewContext<Self>,
2206    ) -> Option<PeerId> {
2207        for (leader_id, states_by_pane) in &mut self.follower_states_by_leader {
2208            let leader_id = *leader_id;
2209            if let Some(state) = states_by_pane.remove(pane) {
2210                for (_, item) in state.items_by_leader_view_id {
2211                    if let FollowerItem::Loaded(item) = item {
2212                        item.set_leader_replica_id(None, cx);
2213                    }
2214                }
2215
2216                if states_by_pane.is_empty() {
2217                    self.follower_states_by_leader.remove(&leader_id);
2218                    if let Some(project_id) = self.project.read(cx).remote_id() {
2219                        self.client
2220                            .send(proto::Unfollow {
2221                                project_id,
2222                                leader_id: leader_id.0,
2223                            })
2224                            .log_err();
2225                    }
2226                }
2227
2228                cx.notify();
2229                return Some(leader_id);
2230            }
2231        }
2232        None
2233    }
2234
2235    pub fn is_following(&self, peer_id: PeerId) -> bool {
2236        self.follower_states_by_leader.contains_key(&peer_id)
2237    }
2238
2239    fn render_titlebar(&self, theme: &Theme, cx: &mut RenderContext<Self>) -> ElementBox {
2240        let project = &self.project.read(cx);
2241        let mut worktree_root_names = String::new();
2242        for (i, name) in project.worktree_root_names(cx).enumerate() {
2243            if i > 0 {
2244                worktree_root_names.push_str(", ");
2245            }
2246            worktree_root_names.push_str(name);
2247        }
2248
2249        // TODO: There should be a better system in place for this
2250        // (https://github.com/zed-industries/zed/issues/1290)
2251        let is_fullscreen = cx.window_is_fullscreen(cx.window_id());
2252        let container_theme = if is_fullscreen {
2253            let mut container_theme = theme.workspace.titlebar.container;
2254            container_theme.padding.left = container_theme.padding.right;
2255            container_theme
2256        } else {
2257            theme.workspace.titlebar.container
2258        };
2259
2260        enum TitleBar {}
2261        ConstrainedBox::new(
2262            MouseEventHandler::<TitleBar>::new(0, cx, |_, cx| {
2263                Container::new(
2264                    Stack::new()
2265                        .with_child(
2266                            Label::new(worktree_root_names, theme.workspace.titlebar.title.clone())
2267                                .aligned()
2268                                .left()
2269                                .boxed(),
2270                        )
2271                        .with_children(
2272                            self.titlebar_item
2273                                .as_ref()
2274                                .map(|item| ChildView::new(item, cx).aligned().right().boxed()),
2275                        )
2276                        .boxed(),
2277                )
2278                .with_style(container_theme)
2279                .boxed()
2280            })
2281            .on_click(MouseButton::Left, |event, cx| {
2282                if event.click_count == 2 {
2283                    cx.zoom_window(cx.window_id());
2284                }
2285            })
2286            .boxed(),
2287        )
2288        .with_height(theme.workspace.titlebar.height)
2289        .named("titlebar")
2290    }
2291
2292    fn active_item_path_changed(&mut self, cx: &mut ViewContext<Self>) {
2293        let active_entry = self.active_project_path(cx);
2294        self.project
2295            .update(cx, |project, cx| project.set_active_path(active_entry, cx));
2296        self.update_window_title(cx);
2297    }
2298
2299    fn update_window_title(&mut self, cx: &mut ViewContext<Self>) {
2300        let mut title = String::new();
2301        let project = self.project().read(cx);
2302        if let Some(path) = self.active_item(cx).and_then(|item| item.project_path(cx)) {
2303            let filename = path
2304                .path
2305                .file_name()
2306                .map(|s| s.to_string_lossy())
2307                .or_else(|| {
2308                    Some(Cow::Borrowed(
2309                        project
2310                            .worktree_for_id(path.worktree_id, cx)?
2311                            .read(cx)
2312                            .root_name(),
2313                    ))
2314                });
2315            if let Some(filename) = filename {
2316                title.push_str(filename.as_ref());
2317                title.push_str("");
2318            }
2319        }
2320        for (i, name) in project.worktree_root_names(cx).enumerate() {
2321            if i > 0 {
2322                title.push_str(", ");
2323            }
2324            title.push_str(name);
2325        }
2326        if title.is_empty() {
2327            title = "empty project".to_string();
2328        }
2329        cx.set_window_title(&title);
2330    }
2331
2332    fn update_window_edited(&mut self, cx: &mut ViewContext<Self>) {
2333        let is_edited = !self.project.read(cx).is_read_only()
2334            && self
2335                .items(cx)
2336                .any(|item| item.has_conflict(cx) || item.is_dirty(cx));
2337        if is_edited != self.window_edited {
2338            self.window_edited = is_edited;
2339            cx.set_window_edited(self.window_edited)
2340        }
2341    }
2342
2343    fn render_disconnected_overlay(&self, cx: &mut RenderContext<Workspace>) -> Option<ElementBox> {
2344        if self.project.read(cx).is_read_only() {
2345            enum DisconnectedOverlay {}
2346            Some(
2347                MouseEventHandler::<DisconnectedOverlay>::new(0, cx, |_, cx| {
2348                    let theme = &cx.global::<Settings>().theme;
2349                    Label::new(
2350                        "Your connection to the remote project has been lost.".to_string(),
2351                        theme.workspace.disconnected_overlay.text.clone(),
2352                    )
2353                    .aligned()
2354                    .contained()
2355                    .with_style(theme.workspace.disconnected_overlay.container)
2356                    .boxed()
2357                })
2358                .with_cursor_style(CursorStyle::Arrow)
2359                .capture_all()
2360                .boxed(),
2361            )
2362        } else {
2363            None
2364        }
2365    }
2366
2367    fn render_notifications(
2368        &self,
2369        theme: &theme::Workspace,
2370        cx: &AppContext,
2371    ) -> Option<ElementBox> {
2372        if self.notifications.is_empty() {
2373            None
2374        } else {
2375            Some(
2376                Flex::column()
2377                    .with_children(self.notifications.iter().map(|(_, _, notification)| {
2378                        ChildView::new(notification.as_ref(), cx)
2379                            .contained()
2380                            .with_style(theme.notification)
2381                            .boxed()
2382                    }))
2383                    .constrained()
2384                    .with_width(theme.notifications.width)
2385                    .contained()
2386                    .with_style(theme.notifications.container)
2387                    .aligned()
2388                    .bottom()
2389                    .right()
2390                    .boxed(),
2391            )
2392        }
2393    }
2394
2395    // RPC handlers
2396
2397    async fn handle_follow(
2398        this: ViewHandle<Self>,
2399        envelope: TypedEnvelope<proto::Follow>,
2400        _: Arc<Client>,
2401        mut cx: AsyncAppContext,
2402    ) -> Result<proto::FollowResponse> {
2403        this.update(&mut cx, |this, cx| {
2404            this.leader_state
2405                .followers
2406                .insert(envelope.original_sender_id()?);
2407
2408            let active_view_id = this
2409                .active_item(cx)
2410                .and_then(|i| i.to_followable_item_handle(cx))
2411                .map(|i| i.id() as u64);
2412            Ok(proto::FollowResponse {
2413                active_view_id,
2414                views: this
2415                    .panes()
2416                    .iter()
2417                    .flat_map(|pane| {
2418                        let leader_id = this.leader_for_pane(pane).map(|id| id.0);
2419                        pane.read(cx).items().filter_map({
2420                            let cx = &cx;
2421                            move |item| {
2422                                let id = item.id() as u64;
2423                                let item = item.to_followable_item_handle(cx)?;
2424                                let variant = item.to_state_proto(cx)?;
2425                                Some(proto::View {
2426                                    id,
2427                                    leader_id,
2428                                    variant: Some(variant),
2429                                })
2430                            }
2431                        })
2432                    })
2433                    .collect(),
2434            })
2435        })
2436    }
2437
2438    async fn handle_unfollow(
2439        this: ViewHandle<Self>,
2440        envelope: TypedEnvelope<proto::Unfollow>,
2441        _: Arc<Client>,
2442        mut cx: AsyncAppContext,
2443    ) -> Result<()> {
2444        this.update(&mut cx, |this, _| {
2445            this.leader_state
2446                .followers
2447                .remove(&envelope.original_sender_id()?);
2448            Ok(())
2449        })
2450    }
2451
2452    async fn handle_update_followers(
2453        this: ViewHandle<Self>,
2454        envelope: TypedEnvelope<proto::UpdateFollowers>,
2455        _: Arc<Client>,
2456        mut cx: AsyncAppContext,
2457    ) -> Result<()> {
2458        let leader_id = envelope.original_sender_id()?;
2459        match envelope
2460            .payload
2461            .variant
2462            .ok_or_else(|| anyhow!("invalid update"))?
2463        {
2464            proto::update_followers::Variant::UpdateActiveView(update_active_view) => {
2465                this.update(&mut cx, |this, cx| {
2466                    this.update_leader_state(leader_id, cx, |state, _| {
2467                        state.active_view_id = update_active_view.id;
2468                    });
2469                    Ok::<_, anyhow::Error>(())
2470                })
2471            }
2472            proto::update_followers::Variant::UpdateView(update_view) => {
2473                this.update(&mut cx, |this, cx| {
2474                    let variant = update_view
2475                        .variant
2476                        .ok_or_else(|| anyhow!("missing update view variant"))?;
2477                    this.update_leader_state(leader_id, cx, |state, cx| {
2478                        let variant = variant.clone();
2479                        match state
2480                            .items_by_leader_view_id
2481                            .entry(update_view.id)
2482                            .or_insert(FollowerItem::Loading(Vec::new()))
2483                        {
2484                            FollowerItem::Loaded(item) => {
2485                                item.apply_update_proto(variant, cx).log_err();
2486                            }
2487                            FollowerItem::Loading(updates) => updates.push(variant),
2488                        }
2489                    });
2490                    Ok(())
2491                })
2492            }
2493            proto::update_followers::Variant::CreateView(view) => {
2494                let panes = this.read_with(&cx, |this, _| {
2495                    this.follower_states_by_leader
2496                        .get(&leader_id)
2497                        .into_iter()
2498                        .flat_map(|states_by_pane| states_by_pane.keys())
2499                        .cloned()
2500                        .collect()
2501                });
2502                Self::add_views_from_leader(this.clone(), leader_id, panes, vec![view], &mut cx)
2503                    .await?;
2504                Ok(())
2505            }
2506        }
2507        .log_err();
2508
2509        Ok(())
2510    }
2511
2512    async fn add_views_from_leader(
2513        this: ViewHandle<Self>,
2514        leader_id: PeerId,
2515        panes: Vec<ViewHandle<Pane>>,
2516        views: Vec<proto::View>,
2517        cx: &mut AsyncAppContext,
2518    ) -> Result<()> {
2519        let project = this.read_with(cx, |this, _| this.project.clone());
2520        let replica_id = project
2521            .read_with(cx, |project, _| {
2522                project
2523                    .collaborators()
2524                    .get(&leader_id)
2525                    .map(|c| c.replica_id)
2526            })
2527            .ok_or_else(|| anyhow!("no such collaborator {}", leader_id))?;
2528
2529        let item_builders = cx.update(|cx| {
2530            cx.default_global::<FollowableItemBuilders>()
2531                .values()
2532                .map(|b| b.0)
2533                .collect::<Vec<_>>()
2534        });
2535
2536        let mut item_tasks_by_pane = HashMap::default();
2537        for pane in panes {
2538            let mut item_tasks = Vec::new();
2539            let mut leader_view_ids = Vec::new();
2540            for view in &views {
2541                let mut variant = view.variant.clone();
2542                if variant.is_none() {
2543                    Err(anyhow!("missing variant"))?;
2544                }
2545                for build_item in &item_builders {
2546                    let task =
2547                        cx.update(|cx| build_item(pane.clone(), project.clone(), &mut variant, cx));
2548                    if let Some(task) = task {
2549                        item_tasks.push(task);
2550                        leader_view_ids.push(view.id);
2551                        break;
2552                    } else {
2553                        assert!(variant.is_some());
2554                    }
2555                }
2556            }
2557
2558            item_tasks_by_pane.insert(pane, (item_tasks, leader_view_ids));
2559        }
2560
2561        for (pane, (item_tasks, leader_view_ids)) in item_tasks_by_pane {
2562            let items = futures::future::try_join_all(item_tasks).await?;
2563            this.update(cx, |this, cx| {
2564                let state = this
2565                    .follower_states_by_leader
2566                    .get_mut(&leader_id)?
2567                    .get_mut(&pane)?;
2568
2569                for (id, item) in leader_view_ids.into_iter().zip(items) {
2570                    item.set_leader_replica_id(Some(replica_id), cx);
2571                    match state.items_by_leader_view_id.entry(id) {
2572                        hash_map::Entry::Occupied(e) => {
2573                            let e = e.into_mut();
2574                            if let FollowerItem::Loading(updates) = e {
2575                                for update in updates.drain(..) {
2576                                    item.apply_update_proto(update, cx)
2577                                        .context("failed to apply view update")
2578                                        .log_err();
2579                                }
2580                            }
2581                            *e = FollowerItem::Loaded(item);
2582                        }
2583                        hash_map::Entry::Vacant(e) => {
2584                            e.insert(FollowerItem::Loaded(item));
2585                        }
2586                    }
2587                }
2588
2589                Some(())
2590            });
2591        }
2592        this.update(cx, |this, cx| this.leader_updated(leader_id, cx));
2593
2594        Ok(())
2595    }
2596
2597    fn update_followers(
2598        &self,
2599        update: proto::update_followers::Variant,
2600        cx: &AppContext,
2601    ) -> Option<()> {
2602        let project_id = self.project.read(cx).remote_id()?;
2603        if !self.leader_state.followers.is_empty() {
2604            self.client
2605                .send(proto::UpdateFollowers {
2606                    project_id,
2607                    follower_ids: self.leader_state.followers.iter().map(|f| f.0).collect(),
2608                    variant: Some(update),
2609                })
2610                .log_err();
2611        }
2612        None
2613    }
2614
2615    pub fn leader_for_pane(&self, pane: &ViewHandle<Pane>) -> Option<PeerId> {
2616        self.follower_states_by_leader
2617            .iter()
2618            .find_map(|(leader_id, state)| {
2619                if state.contains_key(pane) {
2620                    Some(*leader_id)
2621                } else {
2622                    None
2623                }
2624            })
2625    }
2626
2627    fn update_leader_state(
2628        &mut self,
2629        leader_id: PeerId,
2630        cx: &mut ViewContext<Self>,
2631        mut update_fn: impl FnMut(&mut FollowerState, &mut ViewContext<Self>),
2632    ) {
2633        for (_, state) in self
2634            .follower_states_by_leader
2635            .get_mut(&leader_id)
2636            .into_iter()
2637            .flatten()
2638        {
2639            update_fn(state, cx);
2640        }
2641        self.leader_updated(leader_id, cx);
2642    }
2643
2644    fn leader_updated(&mut self, leader_id: PeerId, cx: &mut ViewContext<Self>) -> Option<()> {
2645        cx.notify();
2646
2647        let call = self.active_call()?;
2648        let room = call.read(cx).room()?.read(cx);
2649        let participant = room.remote_participants().get(&leader_id)?;
2650
2651        let mut items_to_add = Vec::new();
2652        match participant.location {
2653            call::ParticipantLocation::SharedProject { project_id } => {
2654                if Some(project_id) == self.project.read(cx).remote_id() {
2655                    for (pane, state) in self.follower_states_by_leader.get(&leader_id)? {
2656                        if let Some(FollowerItem::Loaded(item)) = state
2657                            .active_view_id
2658                            .and_then(|id| state.items_by_leader_view_id.get(&id))
2659                        {
2660                            items_to_add.push((pane.clone(), item.boxed_clone()));
2661                        }
2662                    }
2663                }
2664            }
2665            call::ParticipantLocation::UnsharedProject => {}
2666            call::ParticipantLocation::External => {
2667                for (pane, _) in self.follower_states_by_leader.get(&leader_id)? {
2668                    if let Some(shared_screen) = self.shared_screen_for_peer(leader_id, pane, cx) {
2669                        items_to_add.push((pane.clone(), Box::new(shared_screen)));
2670                    }
2671                }
2672            }
2673        }
2674
2675        for (pane, item) in items_to_add {
2676            Pane::add_item(self, &pane, item.boxed_clone(), false, false, None, cx);
2677            if pane == self.active_pane {
2678                pane.update(cx, |pane, cx| pane.focus_active_item(cx));
2679            }
2680        }
2681
2682        None
2683    }
2684
2685    fn shared_screen_for_peer(
2686        &self,
2687        peer_id: PeerId,
2688        pane: &ViewHandle<Pane>,
2689        cx: &mut ViewContext<Self>,
2690    ) -> Option<ViewHandle<SharedScreen>> {
2691        let call = self.active_call()?;
2692        let room = call.read(cx).room()?.read(cx);
2693        let participant = room.remote_participants().get(&peer_id)?;
2694        let track = participant.tracks.values().next()?.clone();
2695        let user = participant.user.clone();
2696
2697        for item in pane.read(cx).items_of_type::<SharedScreen>() {
2698            if item.read(cx).peer_id == peer_id {
2699                return Some(item);
2700            }
2701        }
2702
2703        Some(cx.add_view(|cx| SharedScreen::new(&track, peer_id, user.clone(), cx)))
2704    }
2705
2706    pub fn on_window_activation_changed(&mut self, active: bool, cx: &mut ViewContext<Self>) {
2707        if !active {
2708            for pane in &self.panes {
2709                pane.update(cx, |pane, cx| {
2710                    if let Some(item) = pane.active_item() {
2711                        item.workspace_deactivated(cx);
2712                    }
2713                    if matches!(
2714                        cx.global::<Settings>().autosave,
2715                        Autosave::OnWindowChange | Autosave::OnFocusChange
2716                    ) {
2717                        for item in pane.items() {
2718                            Pane::autosave_item(item.as_ref(), self.project.clone(), cx)
2719                                .detach_and_log_err(cx);
2720                        }
2721                    }
2722                });
2723            }
2724        }
2725    }
2726
2727    fn active_call(&self) -> Option<&ModelHandle<ActiveCall>> {
2728        self.active_call.as_ref().map(|(call, _)| call)
2729    }
2730
2731    fn on_active_call_event(
2732        &mut self,
2733        _: ModelHandle<ActiveCall>,
2734        event: &call::room::Event,
2735        cx: &mut ViewContext<Self>,
2736    ) {
2737        match event {
2738            call::room::Event::ParticipantLocationChanged { participant_id }
2739            | call::room::Event::RemoteVideoTracksChanged { participant_id } => {
2740                self.leader_updated(*participant_id, cx);
2741            }
2742            _ => {}
2743        }
2744    }
2745}
2746
2747impl Entity for Workspace {
2748    type Event = Event;
2749}
2750
2751impl View for Workspace {
2752    fn ui_name() -> &'static str {
2753        "Workspace"
2754    }
2755
2756    fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
2757        let theme = cx.global::<Settings>().theme.clone();
2758        Stack::new()
2759            .with_child(
2760                Flex::column()
2761                    .with_child(self.render_titlebar(&theme, cx))
2762                    .with_child(
2763                        Stack::new()
2764                            .with_child({
2765                                let project = self.project.clone();
2766                                Flex::row()
2767                                    .with_children(
2768                                        if self.left_sidebar.read(cx).active_item().is_some() {
2769                                            Some(
2770                                                ChildView::new(&self.left_sidebar, cx)
2771                                                    .flex(0.8, false)
2772                                                    .boxed(),
2773                                            )
2774                                        } else {
2775                                            None
2776                                        },
2777                                    )
2778                                    .with_child(
2779                                        FlexItem::new(
2780                                            Flex::column()
2781                                                .with_child(
2782                                                    FlexItem::new(self.center.render(
2783                                                        &project,
2784                                                        &theme,
2785                                                        &self.follower_states_by_leader,
2786                                                        self.active_call(),
2787                                                        cx,
2788                                                    ))
2789                                                    .flex(1., true)
2790                                                    .boxed(),
2791                                                )
2792                                                .with_children(self.dock.render(
2793                                                    &theme,
2794                                                    DockAnchor::Bottom,
2795                                                    cx,
2796                                                ))
2797                                                .boxed(),
2798                                        )
2799                                        .flex(1., true)
2800                                        .boxed(),
2801                                    )
2802                                    .with_children(self.dock.render(&theme, DockAnchor::Right, cx))
2803                                    .with_children(
2804                                        if self.right_sidebar.read(cx).active_item().is_some() {
2805                                            Some(
2806                                                ChildView::new(&self.right_sidebar, cx)
2807                                                    .flex(0.8, false)
2808                                                    .boxed(),
2809                                            )
2810                                        } else {
2811                                            None
2812                                        },
2813                                    )
2814                                    .boxed()
2815                            })
2816                            .with_child(
2817                                Overlay::new(
2818                                    Stack::new()
2819                                        .with_children(self.dock.render(
2820                                            &theme,
2821                                            DockAnchor::Expanded,
2822                                            cx,
2823                                        ))
2824                                        .with_children(self.modal.as_ref().map(|modal| {
2825                                            ChildView::new(modal, cx)
2826                                                .contained()
2827                                                .with_style(theme.workspace.modal)
2828                                                .aligned()
2829                                                .top()
2830                                                .boxed()
2831                                        }))
2832                                        .with_children(
2833                                            self.render_notifications(&theme.workspace, cx),
2834                                        )
2835                                        .boxed(),
2836                                )
2837                                .boxed(),
2838                            )
2839                            .flex(1.0, true)
2840                            .boxed(),
2841                    )
2842                    .with_child(ChildView::new(&self.status_bar, cx).boxed())
2843                    .contained()
2844                    .with_background_color(theme.workspace.background)
2845                    .boxed(),
2846            )
2847            .with_children(DragAndDrop::render(cx))
2848            .with_children(self.render_disconnected_overlay(cx))
2849            .named("workspace")
2850    }
2851
2852    fn focus_in(&mut self, view: AnyViewHandle, cx: &mut ViewContext<Self>) {
2853        if cx.is_self_focused() {
2854            cx.focus(&self.active_pane);
2855        } else {
2856            for pane in self.panes() {
2857                let view = view.clone();
2858                if pane.update(cx, |_, cx| cx.is_child(view)) {
2859                    self.handle_pane_focused(pane.clone(), cx);
2860                    break;
2861                }
2862            }
2863        }
2864    }
2865
2866    fn keymap_context(&self, _: &AppContext) -> gpui::keymap::Context {
2867        let mut keymap = Self::default_keymap_context();
2868        if self.active_pane() == self.dock_pane() {
2869            keymap.set.insert("Dock".into());
2870        }
2871        keymap
2872    }
2873}
2874
2875pub trait WorkspaceHandle {
2876    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath>;
2877}
2878
2879impl WorkspaceHandle for ViewHandle<Workspace> {
2880    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath> {
2881        self.read(cx)
2882            .worktrees(cx)
2883            .flat_map(|worktree| {
2884                let worktree_id = worktree.read(cx).id();
2885                worktree.read(cx).files(true, 0).map(move |f| ProjectPath {
2886                    worktree_id,
2887                    path: f.path.clone(),
2888                })
2889            })
2890            .collect::<Vec<_>>()
2891    }
2892}
2893
2894impl std::fmt::Debug for OpenPaths {
2895    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2896        f.debug_struct("OpenPaths")
2897            .field("paths", &self.paths)
2898            .finish()
2899    }
2900}
2901
2902fn open(_: &Open, cx: &mut MutableAppContext) {
2903    let mut paths = cx.prompt_for_paths(PathPromptOptions {
2904        files: true,
2905        directories: true,
2906        multiple: true,
2907    });
2908    cx.spawn(|mut cx| async move {
2909        if let Some(paths) = paths.recv().await.flatten() {
2910            cx.update(|cx| cx.dispatch_global_action(OpenPaths { paths }));
2911        }
2912    })
2913    .detach();
2914}
2915
2916pub struct WorkspaceCreated(WeakViewHandle<Workspace>);
2917
2918pub fn activate_workspace_for_project(
2919    cx: &mut MutableAppContext,
2920    predicate: impl Fn(&mut Project, &mut ModelContext<Project>) -> bool,
2921) -> Option<ViewHandle<Workspace>> {
2922    for window_id in cx.window_ids().collect::<Vec<_>>() {
2923        if let Some(workspace_handle) = cx.root_view::<Workspace>(window_id) {
2924            let project = workspace_handle.read(cx).project.clone();
2925            if project.update(cx, &predicate) {
2926                cx.activate_window(window_id);
2927                return Some(workspace_handle);
2928            }
2929        }
2930    }
2931    None
2932}
2933
2934#[allow(clippy::type_complexity)]
2935pub fn open_paths(
2936    abs_paths: &[PathBuf],
2937    app_state: &Arc<AppState>,
2938    cx: &mut MutableAppContext,
2939) -> Task<(
2940    ViewHandle<Workspace>,
2941    Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
2942)> {
2943    log::info!("open paths {:?}", abs_paths);
2944
2945    // Open paths in existing workspace if possible
2946    let existing =
2947        activate_workspace_for_project(cx, |project, cx| project.contains_paths(abs_paths, cx));
2948
2949    let app_state = app_state.clone();
2950    let abs_paths = abs_paths.to_vec();
2951    cx.spawn(|mut cx| async move {
2952        let mut new_project = None;
2953        let workspace = if let Some(existing) = existing {
2954            existing
2955        } else {
2956            let contains_directory =
2957                futures::future::join_all(abs_paths.iter().map(|path| app_state.fs.is_file(path)))
2958                    .await
2959                    .contains(&false);
2960
2961            cx.add_window((app_state.build_window_options)(), |cx| {
2962                let project = Project::local(
2963                    app_state.client.clone(),
2964                    app_state.user_store.clone(),
2965                    app_state.languages.clone(),
2966                    app_state.fs.clone(),
2967                    cx,
2968                );
2969                new_project = Some(project.clone());
2970                let mut workspace = Workspace::new(project, app_state.default_item_factory, cx);
2971                (app_state.initialize_workspace)(&mut workspace, &app_state, cx);
2972                if contains_directory {
2973                    workspace.toggle_sidebar(SidebarSide::Left, cx);
2974                }
2975                workspace
2976            })
2977            .1
2978        };
2979
2980        let items = workspace
2981            .update(&mut cx, |workspace, cx| {
2982                workspace.open_paths(abs_paths, true, cx)
2983            })
2984            .await;
2985
2986        (workspace, items)
2987    })
2988}
2989
2990fn open_new(app_state: &Arc<AppState>, cx: &mut MutableAppContext) {
2991    let (window_id, workspace) = cx.add_window((app_state.build_window_options)(), |cx| {
2992        let mut workspace = Workspace::new(
2993            Project::local(
2994                app_state.client.clone(),
2995                app_state.user_store.clone(),
2996                app_state.languages.clone(),
2997                app_state.fs.clone(),
2998                cx,
2999            ),
3000            app_state.default_item_factory,
3001            cx,
3002        );
3003        (app_state.initialize_workspace)(&mut workspace, app_state, cx);
3004        workspace
3005    });
3006    cx.dispatch_action_at(window_id, workspace.id(), NewFile);
3007}
3008
3009#[cfg(test)]
3010mod tests {
3011    use std::cell::Cell;
3012
3013    use crate::sidebar::SidebarItem;
3014
3015    use super::*;
3016    use fs::FakeFs;
3017    use gpui::{executor::Deterministic, ModelHandle, TestAppContext, ViewContext};
3018    use project::{Project, ProjectEntryId};
3019    use serde_json::json;
3020
3021    pub fn default_item_factory(
3022        _workspace: &mut Workspace,
3023        _cx: &mut ViewContext<Workspace>,
3024    ) -> Box<dyn ItemHandle> {
3025        unimplemented!();
3026    }
3027
3028    #[gpui::test]
3029    async fn test_tab_disambiguation(cx: &mut TestAppContext) {
3030        cx.foreground().forbid_parking();
3031        Settings::test_async(cx);
3032
3033        let fs = FakeFs::new(cx.background());
3034        let project = Project::test(fs, [], cx).await;
3035        let (_, workspace) =
3036            cx.add_window(|cx| Workspace::new(project.clone(), default_item_factory, cx));
3037
3038        // Adding an item with no ambiguity renders the tab without detail.
3039        let item1 = cx.add_view(&workspace, |_| {
3040            let mut item = TestItem::new();
3041            item.tab_descriptions = Some(vec!["c", "b1/c", "a/b1/c"]);
3042            item
3043        });
3044        workspace.update(cx, |workspace, cx| {
3045            workspace.add_item(Box::new(item1.clone()), cx);
3046        });
3047        item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), None));
3048
3049        // Adding an item that creates ambiguity increases the level of detail on
3050        // both tabs.
3051        let item2 = cx.add_view(&workspace, |_| {
3052            let mut item = TestItem::new();
3053            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
3054            item
3055        });
3056        workspace.update(cx, |workspace, cx| {
3057            workspace.add_item(Box::new(item2.clone()), cx);
3058        });
3059        item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
3060        item2.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
3061
3062        // Adding an item that creates ambiguity increases the level of detail only
3063        // on the ambiguous tabs. In this case, the ambiguity can't be resolved so
3064        // we stop at the highest detail available.
3065        let item3 = cx.add_view(&workspace, |_| {
3066            let mut item = TestItem::new();
3067            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
3068            item
3069        });
3070        workspace.update(cx, |workspace, cx| {
3071            workspace.add_item(Box::new(item3.clone()), cx);
3072        });
3073        item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
3074        item2.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
3075        item3.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
3076    }
3077
3078    #[gpui::test]
3079    async fn test_tracking_active_path(cx: &mut TestAppContext) {
3080        cx.foreground().forbid_parking();
3081        Settings::test_async(cx);
3082        let fs = FakeFs::new(cx.background());
3083        fs.insert_tree(
3084            "/root1",
3085            json!({
3086                "one.txt": "",
3087                "two.txt": "",
3088            }),
3089        )
3090        .await;
3091        fs.insert_tree(
3092            "/root2",
3093            json!({
3094                "three.txt": "",
3095            }),
3096        )
3097        .await;
3098
3099        let project = Project::test(fs, ["root1".as_ref()], cx).await;
3100        let (window_id, workspace) =
3101            cx.add_window(|cx| Workspace::new(project.clone(), default_item_factory, cx));
3102        let worktree_id = project.read_with(cx, |project, cx| {
3103            project.worktrees(cx).next().unwrap().read(cx).id()
3104        });
3105
3106        let item1 = cx.add_view(&workspace, |_| {
3107            let mut item = TestItem::new();
3108            item.project_path = Some((worktree_id, "one.txt").into());
3109            item
3110        });
3111        let item2 = cx.add_view(&workspace, |_| {
3112            let mut item = TestItem::new();
3113            item.project_path = Some((worktree_id, "two.txt").into());
3114            item
3115        });
3116
3117        // Add an item to an empty pane
3118        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item1), cx));
3119        project.read_with(cx, |project, cx| {
3120            assert_eq!(
3121                project.active_entry(),
3122                project
3123                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
3124                    .map(|e| e.id)
3125            );
3126        });
3127        assert_eq!(
3128            cx.current_window_title(window_id).as_deref(),
3129            Some("one.txt — root1")
3130        );
3131
3132        // Add a second item to a non-empty pane
3133        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item2), cx));
3134        assert_eq!(
3135            cx.current_window_title(window_id).as_deref(),
3136            Some("two.txt — root1")
3137        );
3138        project.read_with(cx, |project, cx| {
3139            assert_eq!(
3140                project.active_entry(),
3141                project
3142                    .entry_for_path(&(worktree_id, "two.txt").into(), cx)
3143                    .map(|e| e.id)
3144            );
3145        });
3146
3147        // Close the active item
3148        workspace
3149            .update(cx, |workspace, cx| {
3150                Pane::close_active_item(workspace, &Default::default(), cx).unwrap()
3151            })
3152            .await
3153            .unwrap();
3154        assert_eq!(
3155            cx.current_window_title(window_id).as_deref(),
3156            Some("one.txt — root1")
3157        );
3158        project.read_with(cx, |project, cx| {
3159            assert_eq!(
3160                project.active_entry(),
3161                project
3162                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
3163                    .map(|e| e.id)
3164            );
3165        });
3166
3167        // Add a project folder
3168        project
3169            .update(cx, |project, cx| {
3170                project.find_or_create_local_worktree("/root2", true, cx)
3171            })
3172            .await
3173            .unwrap();
3174        assert_eq!(
3175            cx.current_window_title(window_id).as_deref(),
3176            Some("one.txt — root1, root2")
3177        );
3178
3179        // Remove a project folder
3180        project.update(cx, |project, cx| {
3181            let _ = project.remove_worktree(worktree_id, cx);
3182        });
3183        assert_eq!(
3184            cx.current_window_title(window_id).as_deref(),
3185            Some("one.txt — root2")
3186        );
3187    }
3188
3189    #[gpui::test]
3190    async fn test_close_window(cx: &mut TestAppContext) {
3191        cx.foreground().forbid_parking();
3192        Settings::test_async(cx);
3193        let fs = FakeFs::new(cx.background());
3194        fs.insert_tree("/root", json!({ "one": "" })).await;
3195
3196        let project = Project::test(fs, ["root".as_ref()], cx).await;
3197        let (window_id, workspace) =
3198            cx.add_window(|cx| Workspace::new(project.clone(), default_item_factory, cx));
3199
3200        // When there are no dirty items, there's nothing to do.
3201        let item1 = cx.add_view(&workspace, |_| TestItem::new());
3202        workspace.update(cx, |w, cx| w.add_item(Box::new(item1.clone()), cx));
3203        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
3204        assert!(task.await.unwrap());
3205
3206        // When there are dirty untitled items, prompt to save each one. If the user
3207        // cancels any prompt, then abort.
3208        let item2 = cx.add_view(&workspace, |_| {
3209            let mut item = TestItem::new();
3210            item.is_dirty = true;
3211            item
3212        });
3213        let item3 = cx.add_view(&workspace, |_| {
3214            let mut item = TestItem::new();
3215            item.is_dirty = true;
3216            item.project_entry_ids = vec![ProjectEntryId::from_proto(1)];
3217            item
3218        });
3219        workspace.update(cx, |w, cx| {
3220            w.add_item(Box::new(item2.clone()), cx);
3221            w.add_item(Box::new(item3.clone()), cx);
3222        });
3223        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
3224        cx.foreground().run_until_parked();
3225        cx.simulate_prompt_answer(window_id, 2 /* cancel */);
3226        cx.foreground().run_until_parked();
3227        assert!(!cx.has_pending_prompt(window_id));
3228        assert!(!task.await.unwrap());
3229    }
3230
3231    #[gpui::test]
3232    async fn test_close_pane_items(cx: &mut TestAppContext) {
3233        cx.foreground().forbid_parking();
3234        Settings::test_async(cx);
3235        let fs = FakeFs::new(cx.background());
3236
3237        let project = Project::test(fs, None, cx).await;
3238        let (window_id, workspace) =
3239            cx.add_window(|cx| Workspace::new(project, default_item_factory, cx));
3240
3241        let item1 = cx.add_view(&workspace, |_| {
3242            let mut item = TestItem::new();
3243            item.is_dirty = true;
3244            item.project_entry_ids = vec![ProjectEntryId::from_proto(1)];
3245            item
3246        });
3247        let item2 = cx.add_view(&workspace, |_| {
3248            let mut item = TestItem::new();
3249            item.is_dirty = true;
3250            item.has_conflict = true;
3251            item.project_entry_ids = vec![ProjectEntryId::from_proto(2)];
3252            item
3253        });
3254        let item3 = cx.add_view(&workspace, |_| {
3255            let mut item = TestItem::new();
3256            item.is_dirty = true;
3257            item.has_conflict = true;
3258            item.project_entry_ids = vec![ProjectEntryId::from_proto(3)];
3259            item
3260        });
3261        let item4 = cx.add_view(&workspace, |_| {
3262            let mut item = TestItem::new();
3263            item.is_dirty = true;
3264            item
3265        });
3266        let pane = workspace.update(cx, |workspace, cx| {
3267            workspace.add_item(Box::new(item1.clone()), cx);
3268            workspace.add_item(Box::new(item2.clone()), cx);
3269            workspace.add_item(Box::new(item3.clone()), cx);
3270            workspace.add_item(Box::new(item4.clone()), cx);
3271            workspace.active_pane().clone()
3272        });
3273
3274        let close_items = workspace.update(cx, |workspace, cx| {
3275            pane.update(cx, |pane, cx| {
3276                pane.activate_item(1, true, true, cx);
3277                assert_eq!(pane.active_item().unwrap().id(), item2.id());
3278            });
3279
3280            let item1_id = item1.id();
3281            let item3_id = item3.id();
3282            let item4_id = item4.id();
3283            Pane::close_items(workspace, pane.clone(), cx, move |id| {
3284                [item1_id, item3_id, item4_id].contains(&id)
3285            })
3286        });
3287
3288        cx.foreground().run_until_parked();
3289        pane.read_with(cx, |pane, _| {
3290            assert_eq!(pane.items_len(), 4);
3291            assert_eq!(pane.active_item().unwrap().id(), item1.id());
3292        });
3293
3294        cx.simulate_prompt_answer(window_id, 0);
3295        cx.foreground().run_until_parked();
3296        pane.read_with(cx, |pane, cx| {
3297            assert_eq!(item1.read(cx).save_count, 1);
3298            assert_eq!(item1.read(cx).save_as_count, 0);
3299            assert_eq!(item1.read(cx).reload_count, 0);
3300            assert_eq!(pane.items_len(), 3);
3301            assert_eq!(pane.active_item().unwrap().id(), item3.id());
3302        });
3303
3304        cx.simulate_prompt_answer(window_id, 1);
3305        cx.foreground().run_until_parked();
3306        pane.read_with(cx, |pane, cx| {
3307            assert_eq!(item3.read(cx).save_count, 0);
3308            assert_eq!(item3.read(cx).save_as_count, 0);
3309            assert_eq!(item3.read(cx).reload_count, 1);
3310            assert_eq!(pane.items_len(), 2);
3311            assert_eq!(pane.active_item().unwrap().id(), item4.id());
3312        });
3313
3314        cx.simulate_prompt_answer(window_id, 0);
3315        cx.foreground().run_until_parked();
3316        cx.simulate_new_path_selection(|_| Some(Default::default()));
3317        close_items.await.unwrap();
3318        pane.read_with(cx, |pane, cx| {
3319            assert_eq!(item4.read(cx).save_count, 0);
3320            assert_eq!(item4.read(cx).save_as_count, 1);
3321            assert_eq!(item4.read(cx).reload_count, 0);
3322            assert_eq!(pane.items_len(), 1);
3323            assert_eq!(pane.active_item().unwrap().id(), item2.id());
3324        });
3325    }
3326
3327    #[gpui::test]
3328    async fn test_prompting_to_save_only_on_last_item_for_entry(cx: &mut TestAppContext) {
3329        cx.foreground().forbid_parking();
3330        Settings::test_async(cx);
3331        let fs = FakeFs::new(cx.background());
3332
3333        let project = Project::test(fs, [], cx).await;
3334        let (window_id, workspace) =
3335            cx.add_window(|cx| Workspace::new(project, default_item_factory, cx));
3336
3337        // Create several workspace items with single project entries, and two
3338        // workspace items with multiple project entries.
3339        let single_entry_items = (0..=4)
3340            .map(|project_entry_id| {
3341                let mut item = TestItem::new();
3342                item.is_dirty = true;
3343                item.project_entry_ids = vec![ProjectEntryId::from_proto(project_entry_id)];
3344                item.is_singleton = true;
3345                item
3346            })
3347            .collect::<Vec<_>>();
3348        let item_2_3 = {
3349            let mut item = TestItem::new();
3350            item.is_dirty = true;
3351            item.is_singleton = false;
3352            item.project_entry_ids =
3353                vec![ProjectEntryId::from_proto(2), ProjectEntryId::from_proto(3)];
3354            item
3355        };
3356        let item_3_4 = {
3357            let mut item = TestItem::new();
3358            item.is_dirty = true;
3359            item.is_singleton = false;
3360            item.project_entry_ids =
3361                vec![ProjectEntryId::from_proto(3), ProjectEntryId::from_proto(4)];
3362            item
3363        };
3364
3365        // Create two panes that contain the following project entries:
3366        //   left pane:
3367        //     multi-entry items:   (2, 3)
3368        //     single-entry items:  0, 1, 2, 3, 4
3369        //   right pane:
3370        //     single-entry items:  1
3371        //     multi-entry items:   (3, 4)
3372        let left_pane = workspace.update(cx, |workspace, cx| {
3373            let left_pane = workspace.active_pane().clone();
3374            workspace.add_item(Box::new(cx.add_view(|_| item_2_3.clone())), cx);
3375            for item in &single_entry_items {
3376                workspace.add_item(Box::new(cx.add_view(|_| item.clone())), cx);
3377            }
3378            left_pane.update(cx, |pane, cx| {
3379                pane.activate_item(2, true, true, cx);
3380            });
3381
3382            workspace
3383                .split_pane(left_pane.clone(), SplitDirection::Right, cx)
3384                .unwrap();
3385
3386            left_pane
3387        });
3388
3389        //Need to cause an effect flush in order to respect new focus
3390        workspace.update(cx, |workspace, cx| {
3391            workspace.add_item(Box::new(cx.add_view(|_| item_3_4.clone())), cx);
3392            cx.focus(left_pane.clone());
3393        });
3394
3395        // When closing all of the items in the left pane, we should be prompted twice:
3396        // once for project entry 0, and once for project entry 2. After those two
3397        // prompts, the task should complete.
3398
3399        let close = workspace.update(cx, |workspace, cx| {
3400            Pane::close_items(workspace, left_pane.clone(), cx, |_| true)
3401        });
3402
3403        cx.foreground().run_until_parked();
3404        left_pane.read_with(cx, |pane, cx| {
3405            assert_eq!(
3406                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
3407                &[ProjectEntryId::from_proto(0)]
3408            );
3409        });
3410        cx.simulate_prompt_answer(window_id, 0);
3411
3412        cx.foreground().run_until_parked();
3413        left_pane.read_with(cx, |pane, cx| {
3414            assert_eq!(
3415                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
3416                &[ProjectEntryId::from_proto(2)]
3417            );
3418        });
3419        cx.simulate_prompt_answer(window_id, 0);
3420
3421        cx.foreground().run_until_parked();
3422        close.await.unwrap();
3423        left_pane.read_with(cx, |pane, _| {
3424            assert_eq!(pane.items_len(), 0);
3425        });
3426    }
3427
3428    #[gpui::test]
3429    async fn test_autosave(deterministic: Arc<Deterministic>, cx: &mut gpui::TestAppContext) {
3430        deterministic.forbid_parking();
3431
3432        Settings::test_async(cx);
3433        let fs = FakeFs::new(cx.background());
3434
3435        let project = Project::test(fs, [], cx).await;
3436        let (window_id, workspace) =
3437            cx.add_window(|cx| Workspace::new(project, default_item_factory, cx));
3438
3439        let item = cx.add_view(&workspace, |_| {
3440            let mut item = TestItem::new();
3441            item.project_entry_ids = vec![ProjectEntryId::from_proto(1)];
3442            item
3443        });
3444        let item_id = item.id();
3445        workspace.update(cx, |workspace, cx| {
3446            workspace.add_item(Box::new(item.clone()), cx);
3447        });
3448
3449        // Autosave on window change.
3450        item.update(cx, |item, cx| {
3451            cx.update_global(|settings: &mut Settings, _| {
3452                settings.autosave = Autosave::OnWindowChange;
3453            });
3454            item.is_dirty = true;
3455        });
3456
3457        // Deactivating the window saves the file.
3458        cx.simulate_window_activation(None);
3459        deterministic.run_until_parked();
3460        item.read_with(cx, |item, _| assert_eq!(item.save_count, 1));
3461
3462        // Autosave on focus change.
3463        item.update(cx, |item, cx| {
3464            cx.focus_self();
3465            cx.update_global(|settings: &mut Settings, _| {
3466                settings.autosave = Autosave::OnFocusChange;
3467            });
3468            item.is_dirty = true;
3469        });
3470
3471        // Blurring the item saves the file.
3472        item.update(cx, |_, cx| cx.blur());
3473        deterministic.run_until_parked();
3474        item.read_with(cx, |item, _| assert_eq!(item.save_count, 2));
3475
3476        // Deactivating the window still saves the file.
3477        cx.simulate_window_activation(Some(window_id));
3478        item.update(cx, |item, cx| {
3479            cx.focus_self();
3480            item.is_dirty = true;
3481        });
3482        cx.simulate_window_activation(None);
3483
3484        deterministic.run_until_parked();
3485        item.read_with(cx, |item, _| assert_eq!(item.save_count, 3));
3486
3487        // Autosave after delay.
3488        item.update(cx, |item, cx| {
3489            cx.update_global(|settings: &mut Settings, _| {
3490                settings.autosave = Autosave::AfterDelay { milliseconds: 500 };
3491            });
3492            item.is_dirty = true;
3493            cx.emit(TestItemEvent::Edit);
3494        });
3495
3496        // Delay hasn't fully expired, so the file is still dirty and unsaved.
3497        deterministic.advance_clock(Duration::from_millis(250));
3498        item.read_with(cx, |item, _| assert_eq!(item.save_count, 3));
3499
3500        // After delay expires, the file is saved.
3501        deterministic.advance_clock(Duration::from_millis(250));
3502        item.read_with(cx, |item, _| assert_eq!(item.save_count, 4));
3503
3504        // Autosave on focus change, ensuring closing the tab counts as such.
3505        item.update(cx, |item, cx| {
3506            cx.update_global(|settings: &mut Settings, _| {
3507                settings.autosave = Autosave::OnFocusChange;
3508            });
3509            item.is_dirty = true;
3510        });
3511
3512        workspace
3513            .update(cx, |workspace, cx| {
3514                let pane = workspace.active_pane().clone();
3515                Pane::close_items(workspace, pane, cx, move |id| id == item_id)
3516            })
3517            .await
3518            .unwrap();
3519        assert!(!cx.has_pending_prompt(window_id));
3520        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
3521
3522        // Add the item again, ensuring autosave is prevented if the underlying file has been deleted.
3523        workspace.update(cx, |workspace, cx| {
3524            workspace.add_item(Box::new(item.clone()), cx);
3525        });
3526        item.update(cx, |item, cx| {
3527            item.project_entry_ids = Default::default();
3528            item.is_dirty = true;
3529            cx.blur();
3530        });
3531        deterministic.run_until_parked();
3532        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
3533
3534        // Ensure autosave is prevented for deleted files also when closing the buffer.
3535        let _close_items = workspace.update(cx, |workspace, cx| {
3536            let pane = workspace.active_pane().clone();
3537            Pane::close_items(workspace, pane, cx, move |id| id == item_id)
3538        });
3539        deterministic.run_until_parked();
3540        assert!(cx.has_pending_prompt(window_id));
3541        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
3542    }
3543
3544    #[gpui::test]
3545    async fn test_pane_navigation(
3546        deterministic: Arc<Deterministic>,
3547        cx: &mut gpui::TestAppContext,
3548    ) {
3549        deterministic.forbid_parking();
3550        Settings::test_async(cx);
3551        let fs = FakeFs::new(cx.background());
3552
3553        let project = Project::test(fs, [], cx).await;
3554        let (_, workspace) = cx.add_window(|cx| Workspace::new(project, default_item_factory, cx));
3555
3556        let item = cx.add_view(&workspace, |_| {
3557            let mut item = TestItem::new();
3558            item.project_entry_ids = vec![ProjectEntryId::from_proto(1)];
3559            item
3560        });
3561        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
3562        let toolbar = pane.read_with(cx, |pane, _| pane.toolbar().clone());
3563        let toolbar_notify_count = Rc::new(RefCell::new(0));
3564
3565        workspace.update(cx, |workspace, cx| {
3566            workspace.add_item(Box::new(item.clone()), cx);
3567            let toolbar_notification_count = toolbar_notify_count.clone();
3568            cx.observe(&toolbar, move |_, _, _| {
3569                *toolbar_notification_count.borrow_mut() += 1
3570            })
3571            .detach();
3572        });
3573
3574        pane.read_with(cx, |pane, _| {
3575            assert!(!pane.can_navigate_backward());
3576            assert!(!pane.can_navigate_forward());
3577        });
3578
3579        item.update(cx, |item, cx| {
3580            item.set_state("one".to_string(), cx);
3581        });
3582
3583        // Toolbar must be notified to re-render the navigation buttons
3584        assert_eq!(*toolbar_notify_count.borrow(), 1);
3585
3586        pane.read_with(cx, |pane, _| {
3587            assert!(pane.can_navigate_backward());
3588            assert!(!pane.can_navigate_forward());
3589        });
3590
3591        workspace
3592            .update(cx, |workspace, cx| {
3593                Pane::go_back(workspace, Some(pane.clone()), cx)
3594            })
3595            .await;
3596
3597        assert_eq!(*toolbar_notify_count.borrow(), 3);
3598        pane.read_with(cx, |pane, _| {
3599            assert!(!pane.can_navigate_backward());
3600            assert!(pane.can_navigate_forward());
3601        });
3602    }
3603
3604    pub struct TestItem {
3605        state: String,
3606        pub label: String,
3607        save_count: usize,
3608        save_as_count: usize,
3609        reload_count: usize,
3610        is_dirty: bool,
3611        is_singleton: bool,
3612        has_conflict: bool,
3613        project_entry_ids: Vec<ProjectEntryId>,
3614        project_path: Option<ProjectPath>,
3615        nav_history: Option<ItemNavHistory>,
3616        tab_descriptions: Option<Vec<&'static str>>,
3617        tab_detail: Cell<Option<usize>>,
3618    }
3619
3620    pub enum TestItemEvent {
3621        Edit,
3622    }
3623
3624    impl Clone for TestItem {
3625        fn clone(&self) -> Self {
3626            Self {
3627                state: self.state.clone(),
3628                label: self.label.clone(),
3629                save_count: self.save_count,
3630                save_as_count: self.save_as_count,
3631                reload_count: self.reload_count,
3632                is_dirty: self.is_dirty,
3633                is_singleton: self.is_singleton,
3634                has_conflict: self.has_conflict,
3635                project_entry_ids: self.project_entry_ids.clone(),
3636                project_path: self.project_path.clone(),
3637                nav_history: None,
3638                tab_descriptions: None,
3639                tab_detail: Default::default(),
3640            }
3641        }
3642    }
3643
3644    impl TestItem {
3645        pub fn new() -> Self {
3646            Self {
3647                state: String::new(),
3648                label: String::new(),
3649                save_count: 0,
3650                save_as_count: 0,
3651                reload_count: 0,
3652                is_dirty: false,
3653                has_conflict: false,
3654                project_entry_ids: Vec::new(),
3655                project_path: None,
3656                is_singleton: true,
3657                nav_history: None,
3658                tab_descriptions: None,
3659                tab_detail: Default::default(),
3660            }
3661        }
3662
3663        pub fn with_label(mut self, state: &str) -> Self {
3664            self.label = state.to_string();
3665            self
3666        }
3667
3668        pub fn with_singleton(mut self, singleton: bool) -> Self {
3669            self.is_singleton = singleton;
3670            self
3671        }
3672
3673        pub fn with_project_entry_ids(mut self, project_entry_ids: &[u64]) -> Self {
3674            self.project_entry_ids.extend(
3675                project_entry_ids
3676                    .iter()
3677                    .copied()
3678                    .map(ProjectEntryId::from_proto),
3679            );
3680            self
3681        }
3682
3683        fn set_state(&mut self, state: String, cx: &mut ViewContext<Self>) {
3684            self.push_to_nav_history(cx);
3685            self.state = state;
3686        }
3687
3688        fn push_to_nav_history(&mut self, cx: &mut ViewContext<Self>) {
3689            if let Some(history) = &mut self.nav_history {
3690                history.push(Some(Box::new(self.state.clone())), cx);
3691            }
3692        }
3693    }
3694
3695    impl Entity for TestItem {
3696        type Event = TestItemEvent;
3697    }
3698
3699    impl View for TestItem {
3700        fn ui_name() -> &'static str {
3701            "TestItem"
3702        }
3703
3704        fn render(&mut self, _: &mut RenderContext<Self>) -> ElementBox {
3705            Empty::new().boxed()
3706        }
3707    }
3708
3709    impl Item for TestItem {
3710        fn tab_description<'a>(&'a self, detail: usize, _: &'a AppContext) -> Option<Cow<'a, str>> {
3711            self.tab_descriptions.as_ref().and_then(|descriptions| {
3712                let description = *descriptions.get(detail).or_else(|| descriptions.last())?;
3713                Some(description.into())
3714            })
3715        }
3716
3717        fn tab_content(&self, detail: Option<usize>, _: &theme::Tab, _: &AppContext) -> ElementBox {
3718            self.tab_detail.set(detail);
3719            Empty::new().boxed()
3720        }
3721
3722        fn project_path(&self, _: &AppContext) -> Option<ProjectPath> {
3723            self.project_path.clone()
3724        }
3725
3726        fn project_entry_ids(&self, _: &AppContext) -> SmallVec<[ProjectEntryId; 3]> {
3727            self.project_entry_ids.iter().copied().collect()
3728        }
3729
3730        fn is_singleton(&self, _: &AppContext) -> bool {
3731            self.is_singleton
3732        }
3733
3734        fn set_nav_history(&mut self, history: ItemNavHistory, _: &mut ViewContext<Self>) {
3735            self.nav_history = Some(history);
3736        }
3737
3738        fn navigate(&mut self, state: Box<dyn Any>, _: &mut ViewContext<Self>) -> bool {
3739            let state = *state.downcast::<String>().unwrap_or_default();
3740            if state != self.state {
3741                self.state = state;
3742                true
3743            } else {
3744                false
3745            }
3746        }
3747
3748        fn deactivated(&mut self, cx: &mut ViewContext<Self>) {
3749            self.push_to_nav_history(cx);
3750        }
3751
3752        fn clone_on_split(&self, _: &mut ViewContext<Self>) -> Option<Self>
3753        where
3754            Self: Sized,
3755        {
3756            Some(self.clone())
3757        }
3758
3759        fn is_dirty(&self, _: &AppContext) -> bool {
3760            self.is_dirty
3761        }
3762
3763        fn has_conflict(&self, _: &AppContext) -> bool {
3764            self.has_conflict
3765        }
3766
3767        fn can_save(&self, _: &AppContext) -> bool {
3768            !self.project_entry_ids.is_empty()
3769        }
3770
3771        fn save(
3772            &mut self,
3773            _: ModelHandle<Project>,
3774            _: &mut ViewContext<Self>,
3775        ) -> Task<anyhow::Result<()>> {
3776            self.save_count += 1;
3777            self.is_dirty = false;
3778            Task::ready(Ok(()))
3779        }
3780
3781        fn save_as(
3782            &mut self,
3783            _: ModelHandle<Project>,
3784            _: std::path::PathBuf,
3785            _: &mut ViewContext<Self>,
3786        ) -> Task<anyhow::Result<()>> {
3787            self.save_as_count += 1;
3788            self.is_dirty = false;
3789            Task::ready(Ok(()))
3790        }
3791
3792        fn reload(
3793            &mut self,
3794            _: ModelHandle<Project>,
3795            _: &mut ViewContext<Self>,
3796        ) -> Task<anyhow::Result<()>> {
3797            self.reload_count += 1;
3798            self.is_dirty = false;
3799            Task::ready(Ok(()))
3800        }
3801
3802        fn to_item_events(_: &Self::Event) -> Vec<ItemEvent> {
3803            vec![ItemEvent::UpdateTab, ItemEvent::Edit]
3804        }
3805    }
3806
3807    impl SidebarItem for TestItem {}
3808}