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