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 can_save_as(&self, _: &App) -> bool {
 280        false
 281    }
 282    fn save(
 283        &mut self,
 284        _format: bool,
 285        _project: Entity<Project>,
 286        _window: &mut Window,
 287        _cx: &mut Context<Self>,
 288    ) -> Task<Result<()>> {
 289        unimplemented!("save() must be implemented if can_save() returns true")
 290    }
 291    fn save_as(
 292        &mut self,
 293        _project: Entity<Project>,
 294        _path: ProjectPath,
 295        _window: &mut Window,
 296        _cx: &mut Context<Self>,
 297    ) -> Task<Result<()>> {
 298        unimplemented!("save_as() must be implemented if can_save() returns true")
 299    }
 300    fn reload(
 301        &mut self,
 302        _project: Entity<Project>,
 303        _window: &mut Window,
 304        _cx: &mut Context<Self>,
 305    ) -> Task<Result<()>> {
 306        unimplemented!("reload() must be implemented if can_save() returns true")
 307    }
 308
 309    fn act_as_type<'a>(
 310        &'a self,
 311        type_id: TypeId,
 312        self_handle: &'a Entity<Self>,
 313        _: &'a App,
 314    ) -> Option<AnyView> {
 315        if TypeId::of::<Self>() == type_id {
 316            Some(self_handle.clone().into())
 317        } else {
 318            None
 319        }
 320    }
 321
 322    fn as_searchable(&self, _: &Entity<Self>) -> Option<Box<dyn SearchableItemHandle>> {
 323        None
 324    }
 325
 326    fn breadcrumb_location(&self, _: &App) -> ToolbarItemLocation {
 327        ToolbarItemLocation::Hidden
 328    }
 329
 330    fn breadcrumbs(&self, _theme: &Theme, _cx: &App) -> Option<Vec<BreadcrumbText>> {
 331        None
 332    }
 333
 334    fn added_to_workspace(
 335        &mut self,
 336        _workspace: &mut Workspace,
 337        _window: &mut Window,
 338        _cx: &mut Context<Self>,
 339    ) {
 340    }
 341
 342    fn show_toolbar(&self) -> bool {
 343        true
 344    }
 345
 346    fn pixel_position_of_cursor(&self, _: &App) -> Option<Point<Pixels>> {
 347        None
 348    }
 349
 350    fn preserve_preview(&self, _cx: &App) -> bool {
 351        false
 352    }
 353
 354    fn include_in_nav_history() -> bool {
 355        true
 356    }
 357}
 358
 359pub trait SerializableItem: Item {
 360    fn serialized_item_kind() -> &'static str;
 361
 362    fn cleanup(
 363        workspace_id: WorkspaceId,
 364        alive_items: Vec<ItemId>,
 365        window: &mut Window,
 366        cx: &mut App,
 367    ) -> Task<Result<()>>;
 368
 369    fn deserialize(
 370        _project: Entity<Project>,
 371        _workspace: WeakEntity<Workspace>,
 372        _workspace_id: WorkspaceId,
 373        _item_id: ItemId,
 374        _window: &mut Window,
 375        _cx: &mut App,
 376    ) -> Task<Result<Entity<Self>>>;
 377
 378    fn serialize(
 379        &mut self,
 380        workspace: &mut Workspace,
 381        item_id: ItemId,
 382        closing: bool,
 383        window: &mut Window,
 384        cx: &mut Context<Self>,
 385    ) -> Option<Task<Result<()>>>;
 386
 387    fn should_serialize(&self, event: &Self::Event) -> bool;
 388}
 389
 390pub trait SerializableItemHandle: ItemHandle {
 391    fn serialized_item_kind(&self) -> &'static str;
 392    fn serialize(
 393        &self,
 394        workspace: &mut Workspace,
 395        closing: bool,
 396        window: &mut Window,
 397        cx: &mut App,
 398    ) -> Option<Task<Result<()>>>;
 399    fn should_serialize(&self, event: &dyn Any, cx: &App) -> bool;
 400}
 401
 402impl<T> SerializableItemHandle for Entity<T>
 403where
 404    T: SerializableItem,
 405{
 406    fn serialized_item_kind(&self) -> &'static str {
 407        T::serialized_item_kind()
 408    }
 409
 410    fn serialize(
 411        &self,
 412        workspace: &mut Workspace,
 413        closing: bool,
 414        window: &mut Window,
 415        cx: &mut App,
 416    ) -> Option<Task<Result<()>>> {
 417        self.update(cx, |this, cx| {
 418            this.serialize(workspace, cx.entity_id().as_u64(), closing, window, cx)
 419        })
 420    }
 421
 422    fn should_serialize(&self, event: &dyn Any, cx: &App) -> bool {
 423        event
 424            .downcast_ref::<T::Event>()
 425            .map_or(false, |event| self.read(cx).should_serialize(event))
 426    }
 427}
 428
 429pub trait ItemHandle: 'static + Send {
 430    fn item_focus_handle(&self, cx: &App) -> FocusHandle;
 431    fn subscribe_to_item_events(
 432        &self,
 433        window: &mut Window,
 434        cx: &mut App,
 435        handler: Box<dyn Fn(ItemEvent, &mut Window, &mut App)>,
 436    ) -> gpui::Subscription;
 437    fn tab_description(&self, detail: usize, cx: &App) -> Option<SharedString>;
 438    fn tab_content(&self, params: TabContentParams, window: &Window, cx: &App) -> AnyElement;
 439    fn tab_icon(&self, window: &Window, cx: &App) -> Option<Icon>;
 440    fn tab_tooltip_text(&self, cx: &App) -> Option<SharedString>;
 441    fn tab_tooltip_content(&self, cx: &App) -> Option<TabTooltipContent>;
 442    fn telemetry_event_text(&self, cx: &App) -> Option<&'static str>;
 443    fn dragged_tab_content(
 444        &self,
 445        params: TabContentParams,
 446        window: &Window,
 447        cx: &App,
 448    ) -> AnyElement;
 449    fn project_path(&self, cx: &App) -> Option<ProjectPath>;
 450    fn project_entry_ids(&self, cx: &App) -> SmallVec<[ProjectEntryId; 3]>;
 451    fn project_paths(&self, cx: &App) -> SmallVec<[ProjectPath; 3]>;
 452    fn project_item_model_ids(&self, cx: &App) -> SmallVec<[EntityId; 3]>;
 453    fn for_each_project_item(
 454        &self,
 455        _: &App,
 456        _: &mut dyn FnMut(EntityId, &dyn project::ProjectItem),
 457    );
 458    fn is_singleton(&self, cx: &App) -> bool;
 459    fn boxed_clone(&self) -> Box<dyn ItemHandle>;
 460    fn clone_on_split(
 461        &self,
 462        workspace_id: Option<WorkspaceId>,
 463        window: &mut Window,
 464        cx: &mut App,
 465    ) -> Option<Box<dyn ItemHandle>>;
 466    fn added_to_pane(
 467        &self,
 468        workspace: &mut Workspace,
 469        pane: Entity<Pane>,
 470        window: &mut Window,
 471        cx: &mut Context<Workspace>,
 472    );
 473    fn deactivated(&self, window: &mut Window, cx: &mut App);
 474    fn discarded(&self, project: Entity<Project>, window: &mut Window, cx: &mut App);
 475    fn workspace_deactivated(&self, window: &mut Window, cx: &mut App);
 476    fn navigate(&self, data: Box<dyn Any>, window: &mut Window, cx: &mut App) -> bool;
 477    fn item_id(&self) -> EntityId;
 478    fn to_any(&self) -> AnyView;
 479    fn is_dirty(&self, cx: &App) -> bool;
 480    fn has_deleted_file(&self, cx: &App) -> bool;
 481    fn has_conflict(&self, cx: &App) -> bool;
 482    fn can_save(&self, cx: &App) -> bool;
 483    fn can_save_as(&self, cx: &App) -> bool;
 484    fn save(
 485        &self,
 486        format: bool,
 487        project: Entity<Project>,
 488        window: &mut Window,
 489        cx: &mut App,
 490    ) -> Task<Result<()>>;
 491    fn save_as(
 492        &self,
 493        project: Entity<Project>,
 494        path: ProjectPath,
 495        window: &mut Window,
 496        cx: &mut App,
 497    ) -> Task<Result<()>>;
 498    fn reload(
 499        &self,
 500        project: Entity<Project>,
 501        window: &mut Window,
 502        cx: &mut App,
 503    ) -> Task<Result<()>>;
 504    fn act_as_type(&self, type_id: TypeId, cx: &App) -> Option<AnyView>;
 505    fn to_followable_item_handle(&self, cx: &App) -> Option<Box<dyn FollowableItemHandle>>;
 506    fn to_serializable_item_handle(&self, cx: &App) -> Option<Box<dyn SerializableItemHandle>>;
 507    fn on_release(
 508        &self,
 509        cx: &mut App,
 510        callback: Box<dyn FnOnce(&mut App) + Send>,
 511    ) -> gpui::Subscription;
 512    fn to_searchable_item_handle(&self, cx: &App) -> Option<Box<dyn SearchableItemHandle>>;
 513    fn breadcrumb_location(&self, cx: &App) -> ToolbarItemLocation;
 514    fn breadcrumbs(&self, theme: &Theme, cx: &App) -> Option<Vec<BreadcrumbText>>;
 515    fn show_toolbar(&self, cx: &App) -> bool;
 516    fn pixel_position_of_cursor(&self, cx: &App) -> Option<Point<Pixels>>;
 517    fn downgrade_item(&self) -> Box<dyn WeakItemHandle>;
 518    fn workspace_settings<'a>(&self, cx: &'a App) -> &'a WorkspaceSettings;
 519    fn preserve_preview(&self, cx: &App) -> bool;
 520    fn include_in_nav_history(&self) -> bool;
 521}
 522
 523pub trait WeakItemHandle: Send + Sync {
 524    fn id(&self) -> EntityId;
 525    fn boxed_clone(&self) -> Box<dyn WeakItemHandle>;
 526    fn upgrade(&self) -> Option<Box<dyn ItemHandle>>;
 527}
 528
 529impl dyn ItemHandle {
 530    pub fn downcast<V: 'static>(&self) -> Option<Entity<V>> {
 531        self.to_any().downcast().ok()
 532    }
 533
 534    pub fn act_as<V: 'static>(&self, cx: &App) -> Option<Entity<V>> {
 535        self.act_as_type(TypeId::of::<V>(), cx)
 536            .and_then(|t| t.downcast().ok())
 537    }
 538}
 539
 540impl<T: Item> ItemHandle for Entity<T> {
 541    fn subscribe_to_item_events(
 542        &self,
 543        window: &mut Window,
 544        cx: &mut App,
 545        handler: Box<dyn Fn(ItemEvent, &mut Window, &mut App)>,
 546    ) -> gpui::Subscription {
 547        window.subscribe(self, cx, move |_, event, window, cx| {
 548            T::to_item_events(event, |item_event| handler(item_event, window, cx));
 549        })
 550    }
 551
 552    fn item_focus_handle(&self, cx: &App) -> FocusHandle {
 553        self.read(cx).focus_handle(cx)
 554    }
 555
 556    fn telemetry_event_text(&self, cx: &App) -> Option<&'static str> {
 557        self.read(cx).telemetry_event_text()
 558    }
 559
 560    fn tab_description(&self, detail: usize, cx: &App) -> Option<SharedString> {
 561        self.read(cx).tab_description(detail, cx)
 562    }
 563
 564    fn tab_content(&self, params: TabContentParams, window: &Window, cx: &App) -> AnyElement {
 565        self.read(cx).tab_content(params, window, cx)
 566    }
 567
 568    fn tab_icon(&self, window: &Window, cx: &App) -> Option<Icon> {
 569        self.read(cx).tab_icon(window, cx)
 570    }
 571
 572    fn tab_tooltip_content(&self, cx: &App) -> Option<TabTooltipContent> {
 573        self.read(cx).tab_tooltip_content(cx)
 574    }
 575
 576    fn tab_tooltip_text(&self, cx: &App) -> Option<SharedString> {
 577        self.read(cx).tab_tooltip_text(cx)
 578    }
 579
 580    fn dragged_tab_content(
 581        &self,
 582        params: TabContentParams,
 583        window: &Window,
 584        cx: &App,
 585    ) -> AnyElement {
 586        self.read(cx).tab_content(
 587            TabContentParams {
 588                selected: true,
 589                ..params
 590            },
 591            window,
 592            cx,
 593        )
 594    }
 595
 596    fn project_path(&self, cx: &App) -> Option<ProjectPath> {
 597        let this = self.read(cx);
 598        let mut result = None;
 599        if this.is_singleton(cx) {
 600            this.for_each_project_item(cx, &mut |_, item| {
 601                result = item.project_path(cx);
 602            });
 603        }
 604        result
 605    }
 606
 607    fn workspace_settings<'a>(&self, cx: &'a App) -> &'a WorkspaceSettings {
 608        if let Some(project_path) = self.project_path(cx) {
 609            WorkspaceSettings::get(
 610                Some(SettingsLocation {
 611                    worktree_id: project_path.worktree_id,
 612                    path: &project_path.path,
 613                }),
 614                cx,
 615            )
 616        } else {
 617            WorkspaceSettings::get_global(cx)
 618        }
 619    }
 620
 621    fn project_entry_ids(&self, cx: &App) -> SmallVec<[ProjectEntryId; 3]> {
 622        let mut result = SmallVec::new();
 623        self.read(cx).for_each_project_item(cx, &mut |_, item| {
 624            if let Some(id) = item.entry_id(cx) {
 625                result.push(id);
 626            }
 627        });
 628        result
 629    }
 630
 631    fn project_paths(&self, cx: &App) -> SmallVec<[ProjectPath; 3]> {
 632        let mut result = SmallVec::new();
 633        self.read(cx).for_each_project_item(cx, &mut |_, item| {
 634            if let Some(id) = item.project_path(cx) {
 635                result.push(id);
 636            }
 637        });
 638        result
 639    }
 640
 641    fn project_item_model_ids(&self, cx: &App) -> SmallVec<[EntityId; 3]> {
 642        let mut result = SmallVec::new();
 643        self.read(cx).for_each_project_item(cx, &mut |id, _| {
 644            result.push(id);
 645        });
 646        result
 647    }
 648
 649    fn for_each_project_item(
 650        &self,
 651        cx: &App,
 652        f: &mut dyn FnMut(EntityId, &dyn project::ProjectItem),
 653    ) {
 654        self.read(cx).for_each_project_item(cx, f)
 655    }
 656
 657    fn is_singleton(&self, cx: &App) -> bool {
 658        self.read(cx).is_singleton(cx)
 659    }
 660
 661    fn boxed_clone(&self) -> Box<dyn ItemHandle> {
 662        Box::new(self.clone())
 663    }
 664
 665    fn clone_on_split(
 666        &self,
 667        workspace_id: Option<WorkspaceId>,
 668        window: &mut Window,
 669        cx: &mut App,
 670    ) -> Option<Box<dyn ItemHandle>> {
 671        self.update(cx, |item, cx| item.clone_on_split(workspace_id, window, cx))
 672            .map(|handle| Box::new(handle) as Box<dyn ItemHandle>)
 673    }
 674
 675    fn added_to_pane(
 676        &self,
 677        workspace: &mut Workspace,
 678        pane: Entity<Pane>,
 679        window: &mut Window,
 680        cx: &mut Context<Workspace>,
 681    ) {
 682        let weak_item = self.downgrade();
 683        let history = pane.read(cx).nav_history_for_item(self);
 684        self.update(cx, |this, cx| {
 685            this.set_nav_history(history, window, cx);
 686            this.added_to_workspace(workspace, window, cx);
 687        });
 688
 689        if let Some(serializable_item) = self.to_serializable_item_handle(cx) {
 690            workspace
 691                .enqueue_item_serialization(serializable_item)
 692                .log_err();
 693        }
 694
 695        if workspace
 696            .panes_by_item
 697            .insert(self.item_id(), pane.downgrade())
 698            .is_none()
 699        {
 700            let mut pending_autosave = DelayedDebouncedEditAction::new();
 701            let (pending_update_tx, mut pending_update_rx) = mpsc::unbounded();
 702            let pending_update = Rc::new(RefCell::new(None));
 703
 704            let mut send_follower_updates = None;
 705            if let Some(item) = self.to_followable_item_handle(cx) {
 706                let is_project_item = item.is_project_item(window, cx);
 707                let item = item.downgrade();
 708
 709                send_follower_updates = Some(cx.spawn_in(window, {
 710                    let pending_update = pending_update.clone();
 711                    |workspace, mut cx| async move {
 712                        while let Some(mut leader_id) = pending_update_rx.next().await {
 713                            while let Ok(Some(id)) = pending_update_rx.try_next() {
 714                                leader_id = id;
 715                            }
 716
 717                            workspace.update_in(&mut cx, |workspace, window, cx| {
 718                                let Some(item) = item.upgrade() else { return };
 719                                workspace.update_followers(
 720                                    is_project_item,
 721                                    proto::update_followers::Variant::UpdateView(
 722                                        proto::UpdateView {
 723                                            id: item
 724                                                .remote_id(workspace.client(), window, cx)
 725                                                .map(|id| id.to_proto()),
 726                                            variant: pending_update.borrow_mut().take(),
 727                                            leader_id,
 728                                        },
 729                                    ),
 730                                    window,
 731                                    cx,
 732                                );
 733                            })?;
 734                            cx.background_executor().timer(LEADER_UPDATE_THROTTLE).await;
 735                        }
 736                        anyhow::Ok(())
 737                    }
 738                }));
 739            }
 740
 741            let mut event_subscription = Some(cx.subscribe_in(
 742                self,
 743                window,
 744                move |workspace, item: &Entity<T>, event, window, cx| {
 745                    let pane = if let Some(pane) = workspace
 746                        .panes_by_item
 747                        .get(&item.item_id())
 748                        .and_then(|pane| pane.upgrade())
 749                    {
 750                        pane
 751                    } else {
 752                        return;
 753                    };
 754
 755                    if let Some(item) = item.to_followable_item_handle(cx) {
 756                        let leader_id = workspace.leader_for_pane(&pane);
 757
 758                        if let Some(leader_id) = leader_id {
 759                            if let Some(FollowEvent::Unfollow) = item.to_follow_event(event) {
 760                                workspace.unfollow(leader_id, window, cx);
 761                            }
 762                        }
 763
 764                        if item.item_focus_handle(cx).contains_focused(window, cx) {
 765                            item.add_event_to_update_proto(
 766                                event,
 767                                &mut pending_update.borrow_mut(),
 768                                window,
 769                                cx,
 770                            );
 771                            pending_update_tx.unbounded_send(leader_id).ok();
 772                        }
 773                    }
 774
 775                    if let Some(item) = item.to_serializable_item_handle(cx) {
 776                        if item.should_serialize(event, cx) {
 777                            workspace.enqueue_item_serialization(item).ok();
 778                        }
 779                    }
 780
 781                    T::to_item_events(event, |event| match event {
 782                        ItemEvent::CloseItem => {
 783                            pane.update(cx, |pane, cx| {
 784                                pane.close_item_by_id(
 785                                    item.item_id(),
 786                                    crate::SaveIntent::Close,
 787                                    window,
 788                                    cx,
 789                                )
 790                            })
 791                            .detach_and_log_err(cx);
 792                        }
 793
 794                        ItemEvent::UpdateTab => {
 795                            pane.update(cx, |_, cx| {
 796                                cx.emit(pane::Event::ChangeItemTitle);
 797                                cx.notify();
 798                            });
 799                        }
 800
 801                        ItemEvent::Edit => {
 802                            let autosave = item.workspace_settings(cx).autosave;
 803
 804                            if let AutosaveSetting::AfterDelay { milliseconds } = autosave {
 805                                let delay = Duration::from_millis(milliseconds);
 806                                let item = item.clone();
 807                                pending_autosave.fire_new(
 808                                    delay,
 809                                    window,
 810                                    cx,
 811                                    move |workspace, window, cx| {
 812                                        Pane::autosave_item(
 813                                            &item,
 814                                            workspace.project().clone(),
 815                                            window,
 816                                            cx,
 817                                        )
 818                                    },
 819                                );
 820                            }
 821                            pane.update(cx, |pane, cx| pane.handle_item_edit(item.item_id(), cx));
 822                        }
 823
 824                        _ => {}
 825                    });
 826                },
 827            ));
 828
 829            cx.on_blur(
 830                &self.read(cx).focus_handle(cx),
 831                window,
 832                move |workspace, window, cx| {
 833                    if let Some(item) = weak_item.upgrade() {
 834                        if item.workspace_settings(cx).autosave == AutosaveSetting::OnFocusChange {
 835                            Pane::autosave_item(&item, workspace.project.clone(), window, cx)
 836                                .detach_and_log_err(cx);
 837                        }
 838                    }
 839                },
 840            )
 841            .detach();
 842
 843            let item_id = self.item_id();
 844            cx.observe_release_in(self, window, move |workspace, _, _, _| {
 845                workspace.panes_by_item.remove(&item_id);
 846                event_subscription.take();
 847                send_follower_updates.take();
 848            })
 849            .detach();
 850        }
 851
 852        cx.defer_in(window, |workspace, window, cx| {
 853            workspace.serialize_workspace(window, cx);
 854        });
 855    }
 856
 857    fn discarded(&self, project: Entity<Project>, window: &mut Window, cx: &mut App) {
 858        self.update(cx, |this, cx| this.discarded(project, window, cx));
 859    }
 860
 861    fn deactivated(&self, window: &mut Window, cx: &mut App) {
 862        self.update(cx, |this, cx| this.deactivated(window, cx));
 863    }
 864
 865    fn workspace_deactivated(&self, window: &mut Window, cx: &mut App) {
 866        self.update(cx, |this, cx| this.workspace_deactivated(window, cx));
 867    }
 868
 869    fn navigate(&self, data: Box<dyn Any>, window: &mut Window, cx: &mut App) -> bool {
 870        self.update(cx, |this, cx| this.navigate(data, window, cx))
 871    }
 872
 873    fn item_id(&self) -> EntityId {
 874        self.entity_id()
 875    }
 876
 877    fn to_any(&self) -> AnyView {
 878        self.clone().into()
 879    }
 880
 881    fn is_dirty(&self, cx: &App) -> bool {
 882        self.read(cx).is_dirty(cx)
 883    }
 884
 885    fn has_deleted_file(&self, cx: &App) -> bool {
 886        self.read(cx).has_deleted_file(cx)
 887    }
 888
 889    fn has_conflict(&self, cx: &App) -> bool {
 890        self.read(cx).has_conflict(cx)
 891    }
 892
 893    fn can_save(&self, cx: &App) -> bool {
 894        self.read(cx).can_save(cx)
 895    }
 896
 897    fn can_save_as(&self, cx: &App) -> bool {
 898        self.read(cx).can_save_as(cx)
 899    }
 900
 901    fn save(
 902        &self,
 903        format: bool,
 904        project: Entity<Project>,
 905        window: &mut Window,
 906        cx: &mut App,
 907    ) -> Task<Result<()>> {
 908        self.update(cx, |item, cx| item.save(format, project, window, cx))
 909    }
 910
 911    fn save_as(
 912        &self,
 913        project: Entity<Project>,
 914        path: ProjectPath,
 915        window: &mut Window,
 916        cx: &mut App,
 917    ) -> Task<anyhow::Result<()>> {
 918        self.update(cx, |item, cx| item.save_as(project, path, window, cx))
 919    }
 920
 921    fn reload(
 922        &self,
 923        project: Entity<Project>,
 924        window: &mut Window,
 925        cx: &mut App,
 926    ) -> Task<Result<()>> {
 927        self.update(cx, |item, cx| item.reload(project, window, cx))
 928    }
 929
 930    fn act_as_type<'a>(&'a self, type_id: TypeId, cx: &'a App) -> Option<AnyView> {
 931        self.read(cx).act_as_type(type_id, self, cx)
 932    }
 933
 934    fn to_followable_item_handle(&self, cx: &App) -> Option<Box<dyn FollowableItemHandle>> {
 935        FollowableViewRegistry::to_followable_view(self.clone(), cx)
 936    }
 937
 938    fn on_release(
 939        &self,
 940        cx: &mut App,
 941        callback: Box<dyn FnOnce(&mut App) + Send>,
 942    ) -> gpui::Subscription {
 943        cx.observe_release(self, move |_, cx| callback(cx))
 944    }
 945
 946    fn to_searchable_item_handle(&self, cx: &App) -> Option<Box<dyn SearchableItemHandle>> {
 947        self.read(cx).as_searchable(self)
 948    }
 949
 950    fn breadcrumb_location(&self, cx: &App) -> ToolbarItemLocation {
 951        self.read(cx).breadcrumb_location(cx)
 952    }
 953
 954    fn breadcrumbs(&self, theme: &Theme, cx: &App) -> Option<Vec<BreadcrumbText>> {
 955        self.read(cx).breadcrumbs(theme, cx)
 956    }
 957
 958    fn show_toolbar(&self, cx: &App) -> bool {
 959        self.read(cx).show_toolbar()
 960    }
 961
 962    fn pixel_position_of_cursor(&self, cx: &App) -> Option<Point<Pixels>> {
 963        self.read(cx).pixel_position_of_cursor(cx)
 964    }
 965
 966    fn downgrade_item(&self) -> Box<dyn WeakItemHandle> {
 967        Box::new(self.downgrade())
 968    }
 969
 970    fn to_serializable_item_handle(&self, cx: &App) -> Option<Box<dyn SerializableItemHandle>> {
 971        SerializableItemRegistry::view_to_serializable_item_handle(self.to_any(), cx)
 972    }
 973
 974    fn preserve_preview(&self, cx: &App) -> bool {
 975        self.read(cx).preserve_preview(cx)
 976    }
 977
 978    fn include_in_nav_history(&self) -> bool {
 979        T::include_in_nav_history()
 980    }
 981}
 982
 983impl From<Box<dyn ItemHandle>> for AnyView {
 984    fn from(val: Box<dyn ItemHandle>) -> Self {
 985        val.to_any()
 986    }
 987}
 988
 989impl From<&Box<dyn ItemHandle>> for AnyView {
 990    fn from(val: &Box<dyn ItemHandle>) -> Self {
 991        val.to_any()
 992    }
 993}
 994
 995impl Clone for Box<dyn ItemHandle> {
 996    fn clone(&self) -> Box<dyn ItemHandle> {
 997        self.boxed_clone()
 998    }
 999}
