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                                                        cx,
2791                                                    ))
2792                                                    .flex(1., true)
2793                                                    .boxed(),
2794                                                )
2795                                                .with_children(self.dock.render(
2796                                                    &theme,
2797                                                    DockAnchor::Bottom,
2798                                                    cx,
2799                                                ))
2800                                                .boxed(),
2801                                        )
2802                                        .flex(1., true)
2803                                        .boxed(),
2804                                    )
2805                                    .with_children(self.dock.render(&theme, DockAnchor::Right, cx))
2806                                    .with_children(
2807                                        if self.right_sidebar.read(cx).active_item().is_some() {
2808                                            Some(
2809                                                ChildView::new(&self.right_sidebar, cx)
2810                                                    .flex(0.8, false)
2811                                                    .boxed(),
2812                                            )
2813                                        } else {
2814                                            None
2815                                        },
2816                                    )
2817                                    .boxed()
2818                            })
2819                            .with_child(
2820                                Overlay::new(
2821                                    Stack::new()
2822                                        .with_children(self.dock.render(
2823                                            &theme,
2824                                            DockAnchor::Expanded,
2825                                            cx,
2826                                        ))
2827                                        .with_children(self.modal.as_ref().map(|modal| {
2828                                            ChildView::new(modal, cx)
2829                                                .contained()
2830                                                .with_style(theme.workspace.modal)
2831                                                .aligned()
2832                                                .top()
2833                                                .boxed()
2834                                        }))
2835                                        .with_children(
2836                                            self.render_notifications(&theme.workspace, cx),
2837                                        )
2838                                        .boxed(),
2839                                )
2840                                .boxed(),
2841                            )
2842                            .flex(1.0, true)
2843                            .boxed(),
2844                    )
2845                    .with_child(ChildView::new(&self.status_bar, cx).boxed())
2846                    .contained()
2847                    .with_background_color(theme.workspace.background)
2848                    .boxed(),
2849            )
2850            .with_children(DragAndDrop::render(cx))
2851            .with_children(self.render_disconnected_overlay(cx))
2852            .named("workspace")
2853    }
2854
2855    fn focus_in(&mut self, view: AnyViewHandle, cx: &mut ViewContext<Self>) {
2856        if cx.is_self_focused() {
2857            cx.focus(&self.active_pane);
2858        } else {
2859            for pane in self.panes() {
2860                let view = view.clone();
2861                if pane.update(cx, |_, cx| cx.is_child(view)) {
2862                    self.handle_pane_focused(pane.clone(), cx);
2863                    break;
2864                }
2865            }
2866        }
2867    }
2868
2869    fn keymap_context(&self, _: &AppContext) -> gpui::keymap::Context {
2870        let mut keymap = Self::default_keymap_context();
2871        if self.active_pane() == self.dock_pane() {
2872            keymap.set.insert("Dock".into());
2873        }
2874        keymap
2875    }
2876}
2877
2878pub trait WorkspaceHandle {
2879    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath>;
2880}
2881
2882impl WorkspaceHandle for ViewHandle<Workspace> {
2883    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath> {
2884        self.read(cx)
2885            .worktrees(cx)
2886            .flat_map(|worktree| {
2887                let worktree_id = worktree.read(cx).id();
2888                worktree.read(cx).files(true, 0).map(move |f| ProjectPath {
2889                    worktree_id,
2890                    path: f.path.clone(),
2891                })
2892            })
2893            .collect::<Vec<_>>()
2894    }
2895}
2896
2897impl std::fmt::Debug for OpenPaths {
2898    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2899        f.debug_struct("OpenPaths")
2900            .field("paths", &self.paths)
2901            .finish()
2902    }
2903}
2904
2905fn open(_: &Open, cx: &mut MutableAppContext) {
2906    let mut paths = cx.prompt_for_paths(PathPromptOptions {
2907        files: true,
2908        directories: true,
2909        multiple: true,
2910    });
2911    cx.spawn(|mut cx| async move {
2912        if let Some(paths) = paths.recv().await.flatten() {
2913            cx.update(|cx| cx.dispatch_global_action(OpenPaths { paths }));
2914        }
2915    })
2916    .detach();
2917}
2918
2919pub struct WorkspaceCreated(WeakViewHandle<Workspace>);
2920
2921pub fn activate_workspace_for_project(
2922    cx: &mut MutableAppContext,
2923    predicate: impl Fn(&mut Project, &mut ModelContext<Project>) -> bool,
2924) -> Option<ViewHandle<Workspace>> {
2925    for window_id in cx.window_ids().collect::<Vec<_>>() {
2926        if let Some(workspace_handle) = cx.root_view::<Workspace>(window_id) {
2927            let project = workspace_handle.read(cx).project.clone();
2928            if project.update(cx, &predicate) {
2929                cx.activate_window(window_id);
2930                return Some(workspace_handle);
2931            }
2932        }
2933    }
2934    None
2935}
2936
2937#[allow(clippy::type_complexity)]
2938pub fn open_paths(
2939    abs_paths: &[PathBuf],
2940    app_state: &Arc<AppState>,
2941    cx: &mut MutableAppContext,
2942) -> Task<(
2943    ViewHandle<Workspace>,
2944    Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
2945)> {
2946    log::info!("open paths {:?}", abs_paths);
2947
2948    // Open paths in existing workspace if possible
2949    let existing =
2950        activate_workspace_for_project(cx, |project, cx| project.contains_paths(abs_paths, cx));
2951
2952    let app_state = app_state.clone();
2953    let abs_paths = abs_paths.to_vec();
2954    cx.spawn(|mut cx| async move {
2955        let mut new_project = None;
2956        let workspace = if let Some(existing) = existing {
2957            existing
2958        } else {
2959            let contains_directory =
2960                futures::future::join_all(abs_paths.iter().map(|path| app_state.fs.is_file(path)))
2961                    .await
2962                    .contains(&false);
2963
2964            cx.add_window((app_state.build_window_options)(), |cx| {
2965                let project = Project::local(
2966                    app_state.client.clone(),
2967                    app_state.user_store.clone(),
2968                    app_state.project_store.clone(),
2969                    app_state.languages.clone(),
2970                    app_state.fs.clone(),
2971                    cx,
2972                );
2973                new_project = Some(project.clone());
2974                let mut workspace = Workspace::new(project, app_state.default_item_factory, cx);
2975                (app_state.initialize_workspace)(&mut workspace, &app_state, cx);
2976                if contains_directory {
2977                    workspace.toggle_sidebar(SidebarSide::Left, cx);
2978                }
2979                workspace
2980            })
2981            .1
2982        };
2983
2984        let items = workspace
2985            .update(&mut cx, |workspace, cx| {
2986                workspace.open_paths(abs_paths, true, cx)
2987            })
2988            .await;
2989
2990        (workspace, items)
2991    })
2992}
2993
2994fn open_new(app_state: &Arc<AppState>, cx: &mut MutableAppContext) {
2995    let (window_id, workspace) = cx.add_window((app_state.build_window_options)(), |cx| {
2996        let mut workspace = Workspace::new(
2997            Project::local(
2998                app_state.client.clone(),
2999                app_state.user_store.clone(),
3000                app_state.project_store.clone(),
3001                app_state.languages.clone(),
3002                app_state.fs.clone(),
3003                cx,
3004            ),
3005            app_state.default_item_factory,
3006            cx,
3007        );
3008        (app_state.initialize_workspace)(&mut workspace, app_state, cx);
3009        workspace
3010    });
3011    cx.dispatch_action_at(window_id, workspace.id(), NewFile);
3012}
3013
3014#[cfg(test)]
3015mod tests {
3016    use std::cell::Cell;
3017
3018    use crate::sidebar::SidebarItem;
3019
3020    use super::*;
3021    use fs::FakeFs;
3022    use gpui::{executor::Deterministic, ModelHandle, TestAppContext, ViewContext};
3023    use project::{Project, ProjectEntryId};
3024    use serde_json::json;
3025
3026    pub fn default_item_factory(
3027        _workspace: &mut Workspace,
3028        _cx: &mut ViewContext<Workspace>,
3029    ) -> Box<dyn ItemHandle> {
3030        unimplemented!();
3031    }
3032
3033    #[gpui::test]
3034    async fn test_tab_disambiguation(cx: &mut TestAppContext) {
3035        cx.foreground().forbid_parking();
3036        Settings::test_async(cx);
3037
3038        let fs = FakeFs::new(cx.background());
3039        let project = Project::test(fs, [], cx).await;
3040        let (_, workspace) =
3041            cx.add_window(|cx| Workspace::new(project.clone(), default_item_factory, cx));
3042
3043        // Adding an item with no ambiguity renders the tab without detail.
3044        let item1 = cx.add_view(&workspace, |_| {
3045            let mut item = TestItem::new();
3046            item.tab_descriptions = Some(vec!["c", "b1/c", "a/b1/c"]);
3047            item
3048        });
3049        workspace.update(cx, |workspace, cx| {
3050            workspace.add_item(Box::new(item1.clone()), cx);
3051        });
3052        item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), None));
3053
3054        // Adding an item that creates ambiguity increases the level of detail on
3055        // both tabs.
3056        let item2 = cx.add_view(&workspace, |_| {
3057            let mut item = TestItem::new();
3058            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
3059            item
3060        });
3061        workspace.update(cx, |workspace, cx| {
3062            workspace.add_item(Box::new(item2.clone()), cx);
3063        });
3064        item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
3065        item2.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
3066
3067        // Adding an item that creates ambiguity increases the level of detail only
3068        // on the ambiguous tabs. In this case, the ambiguity can't be resolved so
3069        // we stop at the highest detail available.
3070        let item3 = cx.add_view(&workspace, |_| {
3071            let mut item = TestItem::new();
3072            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
3073            item
3074        });
3075        workspace.update(cx, |workspace, cx| {
3076            workspace.add_item(Box::new(item3.clone()), cx);
3077        });
3078        item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
3079        item2.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
3080        item3.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
3081    }
3082
3083    #[gpui::test]
3084    async fn test_tracking_active_path(cx: &mut TestAppContext) {
3085        cx.foreground().forbid_parking();
3086        Settings::test_async(cx);
3087        let fs = FakeFs::new(cx.background());
3088        fs.insert_tree(
3089            "/root1",
3090            json!({
3091                "one.txt": "",
3092                "two.txt": "",
3093            }),
3094        )
3095        .await;
3096        fs.insert_tree(
3097            "/root2",
3098            json!({
3099                "three.txt": "",
3100            }),
3101        )
3102        .await;
3103
3104        let project = Project::test(fs, ["root1".as_ref()], cx).await;
3105        let (window_id, workspace) =
3106            cx.add_window(|cx| Workspace::new(project.clone(), default_item_factory, cx));
3107        let worktree_id = project.read_with(cx, |project, cx| {
3108            project.worktrees(cx).next().unwrap().read(cx).id()
3109        });
3110
3111        let item1 = cx.add_view(&workspace, |_| {
3112            let mut item = TestItem::new();
3113            item.project_path = Some((worktree_id, "one.txt").into());
3114            item
3115        });
3116        let item2 = cx.add_view(&workspace, |_| {
3117            let mut item = TestItem::new();
3118            item.project_path = Some((worktree_id, "two.txt").into());
3119            item
3120        });
3121
3122        // Add an item to an empty pane
3123        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item1), cx));
3124        project.read_with(cx, |project, cx| {
3125            assert_eq!(
3126                project.active_entry(),
3127                project
3128                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
3129                    .map(|e| e.id)
3130            );
3131        });
3132        assert_eq!(
3133            cx.current_window_title(window_id).as_deref(),
3134            Some("one.txt — root1")
3135        );
3136
3137        // Add a second item to a non-empty pane
3138        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item2), cx));
3139        assert_eq!(
3140            cx.current_window_title(window_id).as_deref(),
3141            Some("two.txt — root1")
3142        );
3143        project.read_with(cx, |project, cx| {
3144            assert_eq!(
3145                project.active_entry(),
3146                project
3147                    .entry_for_path(&(worktree_id, "two.txt").into(), cx)
3148                    .map(|e| e.id)
3149            );
3150        });
3151
3152        // Close the active item
3153        workspace
3154            .update(cx, |workspace, cx| {
3155                Pane::close_active_item(workspace, &Default::default(), cx).unwrap()
3156            })
3157            .await
3158            .unwrap();
3159        assert_eq!(
3160            cx.current_window_title(window_id).as_deref(),
3161            Some("one.txt — root1")
3162        );
3163        project.read_with(cx, |project, cx| {
3164            assert_eq!(
3165                project.active_entry(),
3166                project
3167                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
3168                    .map(|e| e.id)
3169            );
3170        });
3171
3172        // Add a project folder
3173        project
3174            .update(cx, |project, cx| {
3175                project.find_or_create_local_worktree("/root2", true, cx)
3176            })
3177            .await
3178            .unwrap();
3179        assert_eq!(
3180            cx.current_window_title(window_id).as_deref(),
3181            Some("one.txt — root1, root2")
3182        );
3183
3184        // Remove a project folder
3185        project.update(cx, |project, cx| {
3186            project.remove_worktree(worktree_id, cx);
3187        });
3188        assert_eq!(
3189            cx.current_window_title(window_id).as_deref(),
3190            Some("one.txt — root2")
3191        );
3192    }
3193
3194    #[gpui::test]
3195    async fn test_close_window(cx: &mut TestAppContext) {
3196        cx.foreground().forbid_parking();
3197        Settings::test_async(cx);
3198        let fs = FakeFs::new(cx.background());
3199        fs.insert_tree("/root", json!({ "one": "" })).await;
3200
3201        let project = Project::test(fs, ["root".as_ref()], cx).await;
3202        let (window_id, workspace) =
3203            cx.add_window(|cx| Workspace::new(project.clone(), default_item_factory, cx));
3204
3205        // When there are no dirty items, there's nothing to do.
3206        let item1 = cx.add_view(&workspace, |_| TestItem::new());
3207        workspace.update(cx, |w, cx| w.add_item(Box::new(item1.clone()), cx));
3208        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
3209        assert!(task.await.unwrap());
3210
3211        // When there are dirty untitled items, prompt to save each one. If the user
3212        // cancels any prompt, then abort.
3213        let item2 = cx.add_view(&workspace, |_| {
3214            let mut item = TestItem::new();
3215            item.is_dirty = true;
3216            item
3217        });
3218        let item3 = cx.add_view(&workspace, |_| {
3219            let mut item = TestItem::new();
3220            item.is_dirty = true;
3221            item.project_entry_ids = vec![ProjectEntryId::from_proto(1)];
3222            item
3223        });
3224        workspace.update(cx, |w, cx| {
3225            w.add_item(Box::new(item2.clone()), cx);
3226            w.add_item(Box::new(item3.clone()), cx);
3227        });
3228        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
3229        cx.foreground().run_until_parked();
3230        cx.simulate_prompt_answer(window_id, 2 /* cancel */);
3231        cx.foreground().run_until_parked();
3232        assert!(!cx.has_pending_prompt(window_id));
3233        assert!(!task.await.unwrap());
3234    }
3235
3236    #[gpui::test]
3237    async fn test_close_pane_items(cx: &mut TestAppContext) {
3238        cx.foreground().forbid_parking();
3239        Settings::test_async(cx);
3240        let fs = FakeFs::new(cx.background());
3241
3242        let project = Project::test(fs, None, cx).await;
3243        let (window_id, workspace) =
3244            cx.add_window(|cx| Workspace::new(project, default_item_factory, cx));
3245
3246        let item1 = cx.add_view(&workspace, |_| {
3247            let mut item = TestItem::new();
3248            item.is_dirty = true;
3249            item.project_entry_ids = vec![ProjectEntryId::from_proto(1)];
3250            item
3251        });
3252        let item2 = cx.add_view(&workspace, |_| {
3253            let mut item = TestItem::new();
3254            item.is_dirty = true;
3255            item.has_conflict = true;
3256            item.project_entry_ids = vec![ProjectEntryId::from_proto(2)];
3257            item
3258        });
3259        let item3 = cx.add_view(&workspace, |_| {
3260            let mut item = TestItem::new();
3261            item.is_dirty = true;
3262            item.has_conflict = true;
3263            item.project_entry_ids = vec![ProjectEntryId::from_proto(3)];
3264            item
3265        });
3266        let item4 = cx.add_view(&workspace, |_| {
3267            let mut item = TestItem::new();
3268            item.is_dirty = true;
3269            item
3270        });
3271        let pane = workspace.update(cx, |workspace, cx| {
3272            workspace.add_item(Box::new(item1.clone()), cx);
3273            workspace.add_item(Box::new(item2.clone()), cx);
3274            workspace.add_item(Box::new(item3.clone()), cx);
3275            workspace.add_item(Box::new(item4.clone()), cx);
3276            workspace.active_pane().clone()
3277        });
3278
3279        let close_items = workspace.update(cx, |workspace, cx| {
3280            pane.update(cx, |pane, cx| {
3281                pane.activate_item(1, true, true, cx);
3282                assert_eq!(pane.active_item().unwrap().id(), item2.id());
3283            });
3284
3285            let item1_id = item1.id();
3286            let item3_id = item3.id();
3287            let item4_id = item4.id();
3288            Pane::close_items(workspace, pane.clone(), cx, move |id| {
3289                [item1_id, item3_id, item4_id].contains(&id)
3290            })
3291        });
3292
3293        cx.foreground().run_until_parked();
3294        pane.read_with(cx, |pane, _| {
3295            assert_eq!(pane.items_len(), 4);
3296            assert_eq!(pane.active_item().unwrap().id(), item1.id());
3297        });
3298
3299        cx.simulate_prompt_answer(window_id, 0);
3300        cx.foreground().run_until_parked();
3301        pane.read_with(cx, |pane, cx| {
3302            assert_eq!(item1.read(cx).save_count, 1);
3303            assert_eq!(item1.read(cx).save_as_count, 0);
3304            assert_eq!(item1.read(cx).reload_count, 0);
3305            assert_eq!(pane.items_len(), 3);
3306            assert_eq!(pane.active_item().unwrap().id(), item3.id());
3307        });
3308
3309        cx.simulate_prompt_answer(window_id, 1);
3310        cx.foreground().run_until_parked();
3311        pane.read_with(cx, |pane, cx| {
3312            assert_eq!(item3.read(cx).save_count, 0);
3313            assert_eq!(item3.read(cx).save_as_count, 0);
3314            assert_eq!(item3.read(cx).reload_count, 1);
3315            assert_eq!(pane.items_len(), 2);
3316            assert_eq!(pane.active_item().unwrap().id(), item4.id());
3317        });
3318
3319        cx.simulate_prompt_answer(window_id, 0);
3320        cx.foreground().run_until_parked();
3321        cx.simulate_new_path_selection(|_| Some(Default::default()));
3322        close_items.await.unwrap();
3323        pane.read_with(cx, |pane, cx| {
3324            assert_eq!(item4.read(cx).save_count, 0);
3325            assert_eq!(item4.read(cx).save_as_count, 1);
3326            assert_eq!(item4.read(cx).reload_count, 0);
3327            assert_eq!(pane.items_len(), 1);
3328            assert_eq!(pane.active_item().unwrap().id(), item2.id());
3329        });
3330    }
3331
3332    #[gpui::test]
3333    async fn test_prompting_to_save_only_on_last_item_for_entry(cx: &mut TestAppContext) {
3334        cx.foreground().forbid_parking();
3335        Settings::test_async(cx);
3336        let fs = FakeFs::new(cx.background());
3337
3338        let project = Project::test(fs, [], cx).await;
3339        let (window_id, workspace) =
3340            cx.add_window(|cx| Workspace::new(project, default_item_factory, cx));
3341
3342        // Create several workspace items with single project entries, and two
3343        // workspace items with multiple project entries.
3344        let single_entry_items = (0..=4)
3345            .map(|project_entry_id| {
3346                let mut item = TestItem::new();
3347                item.is_dirty = true;
3348                item.project_entry_ids = vec![ProjectEntryId::from_proto(project_entry_id)];
3349                item.is_singleton = true;
3350                item
3351            })
3352            .collect::<Vec<_>>();
3353        let item_2_3 = {
3354            let mut item = TestItem::new();
3355            item.is_dirty = true;
3356            item.is_singleton = false;
3357            item.project_entry_ids =
3358                vec![ProjectEntryId::from_proto(2), ProjectEntryId::from_proto(3)];
3359            item
3360        };
3361        let item_3_4 = {
3362            let mut item = TestItem::new();
3363            item.is_dirty = true;
3364            item.is_singleton = false;
3365            item.project_entry_ids =
3366                vec![ProjectEntryId::from_proto(3), ProjectEntryId::from_proto(4)];
3367            item
3368        };
3369
3370        // Create two panes that contain the following project entries:
3371        //   left pane:
3372        //     multi-entry items:   (2, 3)
3373        //     single-entry items:  0, 1, 2, 3, 4
3374        //   right pane:
3375        //     single-entry items:  1
3376        //     multi-entry items:   (3, 4)
3377        let left_pane = workspace.update(cx, |workspace, cx| {
3378            let left_pane = workspace.active_pane().clone();
3379            workspace.add_item(Box::new(cx.add_view(|_| item_2_3.clone())), cx);
3380            for item in &single_entry_items {
3381                workspace.add_item(Box::new(cx.add_view(|_| item.clone())), cx);
3382            }
3383            left_pane.update(cx, |pane, cx| {
3384                pane.activate_item(2, true, true, cx);
3385            });
3386
3387            workspace
3388                .split_pane(left_pane.clone(), SplitDirection::Right, cx)
3389                .unwrap();
3390
3391            left_pane
3392        });
3393
3394        //Need to cause an effect flush in order to respect new focus
3395        workspace.update(cx, |workspace, cx| {
3396            workspace.add_item(Box::new(cx.add_view(|_| item_3_4.clone())), cx);
3397            cx.focus(left_pane.clone());
3398        });
3399
3400        // When closing all of the items in the left pane, we should be prompted twice:
3401        // once for project entry 0, and once for project entry 2. After those two
3402        // prompts, the task should complete.
3403
3404        let close = workspace.update(cx, |workspace, cx| {
3405            Pane::close_items(workspace, left_pane.clone(), cx, |_| true)
3406        });
3407
3408        cx.foreground().run_until_parked();
3409        left_pane.read_with(cx, |pane, cx| {
3410            assert_eq!(
3411                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
3412                &[ProjectEntryId::from_proto(0)]
3413            );
3414        });
3415        cx.simulate_prompt_answer(window_id, 0);
3416
3417        cx.foreground().run_until_parked();
3418        left_pane.read_with(cx, |pane, cx| {
3419            assert_eq!(
3420                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
3421                &[ProjectEntryId::from_proto(2)]
3422            );
3423        });
3424        cx.simulate_prompt_answer(window_id, 0);
3425
3426        cx.foreground().run_until_parked();
3427        close.await.unwrap();
3428        left_pane.read_with(cx, |pane, _| {
3429            assert_eq!(pane.items_len(), 0);
3430        });
3431    }
3432
3433    #[gpui::test]
3434    async fn test_autosave(deterministic: Arc<Deterministic>, cx: &mut gpui::TestAppContext) {
3435        deterministic.forbid_parking();
3436
3437        Settings::test_async(cx);
3438        let fs = FakeFs::new(cx.background());
3439
3440        let project = Project::test(fs, [], cx).await;
3441        let (window_id, workspace) =
3442            cx.add_window(|cx| Workspace::new(project, default_item_factory, cx));
3443
3444        let item = cx.add_view(&workspace, |_| {
3445            let mut item = TestItem::new();
3446            item.project_entry_ids = vec![ProjectEntryId::from_proto(1)];
3447            item
3448        });
3449        let item_id = item.id();
3450        workspace.update(cx, |workspace, cx| {
3451            workspace.add_item(Box::new(item.clone()), cx);
3452        });
3453
3454        // Autosave on window change.
3455        item.update(cx, |item, cx| {
3456            cx.update_global(|settings: &mut Settings, _| {
3457                settings.autosave = Autosave::OnWindowChange;
3458            });
3459            item.is_dirty = true;
3460        });
3461
3462        // Deactivating the window saves the file.
3463        cx.simulate_window_activation(None);
3464        deterministic.run_until_parked();
3465        item.read_with(cx, |item, _| assert_eq!(item.save_count, 1));
3466
3467        // Autosave on focus change.
3468        item.update(cx, |item, cx| {
3469            cx.focus_self();
3470            cx.update_global(|settings: &mut Settings, _| {
3471                settings.autosave = Autosave::OnFocusChange;
3472            });
3473            item.is_dirty = true;
3474        });
3475
3476        // Blurring the item saves the file.
3477        item.update(cx, |_, cx| cx.blur());
3478        deterministic.run_until_parked();
3479        item.read_with(cx, |item, _| assert_eq!(item.save_count, 2));
3480
3481        // Deactivating the window still saves the file.
3482        cx.simulate_window_activation(Some(window_id));
3483        item.update(cx, |item, cx| {
3484            cx.focus_self();
3485            item.is_dirty = true;
3486        });
3487        cx.simulate_window_activation(None);
3488
3489        deterministic.run_until_parked();
3490        item.read_with(cx, |item, _| assert_eq!(item.save_count, 3));
3491
3492        // Autosave after delay.
3493        item.update(cx, |item, cx| {
3494            cx.update_global(|settings: &mut Settings, _| {
3495                settings.autosave = Autosave::AfterDelay { milliseconds: 500 };
3496            });
3497            item.is_dirty = true;
3498            cx.emit(TestItemEvent::Edit);
3499        });
3500
3501        // Delay hasn't fully expired, so the file is still dirty and unsaved.
3502        deterministic.advance_clock(Duration::from_millis(250));
3503        item.read_with(cx, |item, _| assert_eq!(item.save_count, 3));
3504
3505        // After delay expires, the file is saved.
3506        deterministic.advance_clock(Duration::from_millis(250));
3507        item.read_with(cx, |item, _| assert_eq!(item.save_count, 4));
3508
3509        // Autosave on focus change, ensuring closing the tab counts as such.
3510        item.update(cx, |item, cx| {
3511            cx.update_global(|settings: &mut Settings, _| {
3512                settings.autosave = Autosave::OnFocusChange;
3513            });
3514            item.is_dirty = true;
3515        });
3516
3517        workspace
3518            .update(cx, |workspace, cx| {
3519                let pane = workspace.active_pane().clone();
3520                Pane::close_items(workspace, pane, cx, move |id| id == item_id)
3521            })
3522            .await
3523            .unwrap();
3524        assert!(!cx.has_pending_prompt(window_id));
3525        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
3526
3527        // Add the item again, ensuring autosave is prevented if the underlying file has been deleted.
3528        workspace.update(cx, |workspace, cx| {
3529            workspace.add_item(Box::new(item.clone()), cx);
3530        });
3531        item.update(cx, |item, cx| {
3532            item.project_entry_ids = Default::default();
3533            item.is_dirty = true;
3534            cx.blur();
3535        });
3536        deterministic.run_until_parked();
3537        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
3538
3539        // Ensure autosave is prevented for deleted files also when closing the buffer.
3540        let _close_items = workspace.update(cx, |workspace, cx| {
3541            let pane = workspace.active_pane().clone();
3542            Pane::close_items(workspace, pane, cx, move |id| id == item_id)
3543        });
3544        deterministic.run_until_parked();
3545        assert!(cx.has_pending_prompt(window_id));
3546        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
3547    }
3548
3549    #[gpui::test]
3550    async fn test_pane_navigation(
3551        deterministic: Arc<Deterministic>,
3552        cx: &mut gpui::TestAppContext,
3553    ) {
3554        deterministic.forbid_parking();
3555        Settings::test_async(cx);
3556        let fs = FakeFs::new(cx.background());
3557
3558        let project = Project::test(fs, [], cx).await;
3559        let (_, workspace) = cx.add_window(|cx| Workspace::new(project, default_item_factory, cx));
3560
3561        let item = cx.add_view(&workspace, |_| {
3562            let mut item = TestItem::new();
3563            item.project_entry_ids = vec![ProjectEntryId::from_proto(1)];
3564            item
3565        });
3566        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
3567        let toolbar = pane.read_with(cx, |pane, _| pane.toolbar().clone());
3568        let toolbar_notify_count = Rc::new(RefCell::new(0));
3569
3570        workspace.update(cx, |workspace, cx| {
3571            workspace.add_item(Box::new(item.clone()), cx);
3572            let toolbar_notification_count = toolbar_notify_count.clone();
3573            cx.observe(&toolbar, move |_, _, _| {
3574                *toolbar_notification_count.borrow_mut() += 1
3575            })
3576            .detach();
3577        });
3578
3579        pane.read_with(cx, |pane, _| {
3580            assert!(!pane.can_navigate_backward());
3581            assert!(!pane.can_navigate_forward());
3582        });
3583
3584        item.update(cx, |item, cx| {
3585            item.set_state("one".to_string(), cx);
3586        });
3587
3588        // Toolbar must be notified to re-render the navigation buttons
3589        assert_eq!(*toolbar_notify_count.borrow(), 1);
3590
3591        pane.read_with(cx, |pane, _| {
3592            assert!(pane.can_navigate_backward());
3593            assert!(!pane.can_navigate_forward());
3594        });
3595
3596        workspace
3597            .update(cx, |workspace, cx| {
3598                Pane::go_back(workspace, Some(pane.clone()), cx)
3599            })
3600            .await;
3601
3602        assert_eq!(*toolbar_notify_count.borrow(), 3);
3603        pane.read_with(cx, |pane, _| {
3604            assert!(!pane.can_navigate_backward());
3605            assert!(pane.can_navigate_forward());
3606        });
3607    }
3608
3609    pub struct TestItem {
3610        state: String,
3611        pub label: String,
3612        save_count: usize,
3613        save_as_count: usize,
3614        reload_count: usize,
3615        is_dirty: bool,
3616        is_singleton: bool,
3617        has_conflict: bool,
3618        project_entry_ids: Vec<ProjectEntryId>,
3619        project_path: Option<ProjectPath>,
3620        nav_history: Option<ItemNavHistory>,
3621        tab_descriptions: Option<Vec<&'static str>>,
3622        tab_detail: Cell<Option<usize>>,
3623    }
3624
3625    pub enum TestItemEvent {
3626        Edit,
3627    }
3628
3629    impl Clone for TestItem {
3630        fn clone(&self) -> Self {
3631            Self {
3632                state: self.state.clone(),
3633                label: self.label.clone(),
3634                save_count: self.save_count,
3635                save_as_count: self.save_as_count,
3636                reload_count: self.reload_count,
3637                is_dirty: self.is_dirty,
3638                is_singleton: self.is_singleton,
3639                has_conflict: self.has_conflict,
3640                project_entry_ids: self.project_entry_ids.clone(),
3641                project_path: self.project_path.clone(),
3642                nav_history: None,
3643                tab_descriptions: None,
3644                tab_detail: Default::default(),
3645            }
3646        }
3647    }
3648
3649    impl TestItem {
3650        pub fn new() -> Self {
3651            Self {
3652                state: String::new(),
3653                label: String::new(),
3654                save_count: 0,
3655                save_as_count: 0,
3656                reload_count: 0,
3657                is_dirty: false,
3658                has_conflict: false,
3659                project_entry_ids: Vec::new(),
3660                project_path: None,
3661                is_singleton: true,
3662                nav_history: None,
3663                tab_descriptions: None,
3664                tab_detail: Default::default(),
3665            }
3666        }
3667
3668        pub fn with_label(mut self, state: &str) -> Self {
3669            self.label = state.to_string();
3670            self
3671        }
3672
3673        pub fn with_singleton(mut self, singleton: bool) -> Self {
3674            self.is_singleton = singleton;
3675            self
3676        }
3677
3678        pub fn with_project_entry_ids(mut self, project_entry_ids: &[u64]) -> Self {
3679            self.project_entry_ids.extend(
3680                project_entry_ids
3681                    .iter()
3682                    .copied()
3683                    .map(ProjectEntryId::from_proto),
3684            );
3685            self
3686        }
3687
3688        fn set_state(&mut self, state: String, cx: &mut ViewContext<Self>) {
3689            self.push_to_nav_history(cx);
3690            self.state = state;
3691        }
3692
3693        fn push_to_nav_history(&mut self, cx: &mut ViewContext<Self>) {
3694            if let Some(history) = &mut self.nav_history {
3695                history.push(Some(Box::new(self.state.clone())), cx);
3696            }
3697        }
3698    }
3699
3700    impl Entity for TestItem {
3701        type Event = TestItemEvent;
3702    }
3703
3704    impl View for TestItem {
3705        fn ui_name() -> &'static str {
3706            "TestItem"
3707        }
3708
3709        fn render(&mut self, _: &mut RenderContext<Self>) -> ElementBox {
3710            Empty::new().boxed()
3711        }
3712    }
3713
3714    impl Item for TestItem {
3715        fn tab_description<'a>(&'a self, detail: usize, _: &'a AppContext) -> Option<Cow<'a, str>> {
3716            self.tab_descriptions.as_ref().and_then(|descriptions| {
3717                let description = *descriptions.get(detail).or_else(|| descriptions.last())?;
3718                Some(description.into())
3719            })
3720        }
3721
3722        fn tab_content(&self, detail: Option<usize>, _: &theme::Tab, _: &AppContext) -> ElementBox {
3723            self.tab_detail.set(detail);
3724            Empty::new().boxed()
3725        }
3726
3727        fn project_path(&self, _: &AppContext) -> Option<ProjectPath> {
3728            self.project_path.clone()
3729        }
3730
3731        fn project_entry_ids(&self, _: &AppContext) -> SmallVec<[ProjectEntryId; 3]> {
3732            self.project_entry_ids.iter().copied().collect()
3733        }
3734
3735        fn is_singleton(&self, _: &AppContext) -> bool {
3736            self.is_singleton
3737        }
3738
3739        fn set_nav_history(&mut self, history: ItemNavHistory, _: &mut ViewContext<Self>) {
3740            self.nav_history = Some(history);
3741        }
3742
3743        fn navigate(&mut self, state: Box<dyn Any>, _: &mut ViewContext<Self>) -> bool {
3744            let state = *state.downcast::<String>().unwrap_or_default();
3745            if state != self.state {
3746                self.state = state;
3747                true
3748            } else {
3749                false
3750            }
3751        }
3752
3753        fn deactivated(&mut self, cx: &mut ViewContext<Self>) {
3754            self.push_to_nav_history(cx);
3755        }
3756
3757        fn clone_on_split(&self, _: &mut ViewContext<Self>) -> Option<Self>
3758        where
3759            Self: Sized,
3760        {
3761            Some(self.clone())
3762        }
3763
3764        fn is_dirty(&self, _: &AppContext) -> bool {
3765            self.is_dirty
3766        }
3767
3768        fn has_conflict(&self, _: &AppContext) -> bool {
3769            self.has_conflict
3770        }
3771
3772        fn can_save(&self, _: &AppContext) -> bool {
3773            !self.project_entry_ids.is_empty()
3774        }
3775
3776        fn save(
3777            &mut self,
3778            _: ModelHandle<Project>,
3779            _: &mut ViewContext<Self>,
3780        ) -> Task<anyhow::Result<()>> {
3781            self.save_count += 1;
3782            self.is_dirty = false;
3783            Task::ready(Ok(()))
3784        }
3785
3786        fn save_as(
3787            &mut self,
3788            _: ModelHandle<Project>,
3789            _: std::path::PathBuf,
3790            _: &mut ViewContext<Self>,
3791        ) -> Task<anyhow::Result<()>> {
3792            self.save_as_count += 1;
3793            self.is_dirty = false;
3794            Task::ready(Ok(()))
3795        }
3796
3797        fn reload(
3798            &mut self,
3799            _: ModelHandle<Project>,
3800            _: &mut ViewContext<Self>,
3801        ) -> Task<anyhow::Result<()>> {
3802            self.reload_count += 1;
3803            self.is_dirty = false;
3804            Task::ready(Ok(()))
3805        }
3806
3807        fn to_item_events(_: &Self::Event) -> Vec<ItemEvent> {
3808            vec![ItemEvent::UpdateTab, ItemEvent::Edit]
3809        }
3810    }
3811
3812    impl SidebarItem for TestItem {}
3813}