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