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