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