workspace.rs

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