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<T, F>(
1254        abs_paths: &[PathBuf],
1255        app_state: &Arc<AppState>,
1256        cx: &mut MutableAppContext,
1257        callback: F,
1258    ) -> Task<T>
1259    where
1260        T: 'static,
1261        F: 'static + FnOnce(&mut Workspace, &mut ViewContext<Workspace>) -> T,
1262    {
1263        let project_handle = Project::local(
1264            app_state.client.clone(),
1265            app_state.user_store.clone(),
1266            app_state.project_store.clone(),
1267            app_state.languages.clone(),
1268            app_state.fs.clone(),
1269            cx,
1270        );
1271
1272        cx.spawn(|mut cx| async move {
1273            // Get project paths for all of the abs_paths
1274            let mut worktree_roots: HashSet<Arc<Path>> = Default::default();
1275            let mut project_paths = Vec::new();
1276            for path in abs_paths {
1277                if let Some((worktree, project_entry)) = cx
1278                    .update(|cx| Workspace::project_path_for_path(project_handle, path, true, cx))
1279                    .await
1280                    .log_err()
1281                {
1282                    worktree_roots.insert(worktree.read_with(&mut cx, |tree, _| tree.abs_path()));
1283                    project_paths.push(project_entry);
1284                }
1285            }
1286
1287            // Use the resolved worktree roots to get the serialized_db from the database
1288            let serialized_workspace = cx.read(|cx| {
1289                cx.global::<Db>()
1290                    .workspace_for_worktree_roots(&Vec::from_iter(worktree_roots.into_iter())[..])
1291            });
1292
1293            // Use the serialized workspace to construct the new window
1294            let (_, workspace) = cx.add_window((app_state.build_window_options)(), |cx| {
1295                let mut workspace = Workspace::new(
1296                    serialized_workspace,
1297                    project_handle,
1298                    app_state.default_item_factory,
1299                    cx,
1300                );
1301                (app_state.initialize_workspace)(&mut workspace, &app_state, cx);
1302                workspace
1303            });
1304
1305            // Call open path for each of the project paths
1306            // (this will bring them to the front if they were in kthe serialized workspace)
1307            let tasks = workspace.update(&mut cx, |workspace, cx| {
1308                let tasks = Vec::new();
1309                for path in project_paths {
1310                    tasks.push(workspace.open_path(path, true, cx));
1311                }
1312                tasks
1313            });
1314            futures::future::join_all(tasks.into_iter()).await;
1315
1316            // Finally call callback on the workspace
1317            workspace.update(&mut cx, |workspace, cx| callback(workspace, cx))
1318        })
1319    }
1320
1321    pub fn weak_handle(&self) -> WeakViewHandle<Self> {
1322        self.weak_self.clone()
1323    }
1324
1325    pub fn left_sidebar(&self) -> &ViewHandle<Sidebar> {
1326        &self.left_sidebar
1327    }
1328
1329    pub fn right_sidebar(&self) -> &ViewHandle<Sidebar> {
1330        &self.right_sidebar
1331    }
1332
1333    pub fn status_bar(&self) -> &ViewHandle<StatusBar> {
1334        &self.status_bar
1335    }
1336
1337    pub fn user_store(&self) -> &ModelHandle<UserStore> {
1338        &self.user_store
1339    }
1340
1341    pub fn project(&self) -> &ModelHandle<Project> {
1342        &self.project
1343    }
1344
1345    pub fn client(&self) -> &Arc<Client> {
1346        &self.client
1347    }
1348
1349    pub fn set_titlebar_item(
1350        &mut self,
1351        item: impl Into<AnyViewHandle>,
1352        cx: &mut ViewContext<Self>,
1353    ) {
1354        self.titlebar_item = Some(item.into());
1355        cx.notify();
1356    }
1357
1358    pub fn titlebar_item(&self) -> Option<AnyViewHandle> {
1359        self.titlebar_item.clone()
1360    }
1361
1362    /// Call the given callback with a workspace whose project is local.
1363    ///
1364    /// If the given workspace has a local project, then it will be passed
1365    /// to the callback. Otherwise, a new empty window will be created.
1366    pub fn with_local_workspace<T, F>(
1367        &mut self,
1368        app_state: &Arc<AppState>,
1369        cx: &mut ViewContext<Self>,
1370        callback: F,
1371    ) -> Task<T>
1372    where
1373        T: 'static,
1374        F: FnOnce(&mut Workspace, &mut ViewContext<Workspace>) -> T,
1375    {
1376        if self.project.read(cx).is_local() {
1377            Task::Ready(Some(callback(self, cx)))
1378        } else {
1379            Self::new_local(&[], app_state, cx, callback)
1380        }
1381    }
1382
1383    pub fn worktrees<'a>(
1384        &self,
1385        cx: &'a AppContext,
1386    ) -> impl 'a + Iterator<Item = ModelHandle<Worktree>> {
1387        self.project.read(cx).worktrees(cx)
1388    }
1389
1390    pub fn visible_worktrees<'a>(
1391        &self,
1392        cx: &'a AppContext,
1393    ) -> impl 'a + Iterator<Item = ModelHandle<Worktree>> {
1394        self.project.read(cx).visible_worktrees(cx)
1395    }
1396
1397    pub fn worktree_scans_complete(&self, cx: &AppContext) -> impl Future<Output = ()> + 'static {
1398        let futures = self
1399            .worktrees(cx)
1400            .filter_map(|worktree| worktree.read(cx).as_local())
1401            .map(|worktree| worktree.scan_complete())
1402            .collect::<Vec<_>>();
1403        async move {
1404            for future in futures {
1405                future.await;
1406            }
1407        }
1408    }
1409
1410    pub fn close(
1411        &mut self,
1412        _: &CloseWindow,
1413        cx: &mut ViewContext<Self>,
1414    ) -> Option<Task<Result<()>>> {
1415        let prepare = self.prepare_to_close(false, cx);
1416        Some(cx.spawn(|this, mut cx| async move {
1417            if prepare.await? {
1418                this.update(&mut cx, |_, cx| {
1419                    let window_id = cx.window_id();
1420                    cx.remove_window(window_id);
1421                });
1422            }
1423            Ok(())
1424        }))
1425    }
1426
1427    pub fn prepare_to_close(
1428        &mut self,
1429        quitting: bool,
1430        cx: &mut ViewContext<Self>,
1431    ) -> Task<Result<bool>> {
1432        let active_call = self.active_call().cloned();
1433        let window_id = cx.window_id();
1434        let workspace_count = cx
1435            .window_ids()
1436            .flat_map(|window_id| cx.root_view::<Workspace>(window_id))
1437            .count();
1438        cx.spawn(|this, mut cx| async move {
1439            if let Some(active_call) = active_call {
1440                if !quitting
1441                    && workspace_count == 1
1442                    && active_call.read_with(&cx, |call, _| call.room().is_some())
1443                {
1444                    let answer = cx
1445                        .prompt(
1446                            window_id,
1447                            PromptLevel::Warning,
1448                            "Do you want to leave the current call?",
1449                            &["Close window and hang up", "Cancel"],
1450                        )
1451                        .next()
1452                        .await;
1453                    if answer == Some(1) {
1454                        return anyhow::Ok(false);
1455                    } else {
1456                        active_call.update(&mut cx, |call, cx| call.hang_up(cx))?;
1457                    }
1458                }
1459            }
1460
1461            Ok(this
1462                .update(&mut cx, |this, cx| this.save_all_internal(true, cx))
1463                .await?)
1464        })
1465    }
1466
1467    fn save_all(&mut self, _: &SaveAll, cx: &mut ViewContext<Self>) -> Option<Task<Result<()>>> {
1468        let save_all = self.save_all_internal(false, cx);
1469        Some(cx.foreground().spawn(async move {
1470            save_all.await?;
1471            Ok(())
1472        }))
1473    }
1474
1475    fn save_all_internal(
1476        &mut self,
1477        should_prompt_to_save: bool,
1478        cx: &mut ViewContext<Self>,
1479    ) -> Task<Result<bool>> {
1480        if self.project.read(cx).is_read_only() {
1481            return Task::ready(Ok(true));
1482        }
1483
1484        let dirty_items = self
1485            .panes
1486            .iter()
1487            .flat_map(|pane| {
1488                pane.read(cx).items().filter_map(|item| {
1489                    if item.is_dirty(cx) {
1490                        Some((pane.clone(), item.boxed_clone()))
1491                    } else {
1492                        None
1493                    }
1494                })
1495            })
1496            .collect::<Vec<_>>();
1497
1498        let project = self.project.clone();
1499        cx.spawn_weak(|_, mut cx| async move {
1500            for (pane, item) in dirty_items {
1501                let (singleton, project_entry_ids) =
1502                    cx.read(|cx| (item.is_singleton(cx), item.project_entry_ids(cx)));
1503                if singleton || !project_entry_ids.is_empty() {
1504                    if let Some(ix) =
1505                        pane.read_with(&cx, |pane, _| pane.index_for_item(item.as_ref()))
1506                    {
1507                        if !Pane::save_item(
1508                            project.clone(),
1509                            &pane,
1510                            ix,
1511                            &*item,
1512                            should_prompt_to_save,
1513                            &mut cx,
1514                        )
1515                        .await?
1516                        {
1517                            return Ok(false);
1518                        }
1519                    }
1520                }
1521            }
1522            Ok(true)
1523        })
1524    }
1525
1526    #[allow(clippy::type_complexity)]
1527    pub fn open_paths(
1528        &mut self,
1529        mut abs_paths: Vec<PathBuf>,
1530        visible: bool,
1531        cx: &mut ViewContext<Self>,
1532    ) -> Task<Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>> {
1533        let fs = self.fs.clone();
1534
1535        // Sort the paths to ensure we add worktrees for parents before their children.
1536        abs_paths.sort_unstable();
1537        cx.spawn(|this, mut cx| async move {
1538            let mut project_paths = Vec::new();
1539            for path in &abs_paths {
1540                project_paths.push(
1541                    this.update(&mut cx, |this, cx| {
1542                        Workspace::project_path_for_path(this.project, path, visible, cx)
1543                    })
1544                    .await
1545                    .log_err(),
1546                );
1547            }
1548
1549            let tasks = abs_paths
1550                .iter()
1551                .cloned()
1552                .zip(project_paths.into_iter())
1553                .map(|(abs_path, project_path)| {
1554                    let this = this.clone();
1555                    cx.spawn(|mut cx| {
1556                        let fs = fs.clone();
1557                        async move {
1558                            let (_worktree, project_path) = project_path?;
1559                            if fs.is_file(&abs_path).await {
1560                                Some(
1561                                    this.update(&mut cx, |this, cx| {
1562                                        this.open_path(project_path, None, true, cx)
1563                                    })
1564                                    .await,
1565                                )
1566                            } else {
1567                                None
1568                            }
1569                        }
1570                    })
1571                })
1572                .collect::<Vec<_>>();
1573
1574            futures::future::join_all(tasks).await
1575        })
1576    }
1577
1578    fn add_folder_to_project(&mut self, _: &AddFolderToProject, cx: &mut ViewContext<Self>) {
1579        let mut paths = cx.prompt_for_paths(PathPromptOptions {
1580            files: false,
1581            directories: true,
1582            multiple: true,
1583        });
1584        cx.spawn(|this, mut cx| async move {
1585            if let Some(paths) = paths.recv().await.flatten() {
1586                let results = this
1587                    .update(&mut cx, |this, cx| this.open_paths(paths, true, cx))
1588                    .await;
1589                for result in results.into_iter().flatten() {
1590                    result.log_err();
1591                }
1592            }
1593        })
1594        .detach();
1595    }
1596
1597    fn remove_folder_from_project(
1598        &mut self,
1599        RemoveWorktreeFromProject(worktree_id): &RemoveWorktreeFromProject,
1600        cx: &mut ViewContext<Self>,
1601    ) {
1602        self.project
1603            .update(cx, |project, cx| project.remove_worktree(*worktree_id, cx));
1604    }
1605
1606    fn project_path_for_path(
1607        project: ModelHandle<Project>,
1608        abs_path: &Path,
1609        visible: bool,
1610        cx: &mut MutableAppContext,
1611    ) -> Task<Result<(ModelHandle<Worktree>, ProjectPath)>> {
1612        let entry = project.update(cx, |project, cx| {
1613            project.find_or_create_local_worktree(abs_path, visible, cx)
1614        });
1615        cx.spawn(|cx| async move {
1616            let (worktree, path) = entry.await?;
1617            let worktree_id = worktree.read_with(&cx, |t, _| t.id());
1618            Ok((
1619                worktree,
1620                ProjectPath {
1621                    worktree_id,
1622                    path: path.into(),
1623                },
1624            ))
1625        })
1626    }
1627
1628    /// Returns the modal that was toggled closed if it was open.
1629    pub fn toggle_modal<V, F>(
1630        &mut self,
1631        cx: &mut ViewContext<Self>,
1632        add_view: F,
1633    ) -> Option<ViewHandle<V>>
1634    where
1635        V: 'static + View,
1636        F: FnOnce(&mut Self, &mut ViewContext<Self>) -> ViewHandle<V>,
1637    {
1638        cx.notify();
1639        // Whatever modal was visible is getting clobbered. If its the same type as V, then return
1640        // it. Otherwise, create a new modal and set it as active.
1641        let already_open_modal = self.modal.take().and_then(|modal| modal.downcast::<V>());
1642        if let Some(already_open_modal) = already_open_modal {
1643            cx.focus_self();
1644            Some(already_open_modal)
1645        } else {
1646            let modal = add_view(self, cx);
1647            cx.focus(&modal);
1648            self.modal = Some(modal.into());
1649            None
1650        }
1651    }
1652
1653    pub fn modal<V: 'static + View>(&self) -> Option<ViewHandle<V>> {
1654        self.modal
1655            .as_ref()
1656            .and_then(|modal| modal.clone().downcast::<V>())
1657    }
1658
1659    pub fn dismiss_modal(&mut self, cx: &mut ViewContext<Self>) {
1660        if self.modal.take().is_some() {
1661            cx.focus(&self.active_pane);
1662            cx.notify();
1663        }
1664    }
1665
1666    pub fn show_notification<V: Notification>(
1667        &mut self,
1668        id: usize,
1669        cx: &mut ViewContext<Self>,
1670        build_notification: impl FnOnce(&mut ViewContext<Self>) -> ViewHandle<V>,
1671    ) {
1672        let type_id = TypeId::of::<V>();
1673        if self
1674            .notifications
1675            .iter()
1676            .all(|(existing_type_id, existing_id, _)| {
1677                (*existing_type_id, *existing_id) != (type_id, id)
1678            })
1679        {
1680            let notification = build_notification(cx);
1681            cx.subscribe(&notification, move |this, handle, event, cx| {
1682                if handle.read(cx).should_dismiss_notification_on_event(event) {
1683                    this.dismiss_notification(type_id, id, cx);
1684                }
1685            })
1686            .detach();
1687            self.notifications
1688                .push((type_id, id, Box::new(notification)));
1689            cx.notify();
1690        }
1691    }
1692
1693    fn dismiss_notification(&mut self, type_id: TypeId, id: usize, cx: &mut ViewContext<Self>) {
1694        self.notifications
1695            .retain(|(existing_type_id, existing_id, _)| {
1696                if (*existing_type_id, *existing_id) == (type_id, id) {
1697                    cx.notify();
1698                    false
1699                } else {
1700                    true
1701                }
1702            });
1703    }
1704
1705    pub fn items<'a>(
1706        &'a self,
1707        cx: &'a AppContext,
1708    ) -> impl 'a + Iterator<Item = &Box<dyn ItemHandle>> {
1709        self.panes.iter().flat_map(|pane| pane.read(cx).items())
1710    }
1711
1712    pub fn item_of_type<T: Item>(&self, cx: &AppContext) -> Option<ViewHandle<T>> {
1713        self.items_of_type(cx).max_by_key(|item| item.id())
1714    }
1715
1716    pub fn items_of_type<'a, T: Item>(
1717        &'a self,
1718        cx: &'a AppContext,
1719    ) -> impl 'a + Iterator<Item = ViewHandle<T>> {
1720        self.panes
1721            .iter()
1722            .flat_map(|pane| pane.read(cx).items_of_type())
1723    }
1724
1725    pub fn active_item(&self, cx: &AppContext) -> Option<Box<dyn ItemHandle>> {
1726        self.active_pane().read(cx).active_item()
1727    }
1728
1729    fn active_project_path(&self, cx: &ViewContext<Self>) -> Option<ProjectPath> {
1730        self.active_item(cx).and_then(|item| item.project_path(cx))
1731    }
1732
1733    pub fn save_active_item(
1734        &mut self,
1735        force_name_change: bool,
1736        cx: &mut ViewContext<Self>,
1737    ) -> Task<Result<()>> {
1738        let project = self.project.clone();
1739        if let Some(item) = self.active_item(cx) {
1740            if !force_name_change && item.can_save(cx) {
1741                if item.has_conflict(cx.as_ref()) {
1742                    const CONFLICT_MESSAGE: &str = "This file has changed on disk since you started editing it. Do you want to overwrite it?";
1743
1744                    let mut answer = cx.prompt(
1745                        PromptLevel::Warning,
1746                        CONFLICT_MESSAGE,
1747                        &["Overwrite", "Cancel"],
1748                    );
1749                    cx.spawn(|_, mut cx| async move {
1750                        let answer = answer.recv().await;
1751                        if answer == Some(0) {
1752                            cx.update(|cx| item.save(project, cx)).await?;
1753                        }
1754                        Ok(())
1755                    })
1756                } else {
1757                    item.save(project, cx)
1758                }
1759            } else if item.is_singleton(cx) {
1760                let worktree = self.worktrees(cx).next();
1761                let start_abs_path = worktree
1762                    .and_then(|w| w.read(cx).as_local())
1763                    .map_or(Path::new(""), |w| w.abs_path())
1764                    .to_path_buf();
1765                let mut abs_path = cx.prompt_for_new_path(&start_abs_path);
1766                cx.spawn(|_, mut cx| async move {
1767                    if let Some(abs_path) = abs_path.recv().await.flatten() {
1768                        cx.update(|cx| item.save_as(project, abs_path, cx)).await?;
1769                    }
1770                    Ok(())
1771                })
1772            } else {
1773                Task::ready(Ok(()))
1774            }
1775        } else {
1776            Task::ready(Ok(()))
1777        }
1778    }
1779
1780    pub fn toggle_sidebar(&mut self, sidebar_side: SidebarSide, cx: &mut ViewContext<Self>) {
1781        let sidebar = match sidebar_side {
1782            SidebarSide::Left => &mut self.left_sidebar,
1783            SidebarSide::Right => &mut self.right_sidebar,
1784        };
1785        let open = sidebar.update(cx, |sidebar, cx| {
1786            let open = !sidebar.is_open();
1787            sidebar.set_open(open, cx);
1788            open
1789        });
1790
1791        if open {
1792            Dock::hide_on_sidebar_shown(self, sidebar_side, cx);
1793        }
1794
1795        cx.focus_self();
1796        cx.notify();
1797    }
1798
1799    pub fn toggle_sidebar_item(&mut self, action: &ToggleSidebarItem, cx: &mut ViewContext<Self>) {
1800        let sidebar = match action.sidebar_side {
1801            SidebarSide::Left => &mut self.left_sidebar,
1802            SidebarSide::Right => &mut self.right_sidebar,
1803        };
1804        let active_item = sidebar.update(cx, move |sidebar, cx| {
1805            if sidebar.is_open() && sidebar.active_item_ix() == action.item_index {
1806                sidebar.set_open(false, cx);
1807                None
1808            } else {
1809                sidebar.set_open(true, cx);
1810                sidebar.activate_item(action.item_index, cx);
1811                sidebar.active_item().cloned()
1812            }
1813        });
1814
1815        if let Some(active_item) = active_item {
1816            Dock::hide_on_sidebar_shown(self, action.sidebar_side, cx);
1817
1818            if active_item.is_focused(cx) {
1819                cx.focus_self();
1820            } else {
1821                cx.focus(active_item.to_any());
1822            }
1823        } else {
1824            cx.focus_self();
1825        }
1826        cx.notify();
1827    }
1828
1829    pub fn toggle_sidebar_item_focus(
1830        &mut self,
1831        sidebar_side: SidebarSide,
1832        item_index: usize,
1833        cx: &mut ViewContext<Self>,
1834    ) {
1835        let sidebar = match sidebar_side {
1836            SidebarSide::Left => &mut self.left_sidebar,
1837            SidebarSide::Right => &mut self.right_sidebar,
1838        };
1839        let active_item = sidebar.update(cx, |sidebar, cx| {
1840            sidebar.set_open(true, cx);
1841            sidebar.activate_item(item_index, cx);
1842            sidebar.active_item().cloned()
1843        });
1844        if let Some(active_item) = active_item {
1845            Dock::hide_on_sidebar_shown(self, sidebar_side, cx);
1846
1847            if active_item.is_focused(cx) {
1848                cx.focus_self();
1849            } else {
1850                cx.focus(active_item.to_any());
1851            }
1852        }
1853        cx.notify();
1854    }
1855
1856    pub fn focus_center(&mut self, _: &menu::Cancel, cx: &mut ViewContext<Self>) {
1857        cx.focus_self();
1858        cx.notify();
1859    }
1860
1861    fn add_pane(&mut self, cx: &mut ViewContext<Self>) -> ViewHandle<Pane> {
1862        let pane = cx.add_view(|cx| Pane::new(None, cx));
1863        let pane_id = pane.id();
1864        cx.subscribe(&pane, move |this, _, event, cx| {
1865            this.handle_pane_event(pane_id, event, cx)
1866        })
1867        .detach();
1868        self.panes.push(pane.clone());
1869        cx.focus(pane.clone());
1870        cx.emit(Event::PaneAdded(pane.clone()));
1871        pane
1872    }
1873
1874    pub fn add_item(&mut self, item: Box<dyn ItemHandle>, cx: &mut ViewContext<Self>) {
1875        let active_pane = self.active_pane().clone();
1876        Pane::add_item(self, &active_pane, item, true, true, None, cx);
1877    }
1878
1879    pub fn open_path(
1880        &mut self,
1881        path: impl Into<ProjectPath>,
1882        pane: Option<WeakViewHandle<Pane>>,
1883        focus_item: bool,
1884        cx: &mut ViewContext<Self>,
1885    ) -> Task<Result<Box<dyn ItemHandle>, anyhow::Error>> {
1886        let pane = pane.unwrap_or_else(|| self.active_pane().downgrade());
1887        let task = self.load_path(path.into(), cx);
1888        cx.spawn(|this, mut cx| async move {
1889            let (project_entry_id, build_item) = task.await?;
1890            let pane = pane
1891                .upgrade(&cx)
1892                .ok_or_else(|| anyhow!("pane was closed"))?;
1893            this.update(&mut cx, |this, cx| {
1894                Ok(Pane::open_item(
1895                    this,
1896                    pane,
1897                    project_entry_id,
1898                    focus_item,
1899                    cx,
1900                    build_item,
1901                ))
1902            })
1903        })
1904    }
1905
1906    pub(crate) fn load_path(
1907        &mut self,
1908        path: ProjectPath,
1909        cx: &mut ViewContext<Self>,
1910    ) -> Task<
1911        Result<(
1912            ProjectEntryId,
1913            impl 'static + FnOnce(&mut ViewContext<Pane>) -> Box<dyn ItemHandle>,
1914        )>,
1915    > {
1916        let project = self.project().clone();
1917        let project_item = project.update(cx, |project, cx| project.open_path(path, cx));
1918        cx.as_mut().spawn(|mut cx| async move {
1919            let (project_entry_id, project_item) = project_item.await?;
1920            let build_item = cx.update(|cx| {
1921                cx.default_global::<ProjectItemBuilders>()
1922                    .get(&project_item.model_type())
1923                    .ok_or_else(|| anyhow!("no item builder for project item"))
1924                    .cloned()
1925            })?;
1926            let build_item =
1927                move |cx: &mut ViewContext<Pane>| build_item(project, project_item, cx);
1928            Ok((project_entry_id, build_item))
1929        })
1930    }
1931
1932    pub fn open_project_item<T>(
1933        &mut self,
1934        project_item: ModelHandle<T::Item>,
1935        cx: &mut ViewContext<Self>,
1936    ) -> ViewHandle<T>
1937    where
1938        T: ProjectItem,
1939    {
1940        use project::Item as _;
1941
1942        let entry_id = project_item.read(cx).entry_id(cx);
1943        if let Some(item) = entry_id
1944            .and_then(|entry_id| self.active_pane().read(cx).item_for_entry(entry_id, cx))
1945            .and_then(|item| item.downcast())
1946        {
1947            self.activate_item(&item, cx);
1948            return item;
1949        }
1950
1951        let item = cx.add_view(|cx| T::for_project_item(self.project().clone(), project_item, cx));
1952        self.add_item(Box::new(item.clone()), cx);
1953        item
1954    }
1955
1956    pub fn open_shared_screen(&mut self, action: &OpenSharedScreen, cx: &mut ViewContext<Self>) {
1957        if let Some(shared_screen) =
1958            self.shared_screen_for_peer(action.peer_id, &self.active_pane, cx)
1959        {
1960            let pane = self.active_pane.clone();
1961            Pane::add_item(self, &pane, Box::new(shared_screen), false, true, None, cx);
1962        }
1963    }
1964
1965    pub fn activate_item(&mut self, item: &dyn ItemHandle, cx: &mut ViewContext<Self>) -> bool {
1966        let result = self.panes.iter().find_map(|pane| {
1967            pane.read(cx)
1968                .index_for_item(item)
1969                .map(|ix| (pane.clone(), ix))
1970        });
1971        if let Some((pane, ix)) = result {
1972            pane.update(cx, |pane, cx| pane.activate_item(ix, true, true, cx));
1973            true
1974        } else {
1975            false
1976        }
1977    }
1978
1979    fn activate_pane_at_index(&mut self, action: &ActivatePane, cx: &mut ViewContext<Self>) {
1980        let panes = self.center.panes();
1981        if let Some(pane) = panes.get(action.0).map(|p| (*p).clone()) {
1982            cx.focus(pane);
1983        } else {
1984            self.split_pane(self.active_pane.clone(), SplitDirection::Right, cx);
1985        }
1986    }
1987
1988    pub fn activate_next_pane(&mut self, cx: &mut ViewContext<Self>) {
1989        let next_pane = {
1990            let panes = self.center.panes();
1991            let ix = panes
1992                .iter()
1993                .position(|pane| **pane == self.active_pane)
1994                .unwrap();
1995            let next_ix = (ix + 1) % panes.len();
1996            panes[next_ix].clone()
1997        };
1998        cx.focus(next_pane);
1999    }
2000
2001    pub fn activate_previous_pane(&mut self, cx: &mut ViewContext<Self>) {
2002        let prev_pane = {
2003            let panes = self.center.panes();
2004            let ix = panes
2005                .iter()
2006                .position(|pane| **pane == self.active_pane)
2007                .unwrap();
2008            let prev_ix = if ix == 0 { panes.len() - 1 } else { ix - 1 };
2009            panes[prev_ix].clone()
2010        };
2011        cx.focus(prev_pane);
2012    }
2013
2014    fn handle_pane_focused(&mut self, pane: ViewHandle<Pane>, cx: &mut ViewContext<Self>) {
2015        if self.active_pane != pane {
2016            self.active_pane
2017                .update(cx, |pane, cx| pane.set_active(false, cx));
2018            self.active_pane = pane.clone();
2019            self.active_pane
2020                .update(cx, |pane, cx| pane.set_active(true, cx));
2021            self.status_bar.update(cx, |status_bar, cx| {
2022                status_bar.set_active_pane(&self.active_pane, cx);
2023            });
2024            self.active_item_path_changed(cx);
2025
2026            if &pane == self.dock_pane() {
2027                Dock::show(self, cx);
2028            } else {
2029                self.last_active_center_pane = Some(pane.downgrade());
2030                if self.dock.is_anchored_at(DockAnchor::Expanded) {
2031                    Dock::hide(self, cx);
2032                }
2033            }
2034            cx.notify();
2035        }
2036
2037        self.update_followers(
2038            proto::update_followers::Variant::UpdateActiveView(proto::UpdateActiveView {
2039                id: self.active_item(cx).map(|item| item.id() as u64),
2040                leader_id: self.leader_for_pane(&pane).map(|id| id.0),
2041            }),
2042            cx,
2043        );
2044    }
2045
2046    fn handle_pane_event(
2047        &mut self,
2048        pane_id: usize,
2049        event: &pane::Event,
2050        cx: &mut ViewContext<Self>,
2051    ) {
2052        if let Some(pane) = self.pane(pane_id) {
2053            let is_dock = &pane == self.dock.pane();
2054            match event {
2055                pane::Event::Split(direction) if !is_dock => {
2056                    self.split_pane(pane, *direction, cx);
2057                }
2058                pane::Event::Remove if !is_dock => self.remove_pane(pane, cx),
2059                pane::Event::Remove if is_dock => Dock::hide(self, cx),
2060                pane::Event::ActivateItem { local } => {
2061                    if *local {
2062                        self.unfollow(&pane, cx);
2063                    }
2064                    if &pane == self.active_pane() {
2065                        self.active_item_path_changed(cx);
2066                    }
2067                }
2068                pane::Event::ChangeItemTitle => {
2069                    if pane == self.active_pane {
2070                        self.active_item_path_changed(cx);
2071                    }
2072                    self.update_window_edited(cx);
2073                }
2074                pane::Event::RemoveItem { item_id } => {
2075                    self.update_window_edited(cx);
2076                    if let hash_map::Entry::Occupied(entry) = self.panes_by_item.entry(*item_id) {
2077                        if entry.get().id() == pane.id() {
2078                            entry.remove();
2079                        }
2080                    }
2081                }
2082                _ => {}
2083            }
2084        } else if self.dock.visible_pane().is_none() {
2085            error!("pane {} not found", pane_id);
2086        }
2087    }
2088
2089    pub fn split_pane(
2090        &mut self,
2091        pane: ViewHandle<Pane>,
2092        direction: SplitDirection,
2093        cx: &mut ViewContext<Self>,
2094    ) -> Option<ViewHandle<Pane>> {
2095        if &pane == self.dock_pane() {
2096            warn!("Can't split dock pane.");
2097            return None;
2098        }
2099
2100        pane.read(cx).active_item().map(|item| {
2101            let new_pane = self.add_pane(cx);
2102            if let Some(clone) = item.clone_on_split(cx.as_mut()) {
2103                Pane::add_item(self, &new_pane, clone, true, true, None, cx);
2104            }
2105            self.center.split(&pane, &new_pane, direction).unwrap();
2106            cx.notify();
2107            new_pane
2108        })
2109    }
2110
2111    pub fn split_pane_with_item(
2112        &mut self,
2113        from: WeakViewHandle<Pane>,
2114        pane_to_split: WeakViewHandle<Pane>,
2115        item_id_to_move: usize,
2116        split_direction: SplitDirection,
2117        cx: &mut ViewContext<Self>,
2118    ) {
2119        if let Some((pane_to_split, from)) = pane_to_split.upgrade(cx).zip(from.upgrade(cx)) {
2120            if &pane_to_split == self.dock_pane() {
2121                warn!("Can't split dock pane.");
2122                return;
2123            }
2124
2125            let new_pane = self.add_pane(cx);
2126            Pane::move_item(self, from.clone(), new_pane.clone(), item_id_to_move, 0, cx);
2127            self.center
2128                .split(&pane_to_split, &new_pane, split_direction)
2129                .unwrap();
2130            cx.notify();
2131        }
2132    }
2133
2134    fn remove_pane(&mut self, pane: ViewHandle<Pane>, cx: &mut ViewContext<Self>) {
2135        if self.center.remove(&pane).unwrap() {
2136            self.panes.retain(|p| p != &pane);
2137            cx.focus(self.panes.last().unwrap().clone());
2138            self.unfollow(&pane, cx);
2139            self.last_leaders_by_pane.remove(&pane.downgrade());
2140            for removed_item in pane.read(cx).items() {
2141                self.panes_by_item.remove(&removed_item.id());
2142            }
2143            if self.last_active_center_pane == Some(pane.downgrade()) {
2144                self.last_active_center_pane = None;
2145            }
2146
2147            cx.notify();
2148        } else {
2149            self.active_item_path_changed(cx);
2150        }
2151    }
2152
2153    pub fn panes(&self) -> &[ViewHandle<Pane>] {
2154        &self.panes
2155    }
2156
2157    fn pane(&self, pane_id: usize) -> Option<ViewHandle<Pane>> {
2158        self.panes.iter().find(|pane| pane.id() == pane_id).cloned()
2159    }
2160
2161    pub fn active_pane(&self) -> &ViewHandle<Pane> {
2162        &self.active_pane
2163    }
2164
2165    pub fn dock_pane(&self) -> &ViewHandle<Pane> {
2166        self.dock.pane()
2167    }
2168
2169    fn project_remote_id_changed(&mut self, remote_id: Option<u64>, cx: &mut ViewContext<Self>) {
2170        if let Some(remote_id) = remote_id {
2171            self.remote_entity_subscription =
2172                Some(self.client.add_view_for_remote_entity(remote_id, cx));
2173        } else {
2174            self.remote_entity_subscription.take();
2175        }
2176    }
2177
2178    fn collaborator_left(&mut self, peer_id: PeerId, cx: &mut ViewContext<Self>) {
2179        self.leader_state.followers.remove(&peer_id);
2180        if let Some(states_by_pane) = self.follower_states_by_leader.remove(&peer_id) {
2181            for state in states_by_pane.into_values() {
2182                for item in state.items_by_leader_view_id.into_values() {
2183                    if let FollowerItem::Loaded(item) = item {
2184                        item.set_leader_replica_id(None, cx);
2185                    }
2186                }
2187            }
2188        }
2189        cx.notify();
2190    }
2191
2192    pub fn toggle_follow(
2193        &mut self,
2194        ToggleFollow(leader_id): &ToggleFollow,
2195        cx: &mut ViewContext<Self>,
2196    ) -> Option<Task<Result<()>>> {
2197        let leader_id = *leader_id;
2198        let pane = self.active_pane().clone();
2199
2200        if let Some(prev_leader_id) = self.unfollow(&pane, cx) {
2201            if leader_id == prev_leader_id {
2202                return None;
2203            }
2204        }
2205
2206        self.last_leaders_by_pane
2207            .insert(pane.downgrade(), leader_id);
2208        self.follower_states_by_leader
2209            .entry(leader_id)
2210            .or_default()
2211            .insert(pane.clone(), Default::default());
2212        cx.notify();
2213
2214        let project_id = self.project.read(cx).remote_id()?;
2215        let request = self.client.request(proto::Follow {
2216            project_id,
2217            leader_id: leader_id.0,
2218        });
2219        Some(cx.spawn_weak(|this, mut cx| async move {
2220            let response = request.await?;
2221            if let Some(this) = this.upgrade(&cx) {
2222                this.update(&mut cx, |this, _| {
2223                    let state = this
2224                        .follower_states_by_leader
2225                        .get_mut(&leader_id)
2226                        .and_then(|states_by_pane| states_by_pane.get_mut(&pane))
2227                        .ok_or_else(|| anyhow!("following interrupted"))?;
2228                    state.active_view_id = response.active_view_id;
2229                    Ok::<_, anyhow::Error>(())
2230                })?;
2231                Self::add_views_from_leader(this, leader_id, vec![pane], response.views, &mut cx)
2232                    .await?;
2233            }
2234            Ok(())
2235        }))
2236    }
2237
2238    pub fn follow_next_collaborator(
2239        &mut self,
2240        _: &FollowNextCollaborator,
2241        cx: &mut ViewContext<Self>,
2242    ) -> Option<Task<Result<()>>> {
2243        let collaborators = self.project.read(cx).collaborators();
2244        let next_leader_id = if let Some(leader_id) = self.leader_for_pane(&self.active_pane) {
2245            let mut collaborators = collaborators.keys().copied();
2246            for peer_id in collaborators.by_ref() {
2247                if peer_id == leader_id {
2248                    break;
2249                }
2250            }
2251            collaborators.next()
2252        } else if let Some(last_leader_id) =
2253            self.last_leaders_by_pane.get(&self.active_pane.downgrade())
2254        {
2255            if collaborators.contains_key(last_leader_id) {
2256                Some(*last_leader_id)
2257            } else {
2258                None
2259            }
2260        } else {
2261            None
2262        };
2263
2264        next_leader_id
2265            .or_else(|| collaborators.keys().copied().next())
2266            .and_then(|leader_id| self.toggle_follow(&ToggleFollow(leader_id), cx))
2267    }
2268
2269    pub fn unfollow(
2270        &mut self,
2271        pane: &ViewHandle<Pane>,
2272        cx: &mut ViewContext<Self>,
2273    ) -> Option<PeerId> {
2274        for (leader_id, states_by_pane) in &mut self.follower_states_by_leader {
2275            let leader_id = *leader_id;
2276            if let Some(state) = states_by_pane.remove(pane) {
2277                for (_, item) in state.items_by_leader_view_id {
2278                    if let FollowerItem::Loaded(item) = item {
2279                        item.set_leader_replica_id(None, cx);
2280                    }
2281                }
2282
2283                if states_by_pane.is_empty() {
2284                    self.follower_states_by_leader.remove(&leader_id);
2285                    if let Some(project_id) = self.project.read(cx).remote_id() {
2286                        self.client
2287                            .send(proto::Unfollow {
2288                                project_id,
2289                                leader_id: leader_id.0,
2290                            })
2291                            .log_err();
2292                    }
2293                }
2294
2295                cx.notify();
2296                return Some(leader_id);
2297            }
2298        }
2299        None
2300    }
2301
2302    pub fn is_following(&self, peer_id: PeerId) -> bool {
2303        self.follower_states_by_leader.contains_key(&peer_id)
2304    }
2305
2306    fn render_titlebar(&self, theme: &Theme, cx: &mut RenderContext<Self>) -> ElementBox {
2307        let project = &self.project.read(cx);
2308        let mut worktree_root_names = String::new();
2309        for (i, name) in project.worktree_root_names(cx).enumerate() {
2310            if i > 0 {
2311                worktree_root_names.push_str(", ");
2312            }
2313            worktree_root_names.push_str(name);
2314        }
2315
2316        // TODO: There should be a better system in place for this
2317        // (https://github.com/zed-industries/zed/issues/1290)
2318        let is_fullscreen = cx.window_is_fullscreen(cx.window_id());
2319        let container_theme = if is_fullscreen {
2320            let mut container_theme = theme.workspace.titlebar.container;
2321            container_theme.padding.left = container_theme.padding.right;
2322            container_theme
2323        } else {
2324            theme.workspace.titlebar.container
2325        };
2326
2327        enum TitleBar {}
2328        ConstrainedBox::new(
2329            MouseEventHandler::<TitleBar>::new(0, cx, |_, cx| {
2330                Container::new(
2331                    Stack::new()
2332                        .with_child(
2333                            Label::new(worktree_root_names, theme.workspace.titlebar.title.clone())
2334                                .aligned()
2335                                .left()
2336                                .boxed(),
2337                        )
2338                        .with_children(
2339                            self.titlebar_item
2340                                .as_ref()
2341                                .map(|item| ChildView::new(item, cx).aligned().right().boxed()),
2342                        )
2343                        .boxed(),
2344                )
2345                .with_style(container_theme)
2346                .boxed()
2347            })
2348            .on_click(MouseButton::Left, |event, cx| {
2349                if event.click_count == 2 {
2350                    cx.zoom_window(cx.window_id());
2351                }
2352            })
2353            .boxed(),
2354        )
2355        .with_height(theme.workspace.titlebar.height)
2356        .named("titlebar")
2357    }
2358
2359    fn active_item_path_changed(&mut self, cx: &mut ViewContext<Self>) {
2360        let active_entry = self.active_project_path(cx);
2361        self.project
2362            .update(cx, |project, cx| project.set_active_path(active_entry, cx));
2363        self.update_window_title(cx);
2364    }
2365
2366    fn update_window_title(&mut self, cx: &mut ViewContext<Self>) {
2367        let mut title = String::new();
2368        let project = self.project().read(cx);
2369        if let Some(path) = self.active_item(cx).and_then(|item| item.project_path(cx)) {
2370            let filename = path
2371                .path
2372                .file_name()
2373                .map(|s| s.to_string_lossy())
2374                .or_else(|| {
2375                    Some(Cow::Borrowed(
2376                        project
2377                            .worktree_for_id(path.worktree_id, cx)?
2378                            .read(cx)
2379                            .root_name(),
2380                    ))
2381                });
2382            if let Some(filename) = filename {
2383                title.push_str(filename.as_ref());
2384                title.push_str("");
2385            }
2386        }
2387        for (i, name) in project.worktree_root_names(cx).enumerate() {
2388            if i > 0 {
2389                title.push_str(", ");
2390            }
2391            title.push_str(name);
2392        }
2393        if title.is_empty() {
2394            title = "empty project".to_string();
2395        }
2396        cx.set_window_title(&title);
2397    }
2398
2399    fn update_window_edited(&mut self, cx: &mut ViewContext<Self>) {
2400        let is_edited = !self.project.read(cx).is_read_only()
2401            && self
2402                .items(cx)
2403                .any(|item| item.has_conflict(cx) || item.is_dirty(cx));
2404        if is_edited != self.window_edited {
2405            self.window_edited = is_edited;
2406            cx.set_window_edited(self.window_edited)
2407        }
2408    }
2409
2410    fn render_disconnected_overlay(&self, cx: &mut RenderContext<Workspace>) -> Option<ElementBox> {
2411        if self.project.read(cx).is_read_only() {
2412            enum DisconnectedOverlay {}
2413            Some(
2414                MouseEventHandler::<DisconnectedOverlay>::new(0, cx, |_, cx| {
2415                    let theme = &cx.global::<Settings>().theme;
2416                    Label::new(
2417                        "Your connection to the remote project has been lost.".to_string(),
2418                        theme.workspace.disconnected_overlay.text.clone(),
2419                    )
2420                    .aligned()
2421                    .contained()
2422                    .with_style(theme.workspace.disconnected_overlay.container)
2423                    .boxed()
2424                })
2425                .with_cursor_style(CursorStyle::Arrow)
2426                .capture_all()
2427                .boxed(),
2428            )
2429        } else {
2430            None
2431        }
2432    }
2433
2434    fn render_notifications(
2435        &self,
2436        theme: &theme::Workspace,
2437        cx: &AppContext,
2438    ) -> Option<ElementBox> {
2439        if self.notifications.is_empty() {
2440            None
2441        } else {
2442            Some(
2443                Flex::column()
2444                    .with_children(self.notifications.iter().map(|(_, _, notification)| {
2445                        ChildView::new(notification.as_ref(), cx)
2446                            .contained()
2447                            .with_style(theme.notification)
2448                            .boxed()
2449                    }))
2450                    .constrained()
2451                    .with_width(theme.notifications.width)
2452                    .contained()
2453                    .with_style(theme.notifications.container)
2454                    .aligned()
2455                    .bottom()
2456                    .right()
2457                    .boxed(),
2458            )
2459        }
2460    }
2461
2462    // RPC handlers
2463
2464    async fn handle_follow(
2465        this: ViewHandle<Self>,
2466        envelope: TypedEnvelope<proto::Follow>,
2467        _: Arc<Client>,
2468        mut cx: AsyncAppContext,
2469    ) -> Result<proto::FollowResponse> {
2470        this.update(&mut cx, |this, cx| {
2471            this.leader_state
2472                .followers
2473                .insert(envelope.original_sender_id()?);
2474
2475            let active_view_id = this
2476                .active_item(cx)
2477                .and_then(|i| i.to_followable_item_handle(cx))
2478                .map(|i| i.id() as u64);
2479            Ok(proto::FollowResponse {
2480                active_view_id,
2481                views: this
2482                    .panes()
2483                    .iter()
2484                    .flat_map(|pane| {
2485                        let leader_id = this.leader_for_pane(pane).map(|id| id.0);
2486                        pane.read(cx).items().filter_map({
2487                            let cx = &cx;
2488                            move |item| {
2489                                let id = item.id() as u64;
2490                                let item = item.to_followable_item_handle(cx)?;
2491                                let variant = item.to_state_proto(cx)?;
2492                                Some(proto::View {
2493                                    id,
2494                                    leader_id,
2495                                    variant: Some(variant),
2496                                })
2497                            }
2498                        })
2499                    })
2500                    .collect(),
2501            })
2502        })
2503    }
2504
2505    async fn handle_unfollow(
2506        this: ViewHandle<Self>,
2507        envelope: TypedEnvelope<proto::Unfollow>,
2508        _: Arc<Client>,
2509        mut cx: AsyncAppContext,
2510    ) -> Result<()> {
2511        this.update(&mut cx, |this, _| {
2512            this.leader_state
2513                .followers
2514                .remove(&envelope.original_sender_id()?);
2515            Ok(())
2516        })
2517    }
2518
2519    async fn handle_update_followers(
2520        this: ViewHandle<Self>,
2521        envelope: TypedEnvelope<proto::UpdateFollowers>,
2522        _: Arc<Client>,
2523        mut cx: AsyncAppContext,
2524    ) -> Result<()> {
2525        let leader_id = envelope.original_sender_id()?;
2526        match envelope
2527            .payload
2528            .variant
2529            .ok_or_else(|| anyhow!("invalid update"))?
2530        {
2531            proto::update_followers::Variant::UpdateActiveView(update_active_view) => {
2532                this.update(&mut cx, |this, cx| {
2533                    this.update_leader_state(leader_id, cx, |state, _| {
2534                        state.active_view_id = update_active_view.id;
2535                    });
2536                    Ok::<_, anyhow::Error>(())
2537                })
2538            }
2539            proto::update_followers::Variant::UpdateView(update_view) => {
2540                this.update(&mut cx, |this, cx| {
2541                    let variant = update_view
2542                        .variant
2543                        .ok_or_else(|| anyhow!("missing update view variant"))?;
2544                    this.update_leader_state(leader_id, cx, |state, cx| {
2545                        let variant = variant.clone();
2546                        match state
2547                            .items_by_leader_view_id
2548                            .entry(update_view.id)
2549                            .or_insert(FollowerItem::Loading(Vec::new()))
2550                        {
2551                            FollowerItem::Loaded(item) => {
2552                                item.apply_update_proto(variant, cx).log_err();
2553                            }
2554                            FollowerItem::Loading(updates) => updates.push(variant),
2555                        }
2556                    });
2557                    Ok(())
2558                })
2559            }
2560            proto::update_followers::Variant::CreateView(view) => {
2561                let panes = this.read_with(&cx, |this, _| {
2562                    this.follower_states_by_leader
2563                        .get(&leader_id)
2564                        .into_iter()
2565                        .flat_map(|states_by_pane| states_by_pane.keys())
2566                        .cloned()
2567                        .collect()
2568                });
2569                Self::add_views_from_leader(this.clone(), leader_id, panes, vec![view], &mut cx)
2570                    .await?;
2571                Ok(())
2572            }
2573        }
2574        .log_err();
2575
2576        Ok(())
2577    }
2578
2579    async fn add_views_from_leader(
2580        this: ViewHandle<Self>,
2581        leader_id: PeerId,
2582        panes: Vec<ViewHandle<Pane>>,
2583        views: Vec<proto::View>,
2584        cx: &mut AsyncAppContext,
2585    ) -> Result<()> {
2586        let project = this.read_with(cx, |this, _| this.project.clone());
2587        let replica_id = project
2588            .read_with(cx, |project, _| {
2589                project
2590                    .collaborators()
2591                    .get(&leader_id)
2592                    .map(|c| c.replica_id)
2593            })
2594            .ok_or_else(|| anyhow!("no such collaborator {}", leader_id))?;
2595
2596        let item_builders = cx.update(|cx| {
2597            cx.default_global::<FollowableItemBuilders>()
2598                .values()
2599                .map(|b| b.0)
2600                .collect::<Vec<_>>()
2601        });
2602
2603        let mut item_tasks_by_pane = HashMap::default();
2604        for pane in panes {
2605            let mut item_tasks = Vec::new();
2606            let mut leader_view_ids = Vec::new();
2607            for view in &views {
2608                let mut variant = view.variant.clone();
2609                if variant.is_none() {
2610                    Err(anyhow!("missing variant"))?;
2611                }
2612                for build_item in &item_builders {
2613                    let task =
2614                        cx.update(|cx| build_item(pane.clone(), project.clone(), &mut variant, cx));
2615                    if let Some(task) = task {
2616                        item_tasks.push(task);
2617                        leader_view_ids.push(view.id);
2618                        break;
2619                    } else {
2620                        assert!(variant.is_some());
2621                    }
2622                }
2623            }
2624
2625            item_tasks_by_pane.insert(pane, (item_tasks, leader_view_ids));
2626        }
2627
2628        for (pane, (item_tasks, leader_view_ids)) in item_tasks_by_pane {
2629            let items = futures::future::try_join_all(item_tasks).await?;
2630            this.update(cx, |this, cx| {
2631                let state = this
2632                    .follower_states_by_leader
2633                    .get_mut(&leader_id)?
2634                    .get_mut(&pane)?;
2635
2636                for (id, item) in leader_view_ids.into_iter().zip(items) {
2637                    item.set_leader_replica_id(Some(replica_id), cx);
2638                    match state.items_by_leader_view_id.entry(id) {
2639                        hash_map::Entry::Occupied(e) => {
2640                            let e = e.into_mut();
2641                            if let FollowerItem::Loading(updates) = e {
2642                                for update in updates.drain(..) {
2643                                    item.apply_update_proto(update, cx)
2644                                        .context("failed to apply view update")
2645                                        .log_err();
2646                                }
2647                            }
2648                            *e = FollowerItem::Loaded(item);
2649                        }
2650                        hash_map::Entry::Vacant(e) => {
2651                            e.insert(FollowerItem::Loaded(item));
2652                        }
2653                    }
2654                }
2655
2656                Some(())
2657            });
2658        }
2659        this.update(cx, |this, cx| this.leader_updated(leader_id, cx));
2660
2661        Ok(())
2662    }
2663
2664    fn update_followers(
2665        &self,
2666        update: proto::update_followers::Variant,
2667        cx: &AppContext,
2668    ) -> Option<()> {
2669        let project_id = self.project.read(cx).remote_id()?;
2670        if !self.leader_state.followers.is_empty() {
2671            self.client
2672                .send(proto::UpdateFollowers {
2673                    project_id,
2674                    follower_ids: self.leader_state.followers.iter().map(|f| f.0).collect(),
2675                    variant: Some(update),
2676                })
2677                .log_err();
2678        }
2679        None
2680    }
2681
2682    pub fn leader_for_pane(&self, pane: &ViewHandle<Pane>) -> Option<PeerId> {
2683        self.follower_states_by_leader
2684            .iter()
2685            .find_map(|(leader_id, state)| {
2686                if state.contains_key(pane) {
2687                    Some(*leader_id)
2688                } else {
2689                    None
2690                }
2691            })
2692    }
2693
2694    fn update_leader_state(
2695        &mut self,
2696        leader_id: PeerId,
2697        cx: &mut ViewContext<Self>,
2698        mut update_fn: impl FnMut(&mut FollowerState, &mut ViewContext<Self>),
2699    ) {
2700        for (_, state) in self
2701            .follower_states_by_leader
2702            .get_mut(&leader_id)
2703            .into_iter()
2704            .flatten()
2705        {
2706            update_fn(state, cx);
2707        }
2708        self.leader_updated(leader_id, cx);
2709    }
2710
2711    fn leader_updated(&mut self, leader_id: PeerId, cx: &mut ViewContext<Self>) -> Option<()> {
2712        cx.notify();
2713
2714        let call = self.active_call()?;
2715        let room = call.read(cx).room()?.read(cx);
2716        let participant = room.remote_participants().get(&leader_id)?;
2717
2718        let mut items_to_add = Vec::new();
2719        match participant.location {
2720            call::ParticipantLocation::SharedProject { project_id } => {
2721                if Some(project_id) == self.project.read(cx).remote_id() {
2722                    for (pane, state) in self.follower_states_by_leader.get(&leader_id)? {
2723                        if let Some(FollowerItem::Loaded(item)) = state
2724                            .active_view_id
2725                            .and_then(|id| state.items_by_leader_view_id.get(&id))
2726                        {
2727                            items_to_add.push((pane.clone(), item.boxed_clone()));
2728                        }
2729                    }
2730                }
2731            }
2732            call::ParticipantLocation::UnsharedProject => {}
2733            call::ParticipantLocation::External => {
2734                for (pane, _) in self.follower_states_by_leader.get(&leader_id)? {
2735                    if let Some(shared_screen) = self.shared_screen_for_peer(leader_id, pane, cx) {
2736                        items_to_add.push((pane.clone(), Box::new(shared_screen)));
2737                    }
2738                }
2739            }
2740        }
2741
2742        for (pane, item) in items_to_add {
2743            Pane::add_item(self, &pane, item.boxed_clone(), false, false, None, cx);
2744            if pane == self.active_pane {
2745                pane.update(cx, |pane, cx| pane.focus_active_item(cx));
2746            }
2747        }
2748
2749        None
2750    }
2751
2752    fn shared_screen_for_peer(
2753        &self,
2754        peer_id: PeerId,
2755        pane: &ViewHandle<Pane>,
2756        cx: &mut ViewContext<Self>,
2757    ) -> Option<ViewHandle<SharedScreen>> {
2758        let call = self.active_call()?;
2759        let room = call.read(cx).room()?.read(cx);
2760        let participant = room.remote_participants().get(&peer_id)?;
2761        let track = participant.tracks.values().next()?.clone();
2762        let user = participant.user.clone();
2763
2764        for item in pane.read(cx).items_of_type::<SharedScreen>() {
2765            if item.read(cx).peer_id == peer_id {
2766                return Some(item);
2767            }
2768        }
2769
2770        Some(cx.add_view(|cx| SharedScreen::new(&track, peer_id, user.clone(), cx)))
2771    }
2772
2773    pub fn on_window_activation_changed(&mut self, active: bool, cx: &mut ViewContext<Self>) {
2774        if !active {
2775            for pane in &self.panes {
2776                pane.update(cx, |pane, cx| {
2777                    if let Some(item) = pane.active_item() {
2778                        item.workspace_deactivated(cx);
2779                    }
2780                    if matches!(
2781                        cx.global::<Settings>().autosave,
2782                        Autosave::OnWindowChange | Autosave::OnFocusChange
2783                    ) {
2784                        for item in pane.items() {
2785                            Pane::autosave_item(item.as_ref(), self.project.clone(), cx)
2786                                .detach_and_log_err(cx);
2787                        }
2788                    }
2789                });
2790            }
2791        }
2792    }
2793
2794    fn active_call(&self) -> Option<&ModelHandle<ActiveCall>> {
2795        self.active_call.as_ref().map(|(call, _)| call)
2796    }
2797
2798    fn on_active_call_event(
2799        &mut self,
2800        _: ModelHandle<ActiveCall>,
2801        event: &call::room::Event,
2802        cx: &mut ViewContext<Self>,
2803    ) {
2804        match event {
2805            call::room::Event::ParticipantLocationChanged { participant_id }
2806            | call::room::Event::RemoteVideoTracksChanged { participant_id } => {
2807                self.leader_updated(*participant_id, cx);
2808            }
2809            _ => {}
2810        }
2811    }
2812}
2813
2814impl Entity for Workspace {
2815    type Event = Event;
2816}
2817
2818impl View for Workspace {
2819    fn ui_name() -> &'static str {
2820        "Workspace"
2821    }
2822
2823    fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
2824        let theme = cx.global::<Settings>().theme.clone();
2825        Stack::new()
2826            .with_child(
2827                Flex::column()
2828                    .with_child(self.render_titlebar(&theme, cx))
2829                    .with_child(
2830                        Stack::new()
2831                            .with_child({
2832                                let project = self.project.clone();
2833                                Flex::row()
2834                                    .with_children(
2835                                        if self.left_sidebar.read(cx).active_item().is_some() {
2836                                            Some(
2837                                                ChildView::new(&self.left_sidebar, cx)
2838                                                    .flex(0.8, false)
2839                                                    .boxed(),
2840                                            )
2841                                        } else {
2842                                            None
2843                                        },
2844                                    )
2845                                    .with_child(
2846                                        FlexItem::new(
2847                                            Flex::column()
2848                                                .with_child(
2849                                                    FlexItem::new(self.center.render(
2850                                                        &project,
2851                                                        &theme,
2852                                                        &self.follower_states_by_leader,
2853                                                        self.active_call(),
2854                                                        self.active_pane(),
2855                                                        cx,
2856                                                    ))
2857                                                    .flex(1., true)
2858                                                    .boxed(),
2859                                                )
2860                                                .with_children(self.dock.render(
2861                                                    &theme,
2862                                                    DockAnchor::Bottom,
2863                                                    cx,
2864                                                ))
2865                                                .boxed(),
2866                                        )
2867                                        .flex(1., true)
2868                                        .boxed(),
2869                                    )
2870                                    .with_children(self.dock.render(&theme, DockAnchor::Right, cx))
2871                                    .with_children(
2872                                        if self.right_sidebar.read(cx).active_item().is_some() {
2873                                            Some(
2874                                                ChildView::new(&self.right_sidebar, cx)
2875                                                    .flex(0.8, false)
2876                                                    .boxed(),
2877                                            )
2878                                        } else {
2879                                            None
2880                                        },
2881                                    )
2882                                    .boxed()
2883                            })
2884                            .with_child(
2885                                Overlay::new(
2886                                    Stack::new()
2887                                        .with_children(self.dock.render(
2888                                            &theme,
2889                                            DockAnchor::Expanded,
2890                                            cx,
2891                                        ))
2892                                        .with_children(self.modal.as_ref().map(|modal| {
2893                                            ChildView::new(modal, cx)
2894                                                .contained()
2895                                                .with_style(theme.workspace.modal)
2896                                                .aligned()
2897                                                .top()
2898                                                .boxed()
2899                                        }))
2900                                        .with_children(
2901                                            self.render_notifications(&theme.workspace, cx),
2902                                        )
2903                                        .boxed(),
2904                                )
2905                                .boxed(),
2906                            )
2907                            .flex(1.0, true)
2908                            .boxed(),
2909                    )
2910                    .with_child(ChildView::new(&self.status_bar, cx).boxed())
2911                    .contained()
2912                    .with_background_color(theme.workspace.background)
2913                    .boxed(),
2914            )
2915            .with_children(DragAndDrop::render(cx))
2916            .with_children(self.render_disconnected_overlay(cx))
2917            .named("workspace")
2918    }
2919
2920    fn focus_in(&mut self, view: AnyViewHandle, cx: &mut ViewContext<Self>) {
2921        if cx.is_self_focused() {
2922            cx.focus(&self.active_pane);
2923        } else {
2924            for pane in self.panes() {
2925                let view = view.clone();
2926                if pane.update(cx, |_, cx| cx.is_child(view)) {
2927                    self.handle_pane_focused(pane.clone(), cx);
2928                    break;
2929                }
2930            }
2931        }
2932    }
2933
2934    fn keymap_context(&self, _: &AppContext) -> gpui::keymap::Context {
2935        let mut keymap = Self::default_keymap_context();
2936        if self.active_pane() == self.dock_pane() {
2937            keymap.set.insert("Dock".into());
2938        }
2939        keymap
2940    }
2941}
2942
2943pub trait WorkspaceHandle {
2944    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath>;
2945}
2946
2947impl WorkspaceHandle for ViewHandle<Workspace> {
2948    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath> {
2949        self.read(cx)
2950            .worktrees(cx)
2951            .flat_map(|worktree| {
2952                let worktree_id = worktree.read(cx).id();
2953                worktree.read(cx).files(true, 0).map(move |f| ProjectPath {
2954                    worktree_id,
2955                    path: f.path.clone(),
2956                })
2957            })
2958            .collect::<Vec<_>>()
2959    }
2960}
2961
2962impl std::fmt::Debug for OpenPaths {
2963    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2964        f.debug_struct("OpenPaths")
2965            .field("paths", &self.paths)
2966            .finish()
2967    }
2968}
2969
2970fn open(_: &Open, cx: &mut MutableAppContext) {
2971    let mut paths = cx.prompt_for_paths(PathPromptOptions {
2972        files: true,
2973        directories: true,
2974        multiple: true,
2975    });
2976    cx.spawn(|mut cx| async move {
2977        if let Some(paths) = paths.recv().await.flatten() {
2978            cx.update(|cx| cx.dispatch_global_action(OpenPaths { paths }));
2979        }
2980    })
2981    .detach();
2982}
2983
2984pub struct WorkspaceCreated(WeakViewHandle<Workspace>);
2985
2986pub fn activate_workspace_for_project(
2987    cx: &mut MutableAppContext,
2988    predicate: impl Fn(&mut Project, &mut ModelContext<Project>) -> bool,
2989) -> Option<ViewHandle<Workspace>> {
2990    for window_id in cx.window_ids().collect::<Vec<_>>() {
2991        if let Some(workspace_handle) = cx.root_view::<Workspace>(window_id) {
2992            let project = workspace_handle.read(cx).project.clone();
2993            if project.update(cx, &predicate) {
2994                cx.activate_window(window_id);
2995                return Some(workspace_handle);
2996            }
2997        }
2998    }
2999    None
3000}
3001
3002#[allow(clippy::type_complexity)]
3003pub fn open_paths(
3004    abs_paths: &[PathBuf],
3005    app_state: &Arc<AppState>,
3006    cx: &mut MutableAppContext,
3007) -> Task<(
3008    ViewHandle<Workspace>,
3009    Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
3010)> {
3011    log::info!("open paths {:?}", abs_paths);
3012
3013    // Open paths in existing workspace if possible
3014    let existing =
3015        activate_workspace_for_project(cx, |project, cx| project.contains_paths(abs_paths, cx));
3016
3017    let app_state = app_state.clone();
3018    let abs_paths = abs_paths.to_vec();
3019    cx.spawn(|mut cx| async move {
3020        let workspace = if let Some(existing) = existing {
3021            existing
3022        } else {
3023            let contains_directory =
3024                futures::future::join_all(abs_paths.iter().map(|path| app_state.fs.is_file(path)))
3025                    .await
3026                    .contains(&false);
3027
3028            cx.update(|cx| {
3029                Workspace::new_local(&abs_paths[..], &app_state, cx, move |workspace, cx| {
3030                    if contains_directory {
3031                        workspace.toggle_sidebar(SidebarSide::Left, cx);
3032                    }
3033                    cx.handle()
3034                })
3035            })
3036            .await
3037        };
3038
3039        let items = workspace
3040            .update(&mut cx, |workspace, cx| {
3041                workspace.open_paths(abs_paths, true, cx)
3042            })
3043            .await;
3044
3045        (workspace, items)
3046    })
3047}
3048
3049fn open_new(app_state: &Arc<AppState>, cx: &mut MutableAppContext) -> Task<()> {
3050    Workspace::new_local(&[], app_state, cx, |_, cx| cx.dispatch_action(NewFile))
3051}
3052
3053#[cfg(test)]
3054mod tests {
3055    use std::cell::Cell;
3056
3057    use crate::sidebar::SidebarItem;
3058
3059    use super::*;
3060    use fs::FakeFs;
3061    use gpui::{executor::Deterministic, ModelHandle, TestAppContext, ViewContext};
3062    use project::{Project, ProjectEntryId};
3063    use serde_json::json;
3064
3065    pub fn default_item_factory(
3066        _workspace: &mut Workspace,
3067        _cx: &mut ViewContext<Workspace>,
3068    ) -> Box<dyn ItemHandle> {
3069        unimplemented!();
3070    }
3071
3072    #[gpui::test]
3073    async fn test_tab_disambiguation(cx: &mut TestAppContext) {
3074        cx.foreground().forbid_parking();
3075        Settings::test_async(cx);
3076
3077        let fs = FakeFs::new(cx.background());
3078        let project = Project::test(fs, [], cx).await;
3079        let (_, workspace) =
3080            cx.add_window(|cx| Workspace::new(project.clone(), default_item_factory, cx));
3081
3082        // Adding an item with no ambiguity renders the tab without detail.
3083        let item1 = cx.add_view(&workspace, |_| {
3084            let mut item = TestItem::new();
3085            item.tab_descriptions = Some(vec!["c", "b1/c", "a/b1/c"]);
3086            item
3087        });
3088        workspace.update(cx, |workspace, cx| {
3089            workspace.add_item(Box::new(item1.clone()), cx);
3090        });
3091        item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), None));
3092
3093        // Adding an item that creates ambiguity increases the level of detail on
3094        // both tabs.
3095        let item2 = cx.add_view(&workspace, |_| {
3096            let mut item = TestItem::new();
3097            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
3098            item
3099        });
3100        workspace.update(cx, |workspace, cx| {
3101            workspace.add_item(Box::new(item2.clone()), cx);
3102        });
3103        item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
3104        item2.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
3105
3106        // Adding an item that creates ambiguity increases the level of detail only
3107        // on the ambiguous tabs. In this case, the ambiguity can't be resolved so
3108        // we stop at the highest detail available.
3109        let item3 = cx.add_view(&workspace, |_| {
3110            let mut item = TestItem::new();
3111            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
3112            item
3113        });
3114        workspace.update(cx, |workspace, cx| {
3115            workspace.add_item(Box::new(item3.clone()), cx);
3116        });
3117        item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
3118        item2.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
3119        item3.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
3120    }
3121
3122    #[gpui::test]
3123    async fn test_tracking_active_path(cx: &mut TestAppContext) {
3124        cx.foreground().forbid_parking();
3125        Settings::test_async(cx);
3126        let fs = FakeFs::new(cx.background());
3127        fs.insert_tree(
3128            "/root1",
3129            json!({
3130                "one.txt": "",
3131                "two.txt": "",
3132            }),
3133        )
3134        .await;
3135        fs.insert_tree(
3136            "/root2",
3137            json!({
3138                "three.txt": "",
3139            }),
3140        )
3141        .await;
3142
3143        let project = Project::test(fs, ["root1".as_ref()], cx).await;
3144        let (window_id, workspace) =
3145            cx.add_window(|cx| Workspace::new(project.clone(), default_item_factory, cx));
3146        let worktree_id = project.read_with(cx, |project, cx| {
3147            project.worktrees(cx).next().unwrap().read(cx).id()
3148        });
3149
3150        let item1 = cx.add_view(&workspace, |_| {
3151            let mut item = TestItem::new();
3152            item.project_path = Some((worktree_id, "one.txt").into());
3153            item
3154        });
3155        let item2 = cx.add_view(&workspace, |_| {
3156            let mut item = TestItem::new();
3157            item.project_path = Some((worktree_id, "two.txt").into());
3158            item
3159        });
3160
3161        // Add an item to an empty pane
3162        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item1), cx));
3163        project.read_with(cx, |project, cx| {
3164            assert_eq!(
3165                project.active_entry(),
3166                project
3167                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
3168                    .map(|e| e.id)
3169            );
3170        });
3171        assert_eq!(
3172            cx.current_window_title(window_id).as_deref(),
3173            Some("one.txt — root1")
3174        );
3175
3176        // Add a second item to a non-empty pane
3177        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item2), cx));
3178        assert_eq!(
3179            cx.current_window_title(window_id).as_deref(),
3180            Some("two.txt — root1")
3181        );
3182        project.read_with(cx, |project, cx| {
3183            assert_eq!(
3184                project.active_entry(),
3185                project
3186                    .entry_for_path(&(worktree_id, "two.txt").into(), cx)
3187                    .map(|e| e.id)
3188            );
3189        });
3190
3191        // Close the active item
3192        workspace
3193            .update(cx, |workspace, cx| {
3194                Pane::close_active_item(workspace, &Default::default(), cx).unwrap()
3195            })
3196            .await
3197            .unwrap();
3198        assert_eq!(
3199            cx.current_window_title(window_id).as_deref(),
3200            Some("one.txt — root1")
3201        );
3202        project.read_with(cx, |project, cx| {
3203            assert_eq!(
3204                project.active_entry(),
3205                project
3206                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
3207                    .map(|e| e.id)
3208            );
3209        });
3210
3211        // Add a project folder
3212        project
3213            .update(cx, |project, cx| {
3214                project.find_or_create_local_worktree("/root2", true, cx)
3215            })
3216            .await
3217            .unwrap();
3218        assert_eq!(
3219            cx.current_window_title(window_id).as_deref(),
3220            Some("one.txt — root1, root2")
3221        );
3222
3223        // Remove a project folder
3224        project.update(cx, |project, cx| {
3225            project.remove_worktree(worktree_id, cx);
3226        });
3227        assert_eq!(
3228            cx.current_window_title(window_id).as_deref(),
3229            Some("one.txt — root2")
3230        );
3231    }
3232
3233    #[gpui::test]
3234    async fn test_close_window(cx: &mut TestAppContext) {
3235        cx.foreground().forbid_parking();
3236        Settings::test_async(cx);
3237        let fs = FakeFs::new(cx.background());
3238        fs.insert_tree("/root", json!({ "one": "" })).await;
3239
3240        let project = Project::test(fs, ["root".as_ref()], cx).await;
3241        let (window_id, workspace) =
3242            cx.add_window(|cx| Workspace::new(project.clone(), default_item_factory, cx));
3243
3244        // When there are no dirty items, there's nothing to do.
3245        let item1 = cx.add_view(&workspace, |_| TestItem::new());
3246        workspace.update(cx, |w, cx| w.add_item(Box::new(item1.clone()), cx));
3247        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
3248        assert!(task.await.unwrap());
3249
3250        // When there are dirty untitled items, prompt to save each one. If the user
3251        // cancels any prompt, then abort.
3252        let item2 = cx.add_view(&workspace, |_| {
3253            let mut item = TestItem::new();
3254            item.is_dirty = true;
3255            item
3256        });
3257        let item3 = cx.add_view(&workspace, |_| {
3258            let mut item = TestItem::new();
3259            item.is_dirty = true;
3260            item.project_entry_ids = vec![ProjectEntryId::from_proto(1)];
3261            item
3262        });
3263        workspace.update(cx, |w, cx| {
3264            w.add_item(Box::new(item2.clone()), cx);
3265            w.add_item(Box::new(item3.clone()), cx);
3266        });
3267        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
3268        cx.foreground().run_until_parked();
3269        cx.simulate_prompt_answer(window_id, 2 /* cancel */);
3270        cx.foreground().run_until_parked();
3271        assert!(!cx.has_pending_prompt(window_id));
3272        assert!(!task.await.unwrap());
3273    }
3274
3275    #[gpui::test]
3276    async fn test_close_pane_items(cx: &mut TestAppContext) {
3277        cx.foreground().forbid_parking();
3278        Settings::test_async(cx);
3279        let fs = FakeFs::new(cx.background());
3280
3281        let project = Project::test(fs, None, cx).await;
3282        let (window_id, workspace) =
3283            cx.add_window(|cx| Workspace::new(project, default_item_factory, cx));
3284
3285        let item1 = cx.add_view(&workspace, |_| {
3286            let mut item = TestItem::new();
3287            item.is_dirty = true;
3288            item.project_entry_ids = vec![ProjectEntryId::from_proto(1)];
3289            item
3290        });
3291        let item2 = cx.add_view(&workspace, |_| {
3292            let mut item = TestItem::new();
3293            item.is_dirty = true;
3294            item.has_conflict = true;
3295            item.project_entry_ids = vec![ProjectEntryId::from_proto(2)];
3296            item
3297        });
3298        let item3 = cx.add_view(&workspace, |_| {
3299            let mut item = TestItem::new();
3300            item.is_dirty = true;
3301            item.has_conflict = true;
3302            item.project_entry_ids = vec![ProjectEntryId::from_proto(3)];
3303            item
3304        });
3305        let item4 = cx.add_view(&workspace, |_| {
3306            let mut item = TestItem::new();
3307            item.is_dirty = true;
3308            item
3309        });
3310        let pane = workspace.update(cx, |workspace, cx| {
3311            workspace.add_item(Box::new(item1.clone()), cx);
3312            workspace.add_item(Box::new(item2.clone()), cx);
3313            workspace.add_item(Box::new(item3.clone()), cx);
3314            workspace.add_item(Box::new(item4.clone()), cx);
3315            workspace.active_pane().clone()
3316        });
3317
3318        let close_items = workspace.update(cx, |workspace, cx| {
3319            pane.update(cx, |pane, cx| {
3320                pane.activate_item(1, true, true, cx);
3321                assert_eq!(pane.active_item().unwrap().id(), item2.id());
3322            });
3323
3324            let item1_id = item1.id();
3325            let item3_id = item3.id();
3326            let item4_id = item4.id();
3327            Pane::close_items(workspace, pane.clone(), cx, move |id| {
3328                [item1_id, item3_id, item4_id].contains(&id)
3329            })
3330        });
3331
3332        cx.foreground().run_until_parked();
3333        pane.read_with(cx, |pane, _| {
3334            assert_eq!(pane.items_len(), 4);
3335            assert_eq!(pane.active_item().unwrap().id(), item1.id());
3336        });
3337
3338        cx.simulate_prompt_answer(window_id, 0);
3339        cx.foreground().run_until_parked();
3340        pane.read_with(cx, |pane, cx| {
3341            assert_eq!(item1.read(cx).save_count, 1);
3342            assert_eq!(item1.read(cx).save_as_count, 0);
3343            assert_eq!(item1.read(cx).reload_count, 0);
3344            assert_eq!(pane.items_len(), 3);
3345            assert_eq!(pane.active_item().unwrap().id(), item3.id());
3346        });
3347
3348        cx.simulate_prompt_answer(window_id, 1);
3349        cx.foreground().run_until_parked();
3350        pane.read_with(cx, |pane, cx| {
3351            assert_eq!(item3.read(cx).save_count, 0);
3352            assert_eq!(item3.read(cx).save_as_count, 0);
3353            assert_eq!(item3.read(cx).reload_count, 1);
3354            assert_eq!(pane.items_len(), 2);
3355            assert_eq!(pane.active_item().unwrap().id(), item4.id());
3356        });
3357
3358        cx.simulate_prompt_answer(window_id, 0);
3359        cx.foreground().run_until_parked();
3360        cx.simulate_new_path_selection(|_| Some(Default::default()));
3361        close_items.await.unwrap();
3362        pane.read_with(cx, |pane, cx| {
3363            assert_eq!(item4.read(cx).save_count, 0);
3364            assert_eq!(item4.read(cx).save_as_count, 1);
3365            assert_eq!(item4.read(cx).reload_count, 0);
3366            assert_eq!(pane.items_len(), 1);
3367            assert_eq!(pane.active_item().unwrap().id(), item2.id());
3368        });
3369    }
3370
3371    #[gpui::test]
3372    async fn test_prompting_to_save_only_on_last_item_for_entry(cx: &mut TestAppContext) {
3373        cx.foreground().forbid_parking();
3374        Settings::test_async(cx);
3375        let fs = FakeFs::new(cx.background());
3376
3377        let project = Project::test(fs, [], cx).await;
3378        let (window_id, workspace) =
3379            cx.add_window(|cx| Workspace::new(project, default_item_factory, cx));
3380
3381        // Create several workspace items with single project entries, and two
3382        // workspace items with multiple project entries.
3383        let single_entry_items = (0..=4)
3384            .map(|project_entry_id| {
3385                let mut item = TestItem::new();
3386                item.is_dirty = true;
3387                item.project_entry_ids = vec![ProjectEntryId::from_proto(project_entry_id)];
3388                item.is_singleton = true;
3389                item
3390            })
3391            .collect::<Vec<_>>();
3392        let item_2_3 = {
3393            let mut item = TestItem::new();
3394            item.is_dirty = true;
3395            item.is_singleton = false;
3396            item.project_entry_ids =
3397                vec![ProjectEntryId::from_proto(2), ProjectEntryId::from_proto(3)];
3398            item
3399        };
3400        let item_3_4 = {
3401            let mut item = TestItem::new();
3402            item.is_dirty = true;
3403            item.is_singleton = false;
3404            item.project_entry_ids =
3405                vec![ProjectEntryId::from_proto(3), ProjectEntryId::from_proto(4)];
3406            item
3407        };
3408
3409        // Create two panes that contain the following project entries:
3410        //   left pane:
3411        //     multi-entry items:   (2, 3)
3412        //     single-entry items:  0, 1, 2, 3, 4
3413        //   right pane:
3414        //     single-entry items:  1
3415        //     multi-entry items:   (3, 4)
3416        let left_pane = workspace.update(cx, |workspace, cx| {
3417            let left_pane = workspace.active_pane().clone();
3418            workspace.add_item(Box::new(cx.add_view(|_| item_2_3.clone())), cx);
3419            for item in &single_entry_items {
3420                workspace.add_item(Box::new(cx.add_view(|_| item.clone())), cx);
3421            }
3422            left_pane.update(cx, |pane, cx| {
3423                pane.activate_item(2, true, true, cx);
3424            });
3425
3426            workspace
3427                .split_pane(left_pane.clone(), SplitDirection::Right, cx)
3428                .unwrap();
3429
3430            left_pane
3431        });
3432
3433        //Need to cause an effect flush in order to respect new focus
3434        workspace.update(cx, |workspace, cx| {
3435            workspace.add_item(Box::new(cx.add_view(|_| item_3_4.clone())), cx);
3436            cx.focus(left_pane.clone());
3437        });
3438
3439        // When closing all of the items in the left pane, we should be prompted twice:
3440        // once for project entry 0, and once for project entry 2. After those two
3441        // prompts, the task should complete.
3442
3443        let close = workspace.update(cx, |workspace, cx| {
3444            Pane::close_items(workspace, left_pane.clone(), cx, |_| true)
3445        });
3446
3447        cx.foreground().run_until_parked();
3448        left_pane.read_with(cx, |pane, cx| {
3449            assert_eq!(
3450                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
3451                &[ProjectEntryId::from_proto(0)]
3452            );
3453        });
3454        cx.simulate_prompt_answer(window_id, 0);
3455
3456        cx.foreground().run_until_parked();
3457        left_pane.read_with(cx, |pane, cx| {
3458            assert_eq!(
3459                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
3460                &[ProjectEntryId::from_proto(2)]
3461            );
3462        });
3463        cx.simulate_prompt_answer(window_id, 0);
3464
3465        cx.foreground().run_until_parked();
3466        close.await.unwrap();
3467        left_pane.read_with(cx, |pane, _| {
3468            assert_eq!(pane.items_len(), 0);
3469        });
3470    }
3471
3472    #[gpui::test]
3473    async fn test_autosave(deterministic: Arc<Deterministic>, cx: &mut gpui::TestAppContext) {
3474        deterministic.forbid_parking();
3475
3476        Settings::test_async(cx);
3477        let fs = FakeFs::new(cx.background());
3478
3479        let project = Project::test(fs, [], cx).await;
3480        let (window_id, workspace) =
3481            cx.add_window(|cx| Workspace::new(project, default_item_factory, cx));
3482
3483        let item = cx.add_view(&workspace, |_| {
3484            let mut item = TestItem::new();
3485            item.project_entry_ids = vec![ProjectEntryId::from_proto(1)];
3486            item
3487        });
3488        let item_id = item.id();
3489        workspace.update(cx, |workspace, cx| {
3490            workspace.add_item(Box::new(item.clone()), cx);
3491        });
3492
3493        // Autosave on window change.
3494        item.update(cx, |item, cx| {
3495            cx.update_global(|settings: &mut Settings, _| {
3496                settings.autosave = Autosave::OnWindowChange;
3497            });
3498            item.is_dirty = true;
3499        });
3500
3501        // Deactivating the window saves the file.
3502        cx.simulate_window_activation(None);
3503        deterministic.run_until_parked();
3504        item.read_with(cx, |item, _| assert_eq!(item.save_count, 1));
3505
3506        // Autosave on focus change.
3507        item.update(cx, |item, cx| {
3508            cx.focus_self();
3509            cx.update_global(|settings: &mut Settings, _| {
3510                settings.autosave = Autosave::OnFocusChange;
3511            });
3512            item.is_dirty = true;
3513        });
3514
3515        // Blurring the item saves the file.
3516        item.update(cx, |_, cx| cx.blur());
3517        deterministic.run_until_parked();
3518        item.read_with(cx, |item, _| assert_eq!(item.save_count, 2));
3519
3520        // Deactivating the window still saves the file.
3521        cx.simulate_window_activation(Some(window_id));
3522        item.update(cx, |item, cx| {
3523            cx.focus_self();
3524            item.is_dirty = true;
3525        });
3526        cx.simulate_window_activation(None);
3527
3528        deterministic.run_until_parked();
3529        item.read_with(cx, |item, _| assert_eq!(item.save_count, 3));
3530
3531        // Autosave after delay.
3532        item.update(cx, |item, cx| {
3533            cx.update_global(|settings: &mut Settings, _| {
3534                settings.autosave = Autosave::AfterDelay { milliseconds: 500 };
3535            });
3536            item.is_dirty = true;
3537            cx.emit(TestItemEvent::Edit);
3538        });
3539
3540        // Delay hasn't fully expired, so the file is still dirty and unsaved.
3541        deterministic.advance_clock(Duration::from_millis(250));
3542        item.read_with(cx, |item, _| assert_eq!(item.save_count, 3));
3543
3544        // After delay expires, the file is saved.
3545        deterministic.advance_clock(Duration::from_millis(250));
3546        item.read_with(cx, |item, _| assert_eq!(item.save_count, 4));
3547
3548        // Autosave on focus change, ensuring closing the tab counts as such.
3549        item.update(cx, |item, cx| {
3550            cx.update_global(|settings: &mut Settings, _| {
3551                settings.autosave = Autosave::OnFocusChange;
3552            });
3553            item.is_dirty = true;
3554        });
3555
3556        workspace
3557            .update(cx, |workspace, cx| {
3558                let pane = workspace.active_pane().clone();
3559                Pane::close_items(workspace, pane, cx, move |id| id == item_id)
3560            })
3561            .await
3562            .unwrap();
3563        assert!(!cx.has_pending_prompt(window_id));
3564        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
3565
3566        // Add the item again, ensuring autosave is prevented if the underlying file has been deleted.
3567        workspace.update(cx, |workspace, cx| {
3568            workspace.add_item(Box::new(item.clone()), cx);
3569        });
3570        item.update(cx, |item, cx| {
3571            item.project_entry_ids = Default::default();
3572            item.is_dirty = true;
3573            cx.blur();
3574        });
3575        deterministic.run_until_parked();
3576        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
3577
3578        // Ensure autosave is prevented for deleted files also when closing the buffer.
3579        let _close_items = workspace.update(cx, |workspace, cx| {
3580            let pane = workspace.active_pane().clone();
3581            Pane::close_items(workspace, pane, cx, move |id| id == item_id)
3582        });
3583        deterministic.run_until_parked();
3584        assert!(cx.has_pending_prompt(window_id));
3585        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
3586    }
3587
3588    #[gpui::test]
3589    async fn test_pane_navigation(
3590        deterministic: Arc<Deterministic>,
3591        cx: &mut gpui::TestAppContext,
3592    ) {
3593        deterministic.forbid_parking();
3594        Settings::test_async(cx);
3595        let fs = FakeFs::new(cx.background());
3596
3597        let project = Project::test(fs, [], cx).await;
3598        let (_, workspace) = cx.add_window(|cx| Workspace::new(project, default_item_factory, cx));
3599
3600        let item = cx.add_view(&workspace, |_| {
3601            let mut item = TestItem::new();
3602            item.project_entry_ids = vec![ProjectEntryId::from_proto(1)];
3603            item
3604        });
3605        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
3606        let toolbar = pane.read_with(cx, |pane, _| pane.toolbar().clone());
3607        let toolbar_notify_count = Rc::new(RefCell::new(0));
3608
3609        workspace.update(cx, |workspace, cx| {
3610            workspace.add_item(Box::new(item.clone()), cx);
3611            let toolbar_notification_count = toolbar_notify_count.clone();
3612            cx.observe(&toolbar, move |_, _, _| {
3613                *toolbar_notification_count.borrow_mut() += 1
3614            })
3615            .detach();
3616        });
3617
3618        pane.read_with(cx, |pane, _| {
3619            assert!(!pane.can_navigate_backward());
3620            assert!(!pane.can_navigate_forward());
3621        });
3622
3623        item.update(cx, |item, cx| {
3624            item.set_state("one".to_string(), cx);
3625        });
3626
3627        // Toolbar must be notified to re-render the navigation buttons
3628        assert_eq!(*toolbar_notify_count.borrow(), 1);
3629
3630        pane.read_with(cx, |pane, _| {
3631            assert!(pane.can_navigate_backward());
3632            assert!(!pane.can_navigate_forward());
3633        });
3634
3635        workspace
3636            .update(cx, |workspace, cx| {
3637                Pane::go_back(workspace, Some(pane.clone()), cx)
3638            })
3639            .await;
3640
3641        assert_eq!(*toolbar_notify_count.borrow(), 3);
3642        pane.read_with(cx, |pane, _| {
3643            assert!(!pane.can_navigate_backward());
3644            assert!(pane.can_navigate_forward());
3645        });
3646    }
3647
3648    pub struct TestItem {
3649        state: String,
3650        pub label: String,
3651        save_count: usize,
3652        save_as_count: usize,
3653        reload_count: usize,
3654        is_dirty: bool,
3655        is_singleton: bool,
3656        has_conflict: bool,
3657        project_entry_ids: Vec<ProjectEntryId>,
3658        project_path: Option<ProjectPath>,
3659        nav_history: Option<ItemNavHistory>,
3660        tab_descriptions: Option<Vec<&'static str>>,
3661        tab_detail: Cell<Option<usize>>,
3662    }
3663
3664    pub enum TestItemEvent {
3665        Edit,
3666    }
3667
3668    impl Clone for TestItem {
3669        fn clone(&self) -> Self {
3670            Self {
3671                state: self.state.clone(),
3672                label: self.label.clone(),
3673                save_count: self.save_count,
3674                save_as_count: self.save_as_count,
3675                reload_count: self.reload_count,
3676                is_dirty: self.is_dirty,
3677                is_singleton: self.is_singleton,
3678                has_conflict: self.has_conflict,
3679                project_entry_ids: self.project_entry_ids.clone(),
3680                project_path: self.project_path.clone(),
3681                nav_history: None,
3682                tab_descriptions: None,
3683                tab_detail: Default::default(),
3684            }
3685        }
3686    }
3687
3688    impl TestItem {
3689        pub fn new() -> Self {
3690            Self {
3691                state: String::new(),
3692                label: String::new(),
3693                save_count: 0,
3694                save_as_count: 0,
3695                reload_count: 0,
3696                is_dirty: false,
3697                has_conflict: false,
3698                project_entry_ids: Vec::new(),
3699                project_path: None,
3700                is_singleton: true,
3701                nav_history: None,
3702                tab_descriptions: None,
3703                tab_detail: Default::default(),
3704            }
3705        }
3706
3707        pub fn with_label(mut self, state: &str) -> Self {
3708            self.label = state.to_string();
3709            self
3710        }
3711
3712        pub fn with_singleton(mut self, singleton: bool) -> Self {
3713            self.is_singleton = singleton;
3714            self
3715        }
3716
3717        pub fn with_project_entry_ids(mut self, project_entry_ids: &[u64]) -> Self {
3718            self.project_entry_ids.extend(
3719                project_entry_ids
3720                    .iter()
3721                    .copied()
3722                    .map(ProjectEntryId::from_proto),
3723            );
3724            self
3725        }
3726
3727        fn set_state(&mut self, state: String, cx: &mut ViewContext<Self>) {
3728            self.push_to_nav_history(cx);
3729            self.state = state;
3730        }
3731
3732        fn push_to_nav_history(&mut self, cx: &mut ViewContext<Self>) {
3733            if let Some(history) = &mut self.nav_history {
3734                history.push(Some(Box::new(self.state.clone())), cx);
3735            }
3736        }
3737    }
3738
3739    impl Entity for TestItem {
3740        type Event = TestItemEvent;
3741    }
3742
3743    impl View for TestItem {
3744        fn ui_name() -> &'static str {
3745            "TestItem"
3746        }
3747
3748        fn render(&mut self, _: &mut RenderContext<Self>) -> ElementBox {
3749            Empty::new().boxed()
3750        }
3751    }
3752
3753    impl Item for TestItem {
3754        fn tab_description<'a>(&'a self, detail: usize, _: &'a AppContext) -> Option<Cow<'a, str>> {
3755            self.tab_descriptions.as_ref().and_then(|descriptions| {
3756                let description = *descriptions.get(detail).or_else(|| descriptions.last())?;
3757                Some(description.into())
3758            })
3759        }
3760
3761        fn tab_content(&self, detail: Option<usize>, _: &theme::Tab, _: &AppContext) -> ElementBox {
3762            self.tab_detail.set(detail);
3763            Empty::new().boxed()
3764        }
3765
3766        fn project_path(&self, _: &AppContext) -> Option<ProjectPath> {
3767            self.project_path.clone()
3768        }
3769
3770        fn project_entry_ids(&self, _: &AppContext) -> SmallVec<[ProjectEntryId; 3]> {
3771            self.project_entry_ids.iter().copied().collect()
3772        }
3773
3774        fn is_singleton(&self, _: &AppContext) -> bool {
3775            self.is_singleton
3776        }
3777
3778        fn set_nav_history(&mut self, history: ItemNavHistory, _: &mut ViewContext<Self>) {
3779            self.nav_history = Some(history);
3780        }
3781
3782        fn navigate(&mut self, state: Box<dyn Any>, _: &mut ViewContext<Self>) -> bool {
3783            let state = *state.downcast::<String>().unwrap_or_default();
3784            if state != self.state {
3785                self.state = state;
3786                true
3787            } else {
3788                false
3789            }
3790        }
3791
3792        fn deactivated(&mut self, cx: &mut ViewContext<Self>) {
3793            self.push_to_nav_history(cx);
3794        }
3795
3796        fn clone_on_split(&self, _: &mut ViewContext<Self>) -> Option<Self>
3797        where
3798            Self: Sized,
3799        {
3800            Some(self.clone())
3801        }
3802
3803        fn is_dirty(&self, _: &AppContext) -> bool {
3804            self.is_dirty
3805        }
3806
3807        fn has_conflict(&self, _: &AppContext) -> bool {
3808            self.has_conflict
3809        }
3810
3811        fn can_save(&self, _: &AppContext) -> bool {
3812            !self.project_entry_ids.is_empty()
3813        }
3814
3815        fn save(
3816            &mut self,
3817            _: ModelHandle<Project>,
3818            _: &mut ViewContext<Self>,
3819        ) -> Task<anyhow::Result<()>> {
3820            self.save_count += 1;
3821            self.is_dirty = false;
3822            Task::ready(Ok(()))
3823        }
3824
3825        fn save_as(
3826            &mut self,
3827            _: ModelHandle<Project>,
3828            _: std::path::PathBuf,
3829            _: &mut ViewContext<Self>,
3830        ) -> Task<anyhow::Result<()>> {
3831            self.save_as_count += 1;
3832            self.is_dirty = false;
3833            Task::ready(Ok(()))
3834        }
3835
3836        fn reload(
3837            &mut self,
3838            _: ModelHandle<Project>,
3839            _: &mut ViewContext<Self>,
3840        ) -> Task<anyhow::Result<()>> {
3841            self.reload_count += 1;
3842            self.is_dirty = false;
3843            Task::ready(Ok(()))
3844        }
3845
3846        fn to_item_events(_: &Self::Event) -> Vec<ItemEvent> {
3847            vec![ItemEvent::UpdateTab, ItemEvent::Edit]
3848        }
3849    }
3850
3851    impl SidebarItem for TestItem {}
3852}