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