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