1000
1001impl<T: Item> WeakItemHandle for WeakEntity<T> {
1002    fn id(&self) -> EntityId {
1003        self.entity_id()
1004    }
1005
1006    fn boxed_clone(&self) -> Box<dyn WeakItemHandle> {
1007        Box::new(self.clone())
1008    }
1009
1010    fn upgrade(&self) -> Option<Box<dyn ItemHandle>> {
1011        self.upgrade().map(|v| Box::new(v) as Box<dyn ItemHandle>)
1012    }
1013}
1014
1015pub trait ProjectItem: Item {
1016    type Item: project::ProjectItem;
1017
1018    fn for_project_item(
1019        project: Entity<Project>,
1020        item: Entity<Self::Item>,
1021        window: &mut Window,
1022        cx: &mut Context<Self>,
1023    ) -> Self
1024    where
1025        Self: Sized;
1026}
1027
1028#[derive(Debug)]
1029pub enum FollowEvent {
1030    Unfollow,
1031}
1032
1033pub enum Dedup {
1034    KeepExisting,
1035    ReplaceExisting,
1036}
1037
1038pub trait FollowableItem: Item {
1039    fn remote_id(&self) -> Option<ViewId>;
1040    fn to_state_proto(&self, window: &Window, cx: &App) -> Option<proto::view::Variant>;
1041    fn from_state_proto(
1042        project: Entity<Workspace>,
1043        id: ViewId,
1044        state: &mut Option<proto::view::Variant>,
1045        window: &mut Window,
1046        cx: &mut App,
1047    ) -> Option<Task<Result<Entity<Self>>>>;
1048    fn to_follow_event(event: &Self::Event) -> Option<FollowEvent>;
1049    fn add_event_to_update_proto(
1050        &self,
1051        event: &Self::Event,
1052        update: &mut Option<proto::update_view::Variant>,
1053        window: &Window,
1054        cx: &App,
1055    ) -> bool;
1056    fn apply_update_proto(
1057        &mut self,
1058        project: &Entity<Project>,
1059        message: proto::update_view::Variant,
1060        window: &mut Window,
1061        cx: &mut Context<Self>,
1062    ) -> Task<Result<()>>;
1063    fn is_project_item(&self, window: &Window, cx: &App) -> bool;
1064    fn set_leader_peer_id(
1065        &mut self,
1066        leader_peer_id: Option<PeerId>,
1067        window: &mut Window,
1068        cx: &mut Context<Self>,
1069    );
1070    fn dedup(&self, existing: &Self, window: &Window, cx: &App) -> Option<Dedup>;
1071}
1072
1073pub trait FollowableItemHandle: ItemHandle {
1074    fn remote_id(&self, client: &Arc<Client>, window: &mut Window, cx: &mut App) -> Option<ViewId>;
1075    fn downgrade(&self) -> Box<dyn WeakFollowableItemHandle>;
1076    fn set_leader_peer_id(&self, leader_peer_id: Option<PeerId>, window: &mut Window, cx: &mut App);
1077    fn to_state_proto(&self, window: &mut Window, cx: &mut App) -> Option<proto::view::Variant>;
1078    fn add_event_to_update_proto(
1079        &self,
1080        event: &dyn Any,
1081        update: &mut Option<proto::update_view::Variant>,
1082        window: &mut Window,
1083        cx: &mut App,
1084    ) -> bool;
1085    fn to_follow_event(&self, event: &dyn Any) -> Option<FollowEvent>;
1086    fn apply_update_proto(
1087        &self,
1088        project: &Entity<Project>,
1089        message: proto::update_view::Variant,
1090        window: &mut Window,
1091        cx: &mut App,
1092    ) -> Task<Result<()>>;
1093    fn is_project_item(&self, window: &mut Window, cx: &mut App) -> bool;
1094    fn dedup(
1095        &self,
1096        existing: &dyn FollowableItemHandle,
1097        window: &mut Window,
1098        cx: &mut App,
1099    ) -> Option<Dedup>;
1100}
1101
1102impl<T: FollowableItem> FollowableItemHandle for Entity<T> {
1103    fn remote_id(&self, client: &Arc<Client>, _: &mut Window, cx: &mut App) -> Option<ViewId> {
1104        self.read(cx).remote_id().or_else(|| {
1105            client.peer_id().map(|creator| ViewId {
1106                creator,
1107                id: self.item_id().as_u64(),
1108            })
1109        })
1110    }
1111
1112    fn downgrade(&self) -> Box<dyn WeakFollowableItemHandle> {
1113        Box::new(self.downgrade())
1114    }
1115
1116    fn set_leader_peer_id(
1117        &self,
1118        leader_peer_id: Option<PeerId>,
1119        window: &mut Window,
1120        cx: &mut App,
1121    ) {
1122        self.update(cx, |this, cx| {
1123            this.set_leader_peer_id(leader_peer_id, window, cx)
1124        })
1125    }
1126
1127    fn to_state_proto(&self, window: &mut Window, cx: &mut App) -> Option<proto::view::Variant> {
1128        self.read(cx).to_state_proto(window, cx)
1129    }
1130
1131    fn add_event_to_update_proto(
1132        &self,
1133        event: &dyn Any,
1134        update: &mut Option<proto::update_view::Variant>,
1135        window: &mut Window,
1136        cx: &mut App,
1137    ) -> bool {
1138        if let Some(event) = event.downcast_ref() {
1139            self.read(cx)
1140                .add_event_to_update_proto(event, update, window, cx)
1141        } else {
1142            false
1143        }
1144    }
1145
1146    fn to_follow_event(&self, event: &dyn Any) -> Option<FollowEvent> {
1147        T::to_follow_event(event.downcast_ref()?)
1148    }
1149
1150    fn apply_update_proto(
1151        &self,
1152        project: &Entity<Project>,
1153        message: proto::update_view::Variant,
1154        window: &mut Window,
1155        cx: &mut App,
1156    ) -> Task<Result<()>> {
1157        self.update(cx, |this, cx| {
1158            this.apply_update_proto(project, message, window, cx)
1159        })
1160    }
1161
1162    fn is_project_item(&self, window: &mut Window, cx: &mut App) -> bool {
1163        self.read(cx).is_project_item(window, cx)
1164    }
1165
1166    fn dedup(
1167        &self,
1168        existing: &dyn FollowableItemHandle,
1169        window: &mut Window,
1170        cx: &mut App,
1171    ) -> Option<Dedup> {
1172        let existing = existing.to_any().downcast::<T>().ok()?;
1173        self.read(cx).dedup(existing.read(cx), window, cx)
1174    }
1175}
1176
1177pub trait WeakFollowableItemHandle: Send + Sync {
1178    fn upgrade(&self) -> Option<Box<dyn FollowableItemHandle>>;
1179}
1180
1181impl<T: FollowableItem> WeakFollowableItemHandle for WeakEntity<T> {
1182    fn upgrade(&self) -> Option<Box<dyn FollowableItemHandle>> {
1183        Some(Box::new(self.upgrade()?))
1184    }
1185}
1186
1187#[cfg(any(test, feature = "test-support"))]
1188pub mod test {
1189    use super::{Item, ItemEvent, SerializableItem, TabContentParams};
1190    use crate::{ItemId, ItemNavHistory, Workspace, WorkspaceId};
1191    use gpui::{
1192        AnyElement, App, AppContext as _, Context, Entity, EntityId, EventEmitter, Focusable,
1193        InteractiveElement, IntoElement, Render, SharedString, Task, WeakEntity, Window,
1194    };
1195    use project::{Project, ProjectEntryId, ProjectPath, WorktreeId};
1196    use std::{any::Any, cell::Cell, path::Path};
1197
1198    pub struct TestProjectItem {
1199        pub entry_id: Option<ProjectEntryId>,
1200        pub project_path: Option<ProjectPath>,
1201        pub is_dirty: bool,
1202    }
1203
1204    pub struct TestItem {
1205        pub workspace_id: Option<WorkspaceId>,
1206        pub state: String,
1207        pub label: String,
1208        pub save_count: usize,
1209        pub save_as_count: usize,
1210        pub reload_count: usize,
1211        pub is_dirty: bool,
1212        pub is_singleton: bool,
1213        pub has_conflict: bool,
1214        pub project_items: Vec<Entity<TestProjectItem>>,
1215        pub nav_history: Option<ItemNavHistory>,
1216        pub tab_descriptions: Option<Vec<&'static str>>,
1217        pub tab_detail: Cell<Option<usize>>,
1218        serialize: Option<Box<dyn Fn() -> Option<Task<anyhow::Result<()>>>>>,
1219        focus_handle: gpui::FocusHandle,
1220    }
1221
1222    impl project::ProjectItem for TestProjectItem {
1223        fn try_open(
1224            _project: &Entity<Project>,
1225            _path: &ProjectPath,
1226            _cx: &mut App,
1227        ) -> Option<Task<gpui::Result<Entity<Self>>>> {
1228            None
1229        }
1230        fn entry_id(&self, _: &App) -> Option<ProjectEntryId> {
1231            self.entry_id
1232        }
1233
1234        fn project_path(&self, _: &App) -> Option<ProjectPath> {
1235            self.project_path.clone()
1236        }
1237
1238        fn is_dirty(&self) -> bool {
1239            self.is_dirty
1240        }
1241    }
1242
1243    pub enum TestItemEvent {
1244        Edit,
1245    }
1246
1247    impl TestProjectItem {
1248        pub fn new(id: u64, path: &str, cx: &mut App) -> Entity<Self> {
1249            let entry_id = Some(ProjectEntryId::from_proto(id));
1250            let project_path = Some(ProjectPath {
1251                worktree_id: WorktreeId::from_usize(0),
1252                path: Path::new(path).into(),
1253            });
1254            cx.new(|_| Self {
1255                entry_id,
1256                project_path,
1257                is_dirty: false,
1258            })
1259        }
1260
1261        pub fn new_untitled(cx: &mut App) -> Entity<Self> {
1262            cx.new(|_| Self {
1263                project_path: None,
1264                entry_id: None,
1265                is_dirty: false,
1266            })
1267        }
1268
1269        pub fn new_dirty(id: u64, path: &str, cx: &mut App) -> Entity<Self> {
1270            let entry_id = Some(ProjectEntryId::from_proto(id));
1271            let project_path = Some(ProjectPath {
1272                worktree_id: WorktreeId::from_usize(0),
1273                path: Path::new(path).into(),
1274            });
1275            cx.new(|_| Self {
1276                entry_id,
1277                project_path,
1278                is_dirty: true,
1279            })
1280        }
1281    }
1282
1283    impl TestItem {
1284        pub fn new(cx: &mut Context<Self>) -> Self {
1285            Self {
1286                state: String::new(),
1287                label: String::new(),
1288                save_count: 0,
1289                save_as_count: 0,
1290                reload_count: 0,
1291                is_dirty: false,
1292                has_conflict: false,
1293                project_items: Vec::new(),
1294                is_singleton: true,
1295                nav_history: None,
1296                tab_descriptions: None,
1297                tab_detail: Default::default(),
1298                workspace_id: Default::default(),
1299                focus_handle: cx.focus_handle(),
1300                serialize: None,
1301            }
1302        }
1303
1304        pub fn new_deserialized(id: WorkspaceId, cx: &mut Context<Self>) -> Self {
1305            let mut this = Self::new(cx);
1306            this.workspace_id = Some(id);
1307            this
1308        }
1309
1310        pub fn with_label(mut self, state: &str) -> Self {
1311            self.label = state.to_string();
1312            self
1313        }
1314
1315        pub fn with_singleton(mut self, singleton: bool) -> Self {
1316            self.is_singleton = singleton;
1317            self
1318        }
1319
1320        pub fn with_dirty(mut self, dirty: bool) -> Self {
1321            self.is_dirty = dirty;
1322            self
1323        }
1324
1325        pub fn with_conflict(mut self, has_conflict: bool) -> Self {
1326            self.has_conflict = has_conflict;
1327            self
1328        }
1329
1330        pub fn with_project_items(mut self, items: &[Entity<TestProjectItem>]) -> Self {
1331            self.project_items.clear();
1332            self.project_items.extend(items.iter().cloned());
1333            self
1334        }
1335
1336        pub fn with_serialize(
1337            mut self,
1338            serialize: impl Fn() -> Option<Task<anyhow::Result<()>>> + 'static,
1339        ) -> Self {
1340            self.serialize = Some(Box::new(serialize));
1341            self
1342        }
1343
1344        pub fn set_state(&mut self, state: String, cx: &mut Context<Self>) {
1345            self.push_to_nav_history(cx);
1346            self.state = state;
1347        }
1348
1349        fn push_to_nav_history(&mut self, cx: &mut Context<Self>) {
1350            if let Some(history) = &mut self.nav_history {
1351                history.push(Some(Box::new(self.state.clone())), cx);
1352            }
1353        }
1354    }
1355
1356    impl Render for TestItem {
1357        fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
1358            gpui::div().track_focus(&self.focus_handle(cx))
1359        }
1360    }
1361
1362    impl EventEmitter<ItemEvent> for TestItem {}
1363
1364    impl Focusable for TestItem {
1365        fn focus_handle(&self, _: &App) -> gpui::FocusHandle {
1366            self.focus_handle.clone()
1367        }
1368    }
1369
1370    impl Item for TestItem {
1371        type Event = ItemEvent;
1372
1373        fn to_item_events(event: &Self::Event, mut f: impl FnMut(ItemEvent)) {
1374            f(*event)
1375        }
1376
1377        fn tab_description(&self, detail: usize, _: &App) -> Option<SharedString> {
1378            self.tab_descriptions.as_ref().and_then(|descriptions| {
1379                let description = *descriptions.get(detail).or_else(|| descriptions.last())?;
1380                Some(description.into())
1381            })
1382        }
1383
1384        fn telemetry_event_text(&self) -> Option<&'static str> {
1385            None
1386        }
1387
1388        fn tab_content(&self, params: TabContentParams, _window: &Window, _cx: &App) -> AnyElement {
1389            self.tab_detail.set(params.detail);
1390            gpui::div().into_any_element()
1391        }
1392
1393        fn for_each_project_item(
1394            &self,
1395            cx: &App,
1396            f: &mut dyn FnMut(EntityId, &dyn project::ProjectItem),
1397        ) {
1398            self.project_items
1399                .iter()
1400                .for_each(|item| f(item.entity_id(), item.read(cx)))
1401        }
1402
1403        fn is_singleton(&self, _: &App) -> bool {
1404            self.is_singleton
1405        }
1406
1407        fn set_nav_history(
1408            &mut self,
1409            history: ItemNavHistory,
1410            _window: &mut Window,
1411            _: &mut Context<Self>,
1412        ) {
1413            self.nav_history = Some(history);
1414        }
1415
1416        fn navigate(
1417            &mut self,
1418            state: Box<dyn Any>,
1419            _window: &mut Window,
1420            _: &mut Context<Self>,
1421        ) -> bool {
1422            let state = *state.downcast::<String>().unwrap_or_default();
1423            if state != self.state {
1424                self.state = state;
1425                true
1426            } else {
1427                false
1428            }
1429        }
1430
1431        fn deactivated(&mut self, _window: &mut Window, cx: &mut Context<Self>) {
1432            self.push_to_nav_history(cx);
1433        }
1434
1435        fn clone_on_split(
1436            &self,
1437            _workspace_id: Option<WorkspaceId>,
1438            _: &mut Window,
1439            cx: &mut Context<Self>,
1440        ) -> Option<Entity<Self>>
1441        where
1442            Self: Sized,
1443        {
1444            Some(cx.new(|cx| Self {
1445                state: self.state.clone(),
1446                label: self.label.clone(),
1447                save_count: self.save_count,
1448                save_as_count: self.save_as_count,
1449                reload_count: self.reload_count,
1450                is_dirty: self.is_dirty,
1451                is_singleton: self.is_singleton,
1452                has_conflict: self.has_conflict,
1453                project_items: self.project_items.clone(),
1454                nav_history: None,
1455                tab_descriptions: None,
1456                tab_detail: Default::default(),
1457                workspace_id: self.workspace_id,
1458                focus_handle: cx.focus_handle(),
1459                serialize: None,
1460            }))
1461        }
1462
1463        fn is_dirty(&self, _: &App) -> bool {
1464            self.is_dirty
1465        }
1466
1467        fn has_conflict(&self, _: &App) -> bool {
1468            self.has_conflict
1469        }
1470
1471        fn can_save(&self, cx: &App) -> bool {
1472            !self.project_items.is_empty()
1473                && self
1474                    .project_items
1475                    .iter()
1476                    .all(|item| item.read(cx).entry_id.is_some())
1477        }
1478
1479        fn can_save_as(&self, _cx: &App) -> bool {
1480            self.is_singleton
1481        }
1482
1483        fn save(
1484            &mut self,
1485            _: bool,
1486            _: Entity<Project>,
1487            _window: &mut Window,
1488            cx: &mut Context<Self>,
1489        ) -> Task<anyhow::Result<()>> {
1490            self.save_count += 1;
1491            self.is_dirty = false;
1492            for item in &self.project_items {
1493                item.update(cx, |item, _| {
1494                    if item.is_dirty {
1495                        item.is_dirty = false;
1496                    }
1497                })
1498            }
1499            Task::ready(Ok(()))
1500        }
1501
1502        fn save_as(
1503            &mut self,
1504            _: Entity<Project>,
1505            _: ProjectPath,
1506            _window: &mut Window,
1507            _: &mut Context<Self>,
1508        ) -> Task<anyhow::Result<()>> {
1509            self.save_as_count += 1;
1510            self.is_dirty = false;
1511            Task::ready(Ok(()))
1512        }
1513
1514        fn reload(
1515            &mut self,
1516            _: Entity<Project>,
1517            _window: &mut Window,
1518            _: &mut Context<Self>,
1519        ) -> Task<anyhow::Result<()>> {
1520            self.reload_count += 1;
1521            self.is_dirty = false;
1522            Task::ready(Ok(()))
1523        }
1524    }
1525
1526    impl SerializableItem for TestItem {
1527        fn serialized_item_kind() -> &'static str {
1528            "TestItem"
1529        }
1530
1531        fn deserialize(
1532            _project: Entity<Project>,
1533            _workspace: WeakEntity<Workspace>,
1534            workspace_id: WorkspaceId,
1535            _item_id: ItemId,
1536            _window: &mut Window,
1537            cx: &mut App,
1538        ) -> Task<anyhow::Result<Entity<Self>>> {
1539            let entity = cx.new(|cx| Self::new_deserialized(workspace_id, cx));
1540            Task::ready(Ok(entity))
1541        }
1542
1543        fn cleanup(
1544            _workspace_id: WorkspaceId,
1545            _alive_items: Vec<ItemId>,
1546            _window: &mut Window,
1547            _cx: &mut App,
1548        ) -> Task<anyhow::Result<()>> {
1549            Task::ready(Ok(()))
1550        }
1551
1552        fn serialize(
1553            &mut self,
1554            _workspace: &mut Workspace,
1555            _item_id: ItemId,
1556            _closing: bool,
1557            _window: &mut Window,
1558            _cx: &mut Context<Self>,
1559        ) -> Option<Task<anyhow::Result<()>>> {
1560            if let Some(serialize) = self.serialize.take() {
1561                let result = serialize();
1562                self.serialize = Some(serialize);
1563                result
1564            } else {
1565                None
1566            }
1567        }
1568
1569        fn should_serialize(&self, _event: &Self::Event) -> bool {
1570            false
1571        }
1572    }
1573}