workspace.rs

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