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