item.rs

   1use crate::{
   2    pane::{self, Pane},
   3    persistence::model::ItemId,
   4    searchable::SearchableItemHandle,
   5    workspace_settings::{AutosaveSetting, WorkspaceSettings},
   6    DelayedDebouncedEditAction, FollowableViewRegistry, ItemNavHistory, SerializableItemRegistry,
   7    ToolbarItemLocation, ViewId, Workspace, WorkspaceId,
   8};
   9use anyhow::Result;
  10use client::{
  11    proto::{self, PeerId},
  12    Client,
  13};
  14use futures::{channel::mpsc, StreamExt};
  15use gpui::{
  16    AnyElement, AnyView, App, Context, Entity, EntityId, EventEmitter, FocusHandle, Focusable,
  17    Font, HighlightStyle, Pixels, Point, Render, SharedString, Task, WeakEntity, Window,
  18};
  19use project::{Project, ProjectEntryId, ProjectPath};
  20use schemars::JsonSchema;
  21use serde::{Deserialize, Serialize};
  22use settings::{Settings, SettingsLocation, SettingsSources};
  23use smallvec::SmallVec;
  24use std::{
  25    any::{Any, TypeId},
  26    cell::RefCell,
  27    ops::Range,
  28    rc::Rc,
  29    sync::Arc,
  30    time::Duration,
  31};
  32use theme::Theme;
  33use ui::{Color, Element as _, Icon, IntoElement, Label, LabelCommon};
  34use util::ResultExt;
  35
  36pub const LEADER_UPDATE_THROTTLE: Duration = Duration::from_millis(200);
  37
  38#[derive(Deserialize)]
  39pub struct ItemSettings {
  40    pub git_status: bool,
  41    pub close_position: ClosePosition,
  42    pub activate_on_close: ActivateOnClose,
  43    pub file_icons: bool,
  44    pub show_diagnostics: ShowDiagnostics,
  45    pub always_show_close_button: bool,
  46}
  47
  48#[derive(Deserialize)]
  49pub struct PreviewTabsSettings {
  50    pub enabled: bool,
  51    pub enable_preview_from_file_finder: bool,
  52    pub enable_preview_from_code_navigation: bool,
  53}
  54
  55#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
  56#[serde(rename_all = "lowercase")]
  57pub enum ClosePosition {
  58    Left,
  59    #[default]
  60    Right,
  61}
  62
  63#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
  64#[serde(rename_all = "snake_case")]
  65pub enum ShowDiagnostics {
  66    #[default]
  67    Off,
  68    Errors,
  69    All,
  70}
  71
  72#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
  73#[serde(rename_all = "snake_case")]
  74pub enum ActivateOnClose {
  75    #[default]
  76    History,
  77    Neighbour,
  78    LeftNeighbour,
  79}
  80
  81#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
  82pub struct ItemSettingsContent {
  83    /// Whether to show the Git file status on a tab item.
  84    ///
  85    /// Default: false
  86    git_status: Option<bool>,
  87    /// Position of the close button in a tab.
  88    ///
  89    /// Default: right
  90    close_position: Option<ClosePosition>,
  91    /// Whether to show the file icon for a tab.
  92    ///
  93    /// Default: false
  94    file_icons: Option<bool>,
  95    /// What to do after closing the current tab.
  96    ///
  97    /// Default: history
  98    pub activate_on_close: Option<ActivateOnClose>,
  99    /// Which files containing diagnostic errors/warnings to mark in the tabs.
 100    /// This setting can take the following three values:
 101    ///
 102    /// Default: off
 103    show_diagnostics: Option<ShowDiagnostics>,
 104    /// Whether to always show the close button on tabs.
 105    ///
 106    /// Default: false
 107    always_show_close_button: Option<bool>,
 108}
 109
 110#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
 111pub struct PreviewTabsSettingsContent {
 112    /// Whether to show opened editors as preview tabs.
 113    /// Preview tabs do not stay open, are reused until explicitly set to be kept open opened (via double-click or editing) and show file names in italic.
 114    ///
 115    /// Default: true
 116    enabled: Option<bool>,
 117    /// Whether to open tabs in preview mode when selected from the file finder.
 118    ///
 119    /// Default: false
 120    enable_preview_from_file_finder: Option<bool>,
 121    /// Whether a preview tab gets replaced when code navigation is used to navigate away from the tab.
 122    ///
 123    /// Default: false
 124    enable_preview_from_code_navigation: Option<bool>,
 125}
 126
 127impl Settings for ItemSettings {
 128    const KEY: Option<&'static str> = Some("tabs");
 129
 130    type FileContent = ItemSettingsContent;
 131
 132    fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> Result<Self> {
 133        sources.json_merge()
 134    }
 135}
 136
 137impl Settings for PreviewTabsSettings {
 138    const KEY: Option<&'static str> = Some("preview_tabs");
 139
 140    type FileContent = PreviewTabsSettingsContent;
 141
 142    fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> Result<Self> {
 143        sources.json_merge()
 144    }
 145}
 146
 147#[derive(Clone, Copy, Eq, PartialEq, Hash, Debug)]
 148pub enum ItemEvent {
 149    CloseItem,
 150    UpdateTab,
 151    UpdateBreadcrumbs,
 152    Edit,
 153}
 154
 155// TODO: Combine this with existing HighlightedText struct?
 156pub struct BreadcrumbText {
 157    pub text: String,
 158    pub highlights: Option<Vec<(Range<usize>, HighlightStyle)>>,
 159    pub font: Option<Font>,
 160}
 161
 162#[derive(Debug, Clone, Copy)]
 163pub struct TabContentParams {
 164    pub detail: Option<usize>,
 165    pub selected: bool,
 166    pub preview: bool,
 167}
 168
 169impl TabContentParams {
 170    /// Returns the text color to be used for the tab content.
 171    pub fn text_color(&self) -> Color {
 172        if self.selected {
 173            Color::Default
 174        } else {
 175            Color::Muted
 176        }
 177    }
 178}
 179
 180pub enum TabTooltipContent {
 181    Text(SharedString),
 182    Custom(Box<dyn Fn(&mut Window, &mut App) -> AnyView>),
 183}
 184
 185pub trait Item: Focusable + EventEmitter<Self::Event> + Render + Sized {
 186    type Event;
 187
 188    /// Returns the tab contents.
 189    ///
 190    /// By default this returns a [`Label`] that displays that text from
 191    /// `tab_content_text`.
 192    fn tab_content(&self, params: TabContentParams, window: &Window, cx: &App) -> AnyElement {
 193        let Some(text) = self.tab_content_text(window, cx) else {
 194            return gpui::Empty.into_any();
 195        };
 196
 197        Label::new(text)
 198            .color(params.text_color())
 199            .into_any_element()
 200    }
 201
 202    /// Returns the textual contents of the tab.
 203    ///
 204    /// Use this if you don't need to customize the tab contents.
 205    fn tab_content_text(&self, _window: &Window, _cx: &App) -> Option<SharedString> {
 206        None
 207    }
 208
 209    fn tab_icon(&self, _window: &Window, _cx: &App) -> Option<Icon> {
 210        None
 211    }
 212
 213    /// Returns the tab tooltip text.
 214    ///
 215    /// Use this if you don't need to customize the tab tooltip content.
 216    fn tab_tooltip_text(&self, _: &App) -> Option<SharedString> {
 217        None
 218    }
 219
 220    /// Returns the tab tooltip content.
 221    ///
 222    /// By default this returns a Tooltip text from
 223    /// `tab_tooltip_text`.
 224    fn tab_tooltip_content(&self, cx: &App) -> Option<TabTooltipContent> {
 225        self.tab_tooltip_text(cx).map(TabTooltipContent::Text)
 226    }
 227
 228    fn tab_description(&self, _: usize, _: &App) -> Option<SharedString> {
 229        None
 230    }
 231
 232    fn to_item_events(_event: &Self::Event, _f: impl FnMut(ItemEvent)) {}
 233
 234    fn deactivated(&mut self, _window: &mut Window, _: &mut Context<Self>) {}
 235    fn discarded(&self, _project: Entity<Project>, _window: &mut Window, _cx: &mut Context<Self>) {}
 236    fn workspace_deactivated(&mut self, _window: &mut Window, _: &mut Context<Self>) {}
 237    fn navigate(&mut self, _: Box<dyn Any>, _window: &mut Window, _: &mut Context<Self>) -> bool {
 238        false
 239    }
 240
 241    fn telemetry_event_text(&self) -> Option<&'static str> {
 242        None
 243    }
 244
 245    /// (model id, Item)
 246    fn for_each_project_item(
 247        &self,
 248        _: &App,
 249        _: &mut dyn FnMut(EntityId, &dyn project::ProjectItem),
 250    ) {
 251    }
 252    fn is_singleton(&self, _cx: &App) -> bool {
 253        false
 254    }
 255    fn set_nav_history(&mut self, _: ItemNavHistory, _window: &mut Window, _: &mut Context<Self>) {}
 256    fn clone_on_split(
 257        &self,
 258        _workspace_id: Option<WorkspaceId>,
 259        _window: &mut Window,
 260        _: &mut Context<Self>,
 261    ) -> Option<Entity<Self>>
 262    where
 263        Self: Sized,
 264    {
 265        None
 266    }
 267    fn is_dirty(&self, _: &App) -> bool {
 268        false
 269    }
 270    fn has_deleted_file(&self, _: &App) -> bool {
 271        false
 272    }
 273    fn has_conflict(&self, _: &App) -> bool {
 274        false
 275    }
 276    fn can_save(&self, _cx: &App) -> bool {
 277        false
 278    }
 279    fn save(
 280        &mut self,
 281        _format: bool,
 282        _project: Entity<Project>,
 283        _window: &mut Window,
 284        _cx: &mut Context<Self>,
 285    ) -> Task<Result<()>> {
 286        unimplemented!("save() must be implemented if can_save() returns true")
 287    }
 288    fn save_as(
 289        &mut self,
 290        _project: Entity<Project>,
 291        _path: ProjectPath,
 292        _window: &mut Window,
 293        _cx: &mut Context<Self>,
 294    ) -> Task<Result<()>> {
 295        unimplemented!("save_as() must be implemented if can_save() returns true")
 296    }
 297    fn reload(
 298        &mut self,
 299        _project: Entity<Project>,
 300        _window: &mut Window,
 301        _cx: &mut Context<Self>,
 302    ) -> Task<Result<()>> {
 303        unimplemented!("reload() must be implemented if can_save() returns true")
 304    }
 305
 306    fn act_as_type<'a>(
 307        &'a self,
 308        type_id: TypeId,
 309        self_handle: &'a Entity<Self>,
 310        _: &'a App,
 311    ) -> Option<AnyView> {
 312        if TypeId::of::<Self>() == type_id {
 313            Some(self_handle.clone().into())
 314        } else {
 315            None
 316        }
 317    }
 318
 319    fn as_searchable(&self, _: &Entity<Self>) -> Option<Box<dyn SearchableItemHandle>> {
 320        None
 321    }
 322
 323    fn breadcrumb_location(&self, _: &App) -> ToolbarItemLocation {
 324        ToolbarItemLocation::Hidden
 325    }
 326
 327    fn breadcrumbs(&self, _theme: &Theme, _cx: &App) -> Option<Vec<BreadcrumbText>> {
 328        None
 329    }
 330
 331    fn added_to_workspace(
 332        &mut self,
 333        _workspace: &mut Workspace,
 334        _window: &mut Window,
 335        _cx: &mut Context<Self>,
 336    ) {
 337    }
 338
 339    fn show_toolbar(&self) -> bool {
 340        true
 341    }
 342
 343    fn pixel_position_of_cursor(&self, _: &App) -> Option<Point<Pixels>> {
 344        None
 345    }
 346
 347    fn preserve_preview(&self, _cx: &App) -> bool {
 348        false
 349    }
 350
 351    fn include_in_nav_history() -> bool {
 352        true
 353    }
 354}
 355
 356pub trait SerializableItem: Item {
 357    fn serialized_item_kind() -> &'static str;
 358
 359    fn cleanup(
 360        workspace_id: WorkspaceId,
 361        alive_items: Vec<ItemId>,
 362        window: &mut Window,
 363        cx: &mut App,
 364    ) -> Task<Result<()>>;
 365
 366    fn deserialize(
 367        _project: Entity<Project>,
 368        _workspace: WeakEntity<Workspace>,
 369        _workspace_id: WorkspaceId,
 370        _item_id: ItemId,
 371        _window: &mut Window,
 372        _cx: &mut App,
 373    ) -> Task<Result<Entity<Self>>>;
 374
 375    fn serialize(
 376        &mut self,
 377        workspace: &mut Workspace,
 378        item_id: ItemId,
 379        closing: bool,
 380        window: &mut Window,
 381        cx: &mut Context<Self>,
 382    ) -> Option<Task<Result<()>>>;
 383
 384    fn should_serialize(&self, event: &Self::Event) -> bool;
 385}
 386
 387pub trait SerializableItemHandle: ItemHandle {
 388    fn serialized_item_kind(&self) -> &'static str;
 389    fn serialize(
 390        &self,
 391        workspace: &mut Workspace,
 392        closing: bool,
 393        window: &mut Window,
 394        cx: &mut App,
 395    ) -> Option<Task<Result<()>>>;
 396    fn should_serialize(&self, event: &dyn Any, cx: &App) -> bool;
 397}
 398
 399impl<T> SerializableItemHandle for Entity<T>
 400where
 401    T: SerializableItem,
 402{
 403    fn serialized_item_kind(&self) -> &'static str {
 404        T::serialized_item_kind()
 405    }
 406
 407    fn serialize(
 408        &self,
 409        workspace: &mut Workspace,
 410        closing: bool,
 411        window: &mut Window,
 412        cx: &mut App,
 413    ) -> Option<Task<Result<()>>> {
 414        self.update(cx, |this, cx| {
 415            this.serialize(workspace, cx.entity_id().as_u64(), closing, window, cx)
 416        })
 417    }
 418
 419    fn should_serialize(&self, event: &dyn Any, cx: &App) -> bool {
 420        event
 421            .downcast_ref::<T::Event>()
 422            .map_or(false, |event| self.read(cx).should_serialize(event))
 423    }
 424}
 425
 426pub trait ItemHandle: 'static + Send {
 427    fn item_focus_handle(&self, cx: &App) -> FocusHandle;
 428    fn subscribe_to_item_events(
 429        &self,
 430        window: &mut Window,
 431        cx: &mut App,
 432        handler: Box<dyn Fn(ItemEvent, &mut Window, &mut App)>,
 433    ) -> gpui::Subscription;
 434    fn tab_description(&self, detail: usize, cx: &App) -> Option<SharedString>;
 435    fn tab_content(&self, params: TabContentParams, window: &Window, cx: &App) -> AnyElement;
 436    fn tab_icon(&self, window: &Window, cx: &App) -> Option<Icon>;
 437    fn tab_tooltip_text(&self, cx: &App) -> Option<SharedString>;
 438    fn tab_tooltip_content(&self, cx: &App) -> Option<TabTooltipContent>;
 439    fn telemetry_event_text(&self, cx: &App) -> Option<&'static str>;
 440    fn dragged_tab_content(
 441        &self,
 442        params: TabContentParams,
 443        window: &Window,
 444        cx: &App,
 445    ) -> AnyElement;
 446    fn project_path(&self, cx: &App) -> Option<ProjectPath>;
 447    fn project_entry_ids(&self, cx: &App) -> SmallVec<[ProjectEntryId; 3]>;
 448    fn project_paths(&self, cx: &App) -> SmallVec<[ProjectPath; 3]>;
 449    fn project_item_model_ids(&self, cx: &App) -> SmallVec<[EntityId; 3]>;
 450    fn for_each_project_item(
 451        &self,
 452        _: &App,
 453        _: &mut dyn FnMut(EntityId, &dyn project::ProjectItem),
 454    );
 455    fn is_singleton(&self, cx: &App) -> bool;
 456    fn boxed_clone(&self) -> Box<dyn ItemHandle>;
 457    fn clone_on_split(
 458        &self,
 459        workspace_id: Option<WorkspaceId>,
 460        window: &mut Window,
 461        cx: &mut App,
 462    ) -> Option<Box<dyn ItemHandle>>;
 463    fn added_to_pane(
 464        &self,
 465        workspace: &mut Workspace,
 466        pane: Entity<Pane>,
 467        window: &mut Window,
 468        cx: &mut Context<Workspace>,
 469    );
 470    fn deactivated(&self, window: &mut Window, cx: &mut App);
 471    fn discarded(&self, project: Entity<Project>, window: &mut Window, cx: &mut App);
 472    fn workspace_deactivated(&self, window: &mut Window, cx: &mut App);
 473    fn navigate(&self, data: Box<dyn Any>, window: &mut Window, cx: &mut App) -> bool;
 474    fn item_id(&self) -> EntityId;
 475    fn to_any(&self) -> AnyView;
 476    fn is_dirty(&self, cx: &App) -> bool;
 477    fn has_deleted_file(&self, cx: &App) -> bool;
 478    fn has_conflict(&self, cx: &App) -> bool;
 479    fn can_save(&self, cx: &App) -> bool;
 480    fn save(
 481        &self,
 482        format: bool,
 483        project: Entity<Project>,
 484        window: &mut Window,
 485        cx: &mut App,
 486    ) -> Task<Result<()>>;
 487    fn save_as(
 488        &self,
 489        project: Entity<Project>,
 490        path: ProjectPath,
 491        window: &mut Window,
 492        cx: &mut App,
 493    ) -> Task<Result<()>>;
 494    fn reload(
 495        &self,
 496        project: Entity<Project>,
 497        window: &mut Window,
 498        cx: &mut App,
 499    ) -> Task<Result<()>>;
 500    fn act_as_type(&self, type_id: TypeId, cx: &App) -> Option<AnyView>;
 501    fn to_followable_item_handle(&self, cx: &App) -> Option<Box<dyn FollowableItemHandle>>;
 502    fn to_serializable_item_handle(&self, cx: &App) -> Option<Box<dyn SerializableItemHandle>>;
 503    fn on_release(
 504        &self,
 505        cx: &mut App,
 506        callback: Box<dyn FnOnce(&mut App) + Send>,
 507    ) -> gpui::Subscription;
 508    fn to_searchable_item_handle(&self, cx: &App) -> Option<Box<dyn SearchableItemHandle>>;
 509    fn breadcrumb_location(&self, cx: &App) -> ToolbarItemLocation;
 510    fn breadcrumbs(&self, theme: &Theme, cx: &App) -> Option<Vec<BreadcrumbText>>;
 511    fn show_toolbar(&self, cx: &App) -> bool;
 512    fn pixel_position_of_cursor(&self, cx: &App) -> Option<Point<Pixels>>;
 513    fn downgrade_item(&self) -> Box<dyn WeakItemHandle>;
 514    fn workspace_settings<'a>(&self, cx: &'a App) -> &'a WorkspaceSettings;
 515    fn preserve_preview(&self, cx: &App) -> bool;
 516    fn include_in_nav_history(&self) -> bool;
 517}
 518
 519pub trait WeakItemHandle: Send + Sync {
 520    fn id(&self) -> EntityId;
 521    fn boxed_clone(&self) -> Box<dyn WeakItemHandle>;
 522    fn upgrade(&self) -> Option<Box<dyn ItemHandle>>;
 523}
 524
 525impl dyn ItemHandle {
 526    pub fn downcast<V: 'static>(&self) -> Option<Entity<V>> {
 527        self.to_any().downcast().ok()
 528    }
 529
 530    pub fn act_as<V: 'static>(&self, cx: &App) -> Option<Entity<V>> {
 531        self.act_as_type(TypeId::of::<V>(), cx)
 532            .and_then(|t| t.downcast().ok())
 533    }
 534}
 535
 536impl<T: Item> ItemHandle for Entity<T> {
 537    fn subscribe_to_item_events(
 538        &self,
 539        window: &mut Window,
 540        cx: &mut App,
 541        handler: Box<dyn Fn(ItemEvent, &mut Window, &mut App)>,
 542    ) -> gpui::Subscription {
 543        window.subscribe(self, cx, move |_, event, window, cx| {
 544            T::to_item_events(event, |item_event| handler(item_event, window, cx));
 545        })
 546    }
 547
 548    fn item_focus_handle(&self, cx: &App) -> FocusHandle {
 549        self.read(cx).focus_handle(cx)
 550    }
 551
 552    fn telemetry_event_text(&self, cx: &App) -> Option<&'static str> {
 553        self.read(cx).telemetry_event_text()
 554    }
 555
 556    fn tab_description(&self, detail: usize, cx: &App) -> Option<SharedString> {
 557        self.read(cx).tab_description(detail, cx)
 558    }
 559
 560    fn tab_content(&self, params: TabContentParams, window: &Window, cx: &App) -> AnyElement {
 561        self.read(cx).tab_content(params, window, cx)
 562    }
 563
 564    fn tab_icon(&self, window: &Window, cx: &App) -> Option<Icon> {
 565        self.read(cx).tab_icon(window, cx)
 566    }
 567
 568    fn tab_tooltip_content(&self, cx: &App) -> Option<TabTooltipContent> {
 569        self.read(cx).tab_tooltip_content(cx)
 570    }
 571
 572    fn tab_tooltip_text(&self, cx: &App) -> Option<SharedString> {
 573        self.read(cx).tab_tooltip_text(cx)
 574    }
 575
 576    fn dragged_tab_content(
 577        &self,
 578        params: TabContentParams,
 579        window: &Window,
 580        cx: &App,
 581    ) -> AnyElement {
 582        self.read(cx).tab_content(
 583            TabContentParams {
 584                selected: true,
 585                ..params
 586            },
 587            window,
 588            cx,
 589        )
 590    }
 591
 592    fn project_path(&self, cx: &App) -> Option<ProjectPath> {
 593        let this = self.read(cx);
 594        let mut result = None;
 595        if this.is_singleton(cx) {
 596            this.for_each_project_item(cx, &mut |_, item| {
 597                result = item.project_path(cx);
 598            });
 599        }
 600        result
 601    }
 602
 603    fn workspace_settings<'a>(&self, cx: &'a App) -> &'a WorkspaceSettings {
 604        if let Some(project_path) = self.project_path(cx) {
 605            WorkspaceSettings::get(
 606                Some(SettingsLocation {
 607                    worktree_id: project_path.worktree_id,
 608                    path: &project_path.path,
 609                }),
 610                cx,
 611            )
 612        } else {
 613            WorkspaceSettings::get_global(cx)
 614        }
 615    }
 616
 617    fn project_entry_ids(&self, cx: &App) -> SmallVec<[ProjectEntryId; 3]> {
 618        let mut result = SmallVec::new();
 619        self.read(cx).for_each_project_item(cx, &mut |_, item| {
 620            if let Some(id) = item.entry_id(cx) {
 621                result.push(id);
 622            }
 623        });
 624        result
 625    }
 626
 627    fn project_paths(&self, cx: &App) -> SmallVec<[ProjectPath; 3]> {
 628        let mut result = SmallVec::new();
 629        self.read(cx).for_each_project_item(cx, &mut |_, item| {
 630            if let Some(id) = item.project_path(cx) {
 631                result.push(id);
 632            }
 633        });
 634        result
 635    }
 636
 637    fn project_item_model_ids(&self, cx: &App) -> SmallVec<[EntityId; 3]> {
 638        let mut result = SmallVec::new();
 639        self.read(cx).for_each_project_item(cx, &mut |id, _| {
 640            result.push(id);
 641        });
 642        result
 643    }
 644
 645    fn for_each_project_item(
 646        &self,
 647        cx: &App,
 648        f: &mut dyn FnMut(EntityId, &dyn project::ProjectItem),
 649    ) {
 650        self.read(cx).for_each_project_item(cx, f)
 651    }
 652
 653    fn is_singleton(&self, cx: &App) -> bool {
 654        self.read(cx).is_singleton(cx)
 655    }
 656
 657    fn boxed_clone(&self) -> Box<dyn ItemHandle> {
 658        Box::new(self.clone())
 659    }
 660
 661    fn clone_on_split(
 662        &self,
 663        workspace_id: Option<WorkspaceId>,
 664        window: &mut Window,
 665        cx: &mut App,
 666    ) -> Option<Box<dyn ItemHandle>> {
 667        self.update(cx, |item, cx| item.clone_on_split(workspace_id, window, cx))
 668            .map(|handle| Box::new(handle) as Box<dyn ItemHandle>)
 669    }
 670
 671    fn added_to_pane(
 672        &self,
 673        workspace: &mut Workspace,
 674        pane: Entity<Pane>,
 675        window: &mut Window,
 676        cx: &mut Context<Workspace>,
 677    ) {
 678        let weak_item = self.downgrade();
 679        let history = pane.read(cx).nav_history_for_item(self);
 680        self.update(cx, |this, cx| {
 681            this.set_nav_history(history, window, cx);
 682            this.added_to_workspace(workspace, window, cx);
 683        });
 684
 685        if let Some(serializable_item) = self.to_serializable_item_handle(cx) {
 686            workspace
 687                .enqueue_item_serialization(serializable_item)
 688                .log_err();
 689        }
 690
 691        if workspace
 692            .panes_by_item
 693            .insert(self.item_id(), pane.downgrade())
 694            .is_none()
 695        {
 696            let mut pending_autosave = DelayedDebouncedEditAction::new();
 697            let (pending_update_tx, mut pending_update_rx) = mpsc::unbounded();
 698            let pending_update = Rc::new(RefCell::new(None));
 699
 700            let mut send_follower_updates = None;
 701            if let Some(item) = self.to_followable_item_handle(cx) {
 702                let is_project_item = item.is_project_item(window, cx);
 703                let item = item.downgrade();
 704
 705                send_follower_updates = Some(cx.spawn_in(window, {
 706                    let pending_update = pending_update.clone();
 707                    |workspace, mut cx| async move {
 708                        while let Some(mut leader_id) = pending_update_rx.next().await {
 709                            while let Ok(Some(id)) = pending_update_rx.try_next() {
 710                                leader_id = id;
 711                            }
 712
 713                            workspace.update_in(&mut cx, |workspace, window, cx| {
 714                                let Some(item) = item.upgrade() else { return };
 715                                workspace.update_followers(
 716                                    is_project_item,
 717                                    proto::update_followers::Variant::UpdateView(
 718                                        proto::UpdateView {
 719                                            id: item
 720                                                .remote_id(workspace.client(), window, cx)
 721                                                .map(|id| id.to_proto()),
 722                                            variant: pending_update.borrow_mut().take(),
 723                                            leader_id,
 724                                        },
 725                                    ),
 726                                    window,
 727                                    cx,
 728                                );
 729                            })?;
 730                            cx.background_executor().timer(LEADER_UPDATE_THROTTLE).await;
 731                        }
 732                        anyhow::Ok(())
 733                    }
 734                }));
 735            }
 736
 737            let mut event_subscription = Some(cx.subscribe_in(
 738                self,
 739                window,
 740                move |workspace, item: &Entity<T>, event, window, cx| {
 741                    let pane = if let Some(pane) = workspace
 742                        .panes_by_item
 743                        .get(&item.item_id())
 744                        .and_then(|pane| pane.upgrade())
 745                    {
 746                        pane
 747                    } else {
 748                        return;
 749                    };
 750
 751                    if let Some(item) = item.to_followable_item_handle(cx) {
 752                        let leader_id = workspace.leader_for_pane(&pane);
 753
 754                        if let Some(leader_id) = leader_id {
 755                            if let Some(FollowEvent::Unfollow) = item.to_follow_event(event) {
 756                                workspace.unfollow(leader_id, window, cx);
 757                            }
 758                        }
 759
 760                        if item.item_focus_handle(cx).contains_focused(window, cx) {
 761                            item.add_event_to_update_proto(
 762                                event,
 763                                &mut pending_update.borrow_mut(),
 764                                window,
 765                                cx,
 766                            );
 767                            pending_update_tx.unbounded_send(leader_id).ok();
 768                        }
 769                    }
 770
 771                    if let Some(item) = item.to_serializable_item_handle(cx) {
 772                        if item.should_serialize(event, cx) {
 773                            workspace.enqueue_item_serialization(item).ok();
 774                        }
 775                    }
 776
 777                    T::to_item_events(event, |event| match event {
 778                        ItemEvent::CloseItem => {
 779                            pane.update(cx, |pane, cx| {
 780                                pane.close_item_by_id(
 781                                    item.item_id(),
 782                                    crate::SaveIntent::Close,
 783                                    window,
 784                                    cx,
 785                                )
 786                            })
 787                            .detach_and_log_err(cx);
 788                        }
 789
 790                        ItemEvent::UpdateTab => {
 791                            pane.update(cx, |_, cx| {
 792                                cx.emit(pane::Event::ChangeItemTitle);
 793                                cx.notify();
 794                            });
 795                        }
 796
 797                        ItemEvent::Edit => {
 798                            let autosave = item.workspace_settings(cx).autosave;
 799
 800                            if let AutosaveSetting::AfterDelay { milliseconds } = autosave {
 801                                let delay = Duration::from_millis(milliseconds);
 802                                let item = item.clone();
 803                                pending_autosave.fire_new(
 804                                    delay,
 805                                    window,
 806                                    cx,
 807                                    move |workspace, window, cx| {
 808                                        Pane::autosave_item(
 809                                            &item,
 810                                            workspace.project().clone(),
 811                                            window,
 812                                            cx,
 813                                        )
 814                                    },
 815                                );
 816                            }
 817                            pane.update(cx, |pane, cx| pane.handle_item_edit(item.item_id(), cx));
 818                        }
 819
 820                        _ => {}
 821                    });
 822                },
 823            ));
 824
 825            cx.on_blur(
 826                &self.read(cx).focus_handle(cx),
 827                window,
 828                move |workspace, window, cx| {
 829                    if let Some(item) = weak_item.upgrade() {
 830                        if item.workspace_settings(cx).autosave == AutosaveSetting::OnFocusChange {
 831                            Pane::autosave_item(&item, workspace.project.clone(), window, cx)
 832                                .detach_and_log_err(cx);
 833                        }
 834                    }
 835                },
 836            )
 837            .detach();
 838
 839            let item_id = self.item_id();
 840            cx.observe_release_in(self, window, move |workspace, _, _, _| {
 841                workspace.panes_by_item.remove(&item_id);
 842                event_subscription.take();
 843                send_follower_updates.take();
 844            })
 845            .detach();
 846        }
 847
 848        cx.defer_in(window, |workspace, window, cx| {
 849            workspace.serialize_workspace(window, cx);
 850        });
 851    }
 852
 853    fn discarded(&self, project: Entity<Project>, window: &mut Window, cx: &mut App) {
 854        self.update(cx, |this, cx| this.discarded(project, window, cx));
 855    }
 856
 857    fn deactivated(&self, window: &mut Window, cx: &mut App) {
 858        self.update(cx, |this, cx| this.deactivated(window, cx));
 859    }
 860
 861    fn workspace_deactivated(&self, window: &mut Window, cx: &mut App) {
 862        self.update(cx, |this, cx| this.workspace_deactivated(window, cx));
 863    }
 864
 865    fn navigate(&self, data: Box<dyn Any>, window: &mut Window, cx: &mut App) -> bool {
 866        self.update(cx, |this, cx| this.navigate(data, window, cx))
 867    }
 868
 869    fn item_id(&self) -> EntityId {
 870        self.entity_id()
 871    }
 872
 873    fn to_any(&self) -> AnyView {
 874        self.clone().into()
 875    }
 876
 877    fn is_dirty(&self, cx: &App) -> bool {
 878        self.read(cx).is_dirty(cx)
 879    }
 880
 881    fn has_deleted_file(&self, cx: &App) -> bool {
 882        self.read(cx).has_deleted_file(cx)
 883    }
 884
 885    fn has_conflict(&self, cx: &App) -> bool {
 886        self.read(cx).has_conflict(cx)
 887    }
 888
 889    fn can_save(&self, cx: &App) -> bool {
 890        self.read(cx).can_save(cx)
 891    }
 892
 893    fn save(
 894        &self,
 895        format: bool,
 896        project: Entity<Project>,
 897        window: &mut Window,
 898        cx: &mut App,
 899    ) -> Task<Result<()>> {
 900        self.update(cx, |item, cx| item.save(format, project, window, cx))
 901    }
 902
 903    fn save_as(
 904        &self,
 905        project: Entity<Project>,
 906        path: ProjectPath,
 907        window: &mut Window,
 908        cx: &mut App,
 909    ) -> Task<anyhow::Result<()>> {
 910        self.update(cx, |item, cx| item.save_as(project, path, window, cx))
 911    }
 912
 913    fn reload(
 914        &self,
 915        project: Entity<Project>,
 916        window: &mut Window,
 917        cx: &mut App,
 918    ) -> Task<Result<()>> {
 919        self.update(cx, |item, cx| item.reload(project, window, cx))
 920    }
 921
 922    fn act_as_type<'a>(&'a self, type_id: TypeId, cx: &'a App) -> Option<AnyView> {
 923        self.read(cx).act_as_type(type_id, self, cx)
 924    }
 925
 926    fn to_followable_item_handle(&self, cx: &App) -> Option<Box<dyn FollowableItemHandle>> {
 927        FollowableViewRegistry::to_followable_view(self.clone(), cx)
 928    }
 929
 930    fn on_release(
 931        &self,
 932        cx: &mut App,
 933        callback: Box<dyn FnOnce(&mut App) + Send>,
 934    ) -> gpui::Subscription {
 935        cx.observe_release(self, move |_, cx| callback(cx))
 936    }
 937
 938    fn to_searchable_item_handle(&self, cx: &App) -> Option<Box<dyn SearchableItemHandle>> {
 939        self.read(cx).as_searchable(self)
 940    }
 941
 942    fn breadcrumb_location(&self, cx: &App) -> ToolbarItemLocation {
 943        self.read(cx).breadcrumb_location(cx)
 944    }
 945
 946    fn breadcrumbs(&self, theme: &Theme, cx: &App) -> Option<Vec<BreadcrumbText>> {
 947        self.read(cx).breadcrumbs(theme, cx)
 948    }
 949
 950    fn show_toolbar(&self, cx: &App) -> bool {
 951        self.read(cx).show_toolbar()
 952    }
 953
 954    fn pixel_position_of_cursor(&self, cx: &App) -> Option<Point<Pixels>> {
 955        self.read(cx).pixel_position_of_cursor(cx)
 956    }
 957
 958    fn downgrade_item(&self) -> Box<dyn WeakItemHandle> {
 959        Box::new(self.downgrade())
 960    }
 961
 962    fn to_serializable_item_handle(&self, cx: &App) -> Option<Box<dyn SerializableItemHandle>> {
 963        SerializableItemRegistry::view_to_serializable_item_handle(self.to_any(), cx)
 964    }
 965
 966    fn preserve_preview(&self, cx: &App) -> bool {
 967        self.read(cx).preserve_preview(cx)
 968    }
 969
 970    fn include_in_nav_history(&self) -> bool {
 971        T::include_in_nav_history()
 972    }
 973}
 974
 975impl From<Box<dyn ItemHandle>> for AnyView {
 976    fn from(val: Box<dyn ItemHandle>) -> Self {
 977        val.to_any()
 978    }
 979}
 980
 981impl From<&Box<dyn ItemHandle>> for AnyView {
 982    fn from(val: &Box<dyn ItemHandle>) -> Self {
 983        val.to_any()
 984    }
 985}
 986
 987impl Clone for Box<dyn ItemHandle> {
 988    fn clone(&self) -> Box<dyn ItemHandle> {
 989        self.boxed_clone()
 990    }
 991}
 992
 993impl<T: Item> WeakItemHandle for WeakEntity<T> {
 994    fn id(&self) -> EntityId {
 995        self.entity_id()
 996    }
 997
 998    fn boxed_clone(&self) -> Box<dyn WeakItemHandle> {
 999        Box::new(self.clone())
1000    }
1001
1002    fn upgrade(&self) -> Option<Box<dyn ItemHandle>> {
1003        self.upgrade().map(|v| Box::new(v) as Box<dyn ItemHandle>)
1004    }
1005}
1006
1007pub trait ProjectItem: Item {
1008    type Item: project::ProjectItem;
1009
1010    fn for_project_item(
1011        project: Entity<Project>,
1012        item: Entity<Self::Item>,
1013        window: &mut Window,
1014        cx: &mut Context<Self>,
1015    ) -> Self
1016    where
1017        Self: Sized;
1018}
1019
1020#[derive(Debug)]
1021pub enum FollowEvent {
1022    Unfollow,
1023}
1024
1025pub enum Dedup {
1026    KeepExisting,
1027    ReplaceExisting,
1028}
1029
1030pub trait FollowableItem: Item {
1031    fn remote_id(&self) -> Option<ViewId>;
1032    fn to_state_proto(&self, window: &Window, cx: &App) -> Option<proto::view::Variant>;
1033    fn from_state_proto(
1034        project: Entity<Workspace>,
1035        id: ViewId,
1036        state: &mut Option<proto::view::Variant>,
1037        window: &mut Window,
1038        cx: &mut App,
1039    ) -> Option<Task<Result<Entity<Self>>>>;
1040    fn to_follow_event(event: &Self::Event) -> Option<FollowEvent>;
1041    fn add_event_to_update_proto(
1042        &self,
1043        event: &Self::Event,
1044        update: &mut Option<proto::update_view::Variant>,
1045        window: &Window,
1046        cx: &App,
1047    ) -> bool;
1048    fn apply_update_proto(
1049        &mut self,
1050        project: &Entity<Project>,
1051        message: proto::update_view::Variant,
1052        window: &mut Window,
1053        cx: &mut Context<Self>,
1054    ) -> Task<Result<()>>;
1055    fn is_project_item(&self, window: &Window, cx: &App) -> bool;
1056    fn set_leader_peer_id(
1057        &mut self,
1058        leader_peer_id: Option<PeerId>,
1059        window: &mut Window,
1060        cx: &mut Context<Self>,
1061    );
1062    fn dedup(&self, existing: &Self, window: &Window, cx: &App) -> Option<Dedup>;
1063}
1064
1065pub trait FollowableItemHandle: ItemHandle {
1066    fn remote_id(&self, client: &Arc<Client>, window: &mut Window, cx: &mut App) -> Option<ViewId>;
1067    fn downgrade(&self) -> Box<dyn WeakFollowableItemHandle>;
1068    fn set_leader_peer_id(&self, leader_peer_id: Option<PeerId>, window: &mut Window, cx: &mut App);
1069    fn to_state_proto(&self, window: &mut Window, cx: &mut App) -> Option<proto::view::Variant>;
1070    fn add_event_to_update_proto(
1071        &self,
1072        event: &dyn Any,
1073        update: &mut Option<proto::update_view::Variant>,
1074        window: &mut Window,
1075        cx: &mut App,
1076    ) -> bool;
1077    fn to_follow_event(&self, event: &dyn Any) -> Option<FollowEvent>;
1078    fn apply_update_proto(
1079        &self,
1080        project: &Entity<Project>,
1081        message: proto::update_view::Variant,
1082        window: &mut Window,
1083        cx: &mut App,
1084    ) -> Task<Result<()>>;
1085    fn is_project_item(&self, window: &mut Window, cx: &mut App) -> bool;
1086    fn dedup(
1087        &self,
1088        existing: &dyn FollowableItemHandle,
1089        window: &mut Window,
1090        cx: &mut App,
1091    ) -> Option<Dedup>;
1092}
1093
1094impl<T: FollowableItem> FollowableItemHandle for Entity<T> {
1095    fn remote_id(&self, client: &Arc<Client>, _: &mut Window, cx: &mut App) -> Option<ViewId> {
1096        self.read(cx).remote_id().or_else(|| {
1097            client.peer_id().map(|creator| ViewId {
1098                creator,
1099                id: self.item_id().as_u64(),
1100            })
1101        })
1102    }
1103
1104    fn downgrade(&self) -> Box<dyn WeakFollowableItemHandle> {
1105        Box::new(self.downgrade())
1106    }
1107
1108    fn set_leader_peer_id(
1109        &self,
1110        leader_peer_id: Option<PeerId>,
1111        window: &mut Window,
1112        cx: &mut App,
1113    ) {
1114        self.update(cx, |this, cx| {
1115            this.set_leader_peer_id(leader_peer_id, window, cx)
1116        })
1117    }
1118
1119    fn to_state_proto(&self, window: &mut Window, cx: &mut App) -> Option<proto::view::Variant> {
1120        self.read(cx).to_state_proto(window, cx)
1121    }
1122
1123    fn add_event_to_update_proto(
1124        &self,
1125        event: &dyn Any,
1126        update: &mut Option<proto::update_view::Variant>,
1127        window: &mut Window,
1128        cx: &mut App,
1129    ) -> bool {
1130        if let Some(event) = event.downcast_ref() {
1131            self.read(cx)
1132                .add_event_to_update_proto(event, update, window, cx)
1133        } else {
1134            false
1135        }
1136    }
1137
1138    fn to_follow_event(&self, event: &dyn Any) -> Option<FollowEvent> {
1139        T::to_follow_event(event.downcast_ref()?)
1140    }
1141
1142    fn apply_update_proto(
1143        &self,
1144        project: &Entity<Project>,
1145        message: proto::update_view::Variant,
1146        window: &mut Window,
1147        cx: &mut App,
1148    ) -> Task<Result<()>> {
1149        self.update(cx, |this, cx| {
1150            this.apply_update_proto(project, message, window, cx)
1151        })
1152    }
1153
1154    fn is_project_item(&self, window: &mut Window, cx: &mut App) -> bool {
1155        self.read(cx).is_project_item(window, cx)
1156    }
1157
1158    fn dedup(
1159        &self,
1160        existing: &dyn FollowableItemHandle,
1161        window: &mut Window,
1162        cx: &mut App,
1163    ) -> Option<Dedup> {
1164        let existing = existing.to_any().downcast::<T>().ok()?;
1165        self.read(cx).dedup(existing.read(cx), window, cx)
1166    }
1167}
1168
1169pub trait WeakFollowableItemHandle: Send + Sync {
1170    fn upgrade(&self) -> Option<Box<dyn FollowableItemHandle>>;
1171}
1172
1173impl<T: FollowableItem> WeakFollowableItemHandle for WeakEntity<T> {
1174    fn upgrade(&self) -> Option<Box<dyn FollowableItemHandle>> {
1175        Some(Box::new(self.upgrade()?))
1176    }
1177}
1178
1179#[cfg(any(test, feature = "test-support"))]
1180pub mod test {
1181    use super::{Item, ItemEvent, SerializableItem, TabContentParams};
1182    use crate::{ItemId, ItemNavHistory, Workspace, WorkspaceId};
1183    use gpui::{
1184        AnyElement, App, AppContext as _, Context, Entity, EntityId, EventEmitter, Focusable,
1185        InteractiveElement, IntoElement, Render, SharedString, Task, WeakEntity, Window,
1186    };
1187    use project::{Project, ProjectEntryId, ProjectPath, WorktreeId};
1188    use std::{any::Any, cell::Cell, path::Path};
1189
1190    pub struct TestProjectItem {
1191        pub entry_id: Option<ProjectEntryId>,
1192        pub project_path: Option<ProjectPath>,
1193        pub is_dirty: bool,
1194    }
1195
1196    pub struct TestItem {
1197        pub workspace_id: Option<WorkspaceId>,
1198        pub state: String,
1199        pub label: String,
1200        pub save_count: usize,
1201        pub save_as_count: usize,
1202        pub reload_count: usize,
1203        pub is_dirty: bool,
1204        pub is_singleton: bool,
1205        pub has_conflict: bool,
1206        pub project_items: Vec<Entity<TestProjectItem>>,
1207        pub nav_history: Option<ItemNavHistory>,
1208        pub tab_descriptions: Option<Vec<&'static str>>,
1209        pub tab_detail: Cell<Option<usize>>,
1210        serialize: Option<Box<dyn Fn() -> Option<Task<anyhow::Result<()>>>>>,
1211        focus_handle: gpui::FocusHandle,
1212    }
1213
1214    impl project::ProjectItem for TestProjectItem {
1215        fn try_open(
1216            _project: &Entity<Project>,
1217            _path: &ProjectPath,
1218            _cx: &mut App,
1219        ) -> Option<Task<gpui::Result<Entity<Self>>>> {
1220            None
1221        }
1222        fn entry_id(&self, _: &App) -> Option<ProjectEntryId> {
1223            self.entry_id
1224        }
1225
1226        fn project_path(&self, _: &App) -> Option<ProjectPath> {
1227            self.project_path.clone()
1228        }
1229
1230        fn is_dirty(&self) -> bool {
1231            self.is_dirty
1232        }
1233    }
1234
1235    pub enum TestItemEvent {
1236        Edit,
1237    }
1238
1239    impl TestProjectItem {
1240        pub fn new(id: u64, path: &str, cx: &mut App) -> Entity<Self> {
1241            let entry_id = Some(ProjectEntryId::from_proto(id));
1242            let project_path = Some(ProjectPath {
1243                worktree_id: WorktreeId::from_usize(0),
1244                path: Path::new(path).into(),
1245            });
1246            cx.new(|_| Self {
1247                entry_id,
1248                project_path,
1249                is_dirty: false,
1250            })
1251        }
1252
1253        pub fn new_untitled(cx: &mut App) -> Entity<Self> {
1254            cx.new(|_| Self {
1255                project_path: None,
1256                entry_id: None,
1257                is_dirty: false,
1258            })
1259        }
1260    }
1261
1262    impl TestItem {
1263        pub fn new(cx: &mut Context<Self>) -> Self {
1264            Self {
1265                state: String::new(),
1266                label: String::new(),
1267                save_count: 0,
1268                save_as_count: 0,
1269                reload_count: 0,
1270                is_dirty: false,
1271                has_conflict: false,
1272                project_items: Vec::new(),
1273                is_singleton: true,
1274                nav_history: None,
1275                tab_descriptions: None,
1276                tab_detail: Default::default(),
1277                workspace_id: Default::default(),
1278                focus_handle: cx.focus_handle(),
1279                serialize: None,
1280            }
1281        }
1282
1283        pub fn new_deserialized(id: WorkspaceId, cx: &mut Context<Self>) -> Self {
1284            let mut this = Self::new(cx);
1285            this.workspace_id = Some(id);
1286            this
1287        }
1288
1289        pub fn with_label(mut self, state: &str) -> Self {
1290            self.label = state.to_string();
1291            self
1292        }
1293
1294        pub fn with_singleton(mut self, singleton: bool) -> Self {
1295            self.is_singleton = singleton;
1296            self
1297        }
1298
1299        pub fn with_dirty(mut self, dirty: bool) -> Self {
1300            self.is_dirty = dirty;
1301            self
1302        }
1303
1304        pub fn with_conflict(mut self, has_conflict: bool) -> Self {
1305            self.has_conflict = has_conflict;
1306            self
1307        }
1308
1309        pub fn with_project_items(mut self, items: &[Entity<TestProjectItem>]) -> Self {
1310            self.project_items.clear();
1311            self.project_items.extend(items.iter().cloned());
1312            self
1313        }
1314
1315        pub fn with_serialize(
1316            mut self,
1317            serialize: impl Fn() -> Option<Task<anyhow::Result<()>>> + 'static,
1318        ) -> Self {
1319            self.serialize = Some(Box::new(serialize));
1320            self
1321        }
1322
1323        pub fn set_state(&mut self, state: String, cx: &mut Context<Self>) {
1324            self.push_to_nav_history(cx);
1325            self.state = state;
1326        }
1327
1328        fn push_to_nav_history(&mut self, cx: &mut Context<Self>) {
1329            if let Some(history) = &mut self.nav_history {
1330                history.push(Some(Box::new(self.state.clone())), cx);
1331            }
1332        }
1333    }
1334
1335    impl Render for TestItem {
1336        fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
1337            gpui::div().track_focus(&self.focus_handle(cx))
1338        }
1339    }
1340
1341    impl EventEmitter<ItemEvent> for TestItem {}
1342
1343    impl Focusable for TestItem {
1344        fn focus_handle(&self, _: &App) -> gpui::FocusHandle {
1345            self.focus_handle.clone()
1346        }
1347    }
1348
1349    impl Item for TestItem {
1350        type Event = ItemEvent;
1351
1352        fn to_item_events(event: &Self::Event, mut f: impl FnMut(ItemEvent)) {
1353            f(*event)
1354        }
1355
1356        fn tab_description(&self, detail: usize, _: &App) -> Option<SharedString> {
1357            self.tab_descriptions.as_ref().and_then(|descriptions| {
1358                let description = *descriptions.get(detail).or_else(|| descriptions.last())?;
1359                Some(description.into())
1360            })
1361        }
1362
1363        fn telemetry_event_text(&self) -> Option<&'static str> {
1364            None
1365        }
1366
1367        fn tab_content(&self, params: TabContentParams, _window: &Window, _cx: &App) -> AnyElement {
1368            self.tab_detail.set(params.detail);
1369            gpui::div().into_any_element()
1370        }
1371
1372        fn for_each_project_item(
1373            &self,
1374            cx: &App,
1375            f: &mut dyn FnMut(EntityId, &dyn project::ProjectItem),
1376        ) {
1377            self.project_items
1378                .iter()
1379                .for_each(|item| f(item.entity_id(), item.read(cx)))
1380        }
1381
1382        fn is_singleton(&self, _: &App) -> bool {
1383            self.is_singleton
1384        }
1385
1386        fn set_nav_history(
1387            &mut self,
1388            history: ItemNavHistory,
1389            _window: &mut Window,
1390            _: &mut Context<Self>,
1391        ) {
1392            self.nav_history = Some(history);
1393        }
1394
1395        fn navigate(
1396            &mut self,
1397            state: Box<dyn Any>,
1398            _window: &mut Window,
1399            _: &mut Context<Self>,
1400        ) -> bool {
1401            let state = *state.downcast::<String>().unwrap_or_default();
1402            if state != self.state {
1403                self.state = state;
1404                true
1405            } else {
1406                false
1407            }
1408        }
1409
1410        fn deactivated(&mut self, _window: &mut Window, cx: &mut Context<Self>) {
1411            self.push_to_nav_history(cx);
1412        }
1413
1414        fn clone_on_split(
1415            &self,
1416            _workspace_id: Option<WorkspaceId>,
1417            _: &mut Window,
1418            cx: &mut Context<Self>,
1419        ) -> Option<Entity<Self>>
1420        where
1421            Self: Sized,
1422        {
1423            Some(cx.new(|cx| Self {
1424                state: self.state.clone(),
1425                label: self.label.clone(),
1426                save_count: self.save_count,
1427                save_as_count: self.save_as_count,
1428                reload_count: self.reload_count,
1429                is_dirty: self.is_dirty,
1430                is_singleton: self.is_singleton,
1431                has_conflict: self.has_conflict,
1432                project_items: self.project_items.clone(),
1433                nav_history: None,
1434                tab_descriptions: None,
1435                tab_detail: Default::default(),
1436                workspace_id: self.workspace_id,
1437                focus_handle: cx.focus_handle(),
1438                serialize: None,
1439            }))
1440        }
1441
1442        fn is_dirty(&self, _: &App) -> bool {
1443            self.is_dirty
1444        }
1445
1446        fn has_conflict(&self, _: &App) -> bool {
1447            self.has_conflict
1448        }
1449
1450        fn can_save(&self, cx: &App) -> bool {
1451            !self.project_items.is_empty()
1452                && self
1453                    .project_items
1454                    .iter()
1455                    .all(|item| item.read(cx).entry_id.is_some())
1456        }
1457
1458        fn save(
1459            &mut self,
1460            _: bool,
1461            _: Entity<Project>,
1462            _window: &mut Window,
1463            _: &mut Context<Self>,
1464        ) -> Task<anyhow::Result<()>> {
1465            self.save_count += 1;
1466            self.is_dirty = false;
1467            Task::ready(Ok(()))
1468        }
1469
1470        fn save_as(
1471            &mut self,
1472            _: Entity<Project>,
1473            _: ProjectPath,
1474            _window: &mut Window,
1475            _: &mut Context<Self>,
1476        ) -> Task<anyhow::Result<()>> {
1477            self.save_as_count += 1;
1478            self.is_dirty = false;
1479            Task::ready(Ok(()))
1480        }
1481
1482        fn reload(
1483            &mut self,
1484            _: Entity<Project>,
1485            _window: &mut Window,
1486            _: &mut Context<Self>,
1487        ) -> Task<anyhow::Result<()>> {
1488            self.reload_count += 1;
1489            self.is_dirty = false;
1490            Task::ready(Ok(()))
1491        }
1492    }
1493
1494    impl SerializableItem for TestItem {
1495        fn serialized_item_kind() -> &'static str {
1496            "TestItem"
1497        }
1498
1499        fn deserialize(
1500            _project: Entity<Project>,
1501            _workspace: WeakEntity<Workspace>,
1502            workspace_id: WorkspaceId,
1503            _item_id: ItemId,
1504            _window: &mut Window,
1505            cx: &mut App,
1506        ) -> Task<anyhow::Result<Entity<Self>>> {
1507            let entity = cx.new(|cx| Self::new_deserialized(workspace_id, cx));
1508            Task::ready(Ok(entity))
1509        }
1510
1511        fn cleanup(
1512            _workspace_id: WorkspaceId,
1513            _alive_items: Vec<ItemId>,
1514            _window: &mut Window,
1515            _cx: &mut App,
1516        ) -> Task<anyhow::Result<()>> {
1517            Task::ready(Ok(()))
1518        }
1519
1520        fn serialize(
1521            &mut self,
1522            _workspace: &mut Workspace,
1523            _item_id: ItemId,
1524            _closing: bool,
1525            _window: &mut Window,
1526            _cx: &mut Context<Self>,
1527        ) -> Option<Task<anyhow::Result<()>>> {
1528            if let Some(serialize) = self.serialize.take() {
1529                let result = serialize();
1530                self.serialize = Some(serialize);
1531                result
1532            } else {
1533                None
1534            }
1535        }
1536
1537        fn should_serialize(&self, _event: &Self::Event) -> bool {
1538            false
1539        }
1540    }
1541}