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