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