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