item.rs

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