item.rs

   1use crate::{
   2    pane::{self, Pane},
   3    persistence::model::ItemId,
   4    searchable::SearchableItemHandle,
   5    workspace_settings::{AutosaveSetting, WorkspaceSettings},
   6    DelayedDebouncedEditAction, FollowableItemBuilders, ItemNavHistory, ToolbarItemLocation,
   7    ViewId, Workspace, WorkspaceId,
   8};
   9use anyhow::Result;
  10use client2::{
  11    proto::{self, PeerId},
  12    Client,
  13};
  14use gpui::{
  15    AnyElement, AnyView, AppContext, Entity, EntityId, EventEmitter, FocusHandle, HighlightStyle,
  16    Model, Pixels, Point, Render, SharedString, Task, View, ViewContext, WeakView, WindowContext,
  17};
  18use project2::{Project, ProjectEntryId, ProjectPath};
  19use schemars::JsonSchema;
  20use serde::{Deserialize, Serialize};
  21use settings2::Settings;
  22use smallvec::SmallVec;
  23use std::{
  24    any::{Any, TypeId},
  25    cell::RefCell,
  26    ops::Range,
  27    path::PathBuf,
  28    rc::Rc,
  29    sync::{
  30        atomic::{AtomicBool, Ordering},
  31        Arc,
  32    },
  33    time::Duration,
  34};
  35use theme2::Theme;
  36
  37#[derive(Deserialize)]
  38pub struct ItemSettings {
  39    pub git_status: bool,
  40    pub close_position: ClosePosition,
  41}
  42
  43#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
  44#[serde(rename_all = "lowercase")]
  45pub enum ClosePosition {
  46    Left,
  47    #[default]
  48    Right,
  49}
  50
  51impl ClosePosition {
  52    pub fn right(&self) -> bool {
  53        match self {
  54            ClosePosition::Left => false,
  55            ClosePosition::Right => true,
  56        }
  57    }
  58}
  59
  60#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
  61pub struct ItemSettingsContent {
  62    git_status: Option<bool>,
  63    close_position: Option<ClosePosition>,
  64}
  65
  66impl Settings for ItemSettings {
  67    const KEY: Option<&'static str> = Some("tabs");
  68
  69    type FileContent = ItemSettingsContent;
  70
  71    fn load(
  72        default_value: &Self::FileContent,
  73        user_values: &[&Self::FileContent],
  74        _: &mut AppContext,
  75    ) -> Result<Self> {
  76        Self::load_via_json_merge(default_value, user_values)
  77    }
  78}
  79
  80#[derive(Eq, PartialEq, Hash, Debug)]
  81pub enum ItemEvent {
  82    CloseItem,
  83    UpdateTab,
  84    UpdateBreadcrumbs,
  85    Edit,
  86}
  87
  88// TODO: Combine this with existing HighlightedText struct?
  89pub struct BreadcrumbText {
  90    pub text: String,
  91    pub highlights: Option<Vec<(Range<usize>, HighlightStyle)>>,
  92}
  93
  94pub trait Item: Render + EventEmitter {
  95    fn focus_handle(&self) -> FocusHandle;
  96    fn deactivated(&mut self, _: &mut ViewContext<Self>) {}
  97    fn workspace_deactivated(&mut self, _: &mut ViewContext<Self>) {}
  98    fn navigate(&mut self, _: Box<dyn Any>, _: &mut ViewContext<Self>) -> bool {
  99        false
 100    }
 101    fn tab_tooltip_text(&self, _: &AppContext) -> Option<SharedString> {
 102        None
 103    }
 104    fn tab_description(&self, _: usize, _: &AppContext) -> Option<SharedString> {
 105        None
 106    }
 107    fn tab_content<V: 'static>(&self, detail: Option<usize>, cx: &AppContext) -> AnyElement<V>;
 108
 109    fn for_each_project_item(
 110        &self,
 111        _: &AppContext,
 112        _: &mut dyn FnMut(EntityId, &dyn project2::Item),
 113    ) {
 114    } // (model id, Item)
 115    fn is_singleton(&self, _cx: &AppContext) -> bool {
 116        false
 117    }
 118    fn set_nav_history(&mut self, _: ItemNavHistory, _: &mut ViewContext<Self>) {}
 119    fn clone_on_split(
 120        &self,
 121        _workspace_id: WorkspaceId,
 122        _: &mut ViewContext<Self>,
 123    ) -> Option<View<Self>>
 124    where
 125        Self: Sized,
 126    {
 127        None
 128    }
 129    fn is_dirty(&self, _: &AppContext) -> bool {
 130        false
 131    }
 132    fn has_conflict(&self, _: &AppContext) -> bool {
 133        false
 134    }
 135    fn can_save(&self, _cx: &AppContext) -> bool {
 136        false
 137    }
 138    fn save(&mut self, _project: Model<Project>, _cx: &mut ViewContext<Self>) -> Task<Result<()>> {
 139        unimplemented!("save() must be implemented if can_save() returns true")
 140    }
 141    fn save_as(
 142        &mut self,
 143        _project: Model<Project>,
 144        _abs_path: PathBuf,
 145        _cx: &mut ViewContext<Self>,
 146    ) -> Task<Result<()>> {
 147        unimplemented!("save_as() must be implemented if can_save() returns true")
 148    }
 149    fn reload(
 150        &mut self,
 151        _project: Model<Project>,
 152        _cx: &mut ViewContext<Self>,
 153    ) -> Task<Result<()>> {
 154        unimplemented!("reload() must be implemented if can_save() returns true")
 155    }
 156    fn to_item_events(_event: &Self::Event) -> SmallVec<[ItemEvent; 2]> {
 157        SmallVec::new()
 158    }
 159    fn should_close_item_on_event(_: &Self::Event) -> bool {
 160        false
 161    }
 162    fn should_update_tab_on_event(_: &Self::Event) -> bool {
 163        false
 164    }
 165
 166    fn act_as_type<'a>(
 167        &'a self,
 168        type_id: TypeId,
 169        self_handle: &'a View<Self>,
 170        _: &'a AppContext,
 171    ) -> Option<AnyView> {
 172        if TypeId::of::<Self>() == type_id {
 173            Some(self_handle.clone().into())
 174        } else {
 175            None
 176        }
 177    }
 178
 179    fn as_searchable(&self, _: &View<Self>) -> Option<Box<dyn SearchableItemHandle>> {
 180        None
 181    }
 182
 183    fn breadcrumb_location(&self) -> ToolbarItemLocation {
 184        ToolbarItemLocation::Hidden
 185    }
 186
 187    fn breadcrumbs(&self, _theme: &Theme, _cx: &AppContext) -> Option<Vec<BreadcrumbText>> {
 188        None
 189    }
 190
 191    fn added_to_workspace(&mut self, _workspace: &mut Workspace, _cx: &mut ViewContext<Self>) {}
 192
 193    fn serialized_item_kind() -> Option<&'static str> {
 194        None
 195    }
 196
 197    fn deserialize(
 198        _project: Model<Project>,
 199        _workspace: WeakView<Workspace>,
 200        _workspace_id: WorkspaceId,
 201        _item_id: ItemId,
 202        _cx: &mut ViewContext<Pane>,
 203    ) -> Task<Result<View<Self>>> {
 204        unimplemented!(
 205            "deserialize() must be implemented if serialized_item_kind() returns Some(_)"
 206        )
 207    }
 208    fn show_toolbar(&self) -> bool {
 209        true
 210    }
 211    fn pixel_position_of_cursor(&self, _: &AppContext) -> Option<Point<Pixels>> {
 212        None
 213    }
 214}
 215
 216pub trait ItemHandle: 'static + Send {
 217    fn focus_handle(&self, cx: &WindowContext) -> FocusHandle;
 218    fn subscribe_to_item_events(
 219        &self,
 220        cx: &mut WindowContext,
 221        handler: Box<dyn Fn(ItemEvent, &mut WindowContext) + Send>,
 222    ) -> gpui::Subscription;
 223    fn tab_tooltip_text(&self, cx: &AppContext) -> Option<SharedString>;
 224    fn tab_description(&self, detail: usize, cx: &AppContext) -> Option<SharedString>;
 225    fn tab_content(&self, detail: Option<usize>, cx: &AppContext) -> AnyElement<Pane>;
 226    fn dragged_tab_content(&self, detail: Option<usize>, cx: &AppContext) -> AnyElement<Workspace>;
 227    fn project_path(&self, cx: &AppContext) -> Option<ProjectPath>;
 228    fn project_entry_ids(&self, cx: &AppContext) -> SmallVec<[ProjectEntryId; 3]>;
 229    fn project_item_model_ids(&self, cx: &AppContext) -> SmallVec<[EntityId; 3]>;
 230    fn for_each_project_item(
 231        &self,
 232        _: &AppContext,
 233        _: &mut dyn FnMut(EntityId, &dyn project2::Item),
 234    );
 235    fn is_singleton(&self, cx: &AppContext) -> bool;
 236    fn boxed_clone(&self) -> Box<dyn ItemHandle>;
 237    fn clone_on_split(
 238        &self,
 239        workspace_id: WorkspaceId,
 240        cx: &mut WindowContext,
 241    ) -> Option<Box<dyn ItemHandle>>;
 242    fn added_to_pane(
 243        &self,
 244        workspace: &mut Workspace,
 245        pane: View<Pane>,
 246        cx: &mut ViewContext<Workspace>,
 247    );
 248    fn deactivated(&self, cx: &mut WindowContext);
 249    fn workspace_deactivated(&self, cx: &mut WindowContext);
 250    fn navigate(&self, data: Box<dyn Any>, cx: &mut WindowContext) -> bool;
 251    fn id(&self) -> EntityId;
 252    fn to_any(&self) -> AnyView;
 253    fn is_dirty(&self, cx: &AppContext) -> bool;
 254    fn has_conflict(&self, cx: &AppContext) -> bool;
 255    fn can_save(&self, cx: &AppContext) -> bool;
 256    fn save(&self, project: Model<Project>, cx: &mut WindowContext) -> Task<Result<()>>;
 257    fn save_as(
 258        &self,
 259        project: Model<Project>,
 260        abs_path: PathBuf,
 261        cx: &mut WindowContext,
 262    ) -> Task<Result<()>>;
 263    fn reload(&self, project: Model<Project>, cx: &mut WindowContext) -> Task<Result<()>>;
 264    fn act_as_type(&self, type_id: TypeId, cx: &AppContext) -> Option<AnyView>;
 265    fn to_followable_item_handle(&self, cx: &AppContext) -> Option<Box<dyn FollowableItemHandle>>;
 266    fn on_release(
 267        &self,
 268        cx: &mut AppContext,
 269        callback: Box<dyn FnOnce(&mut AppContext) + Send>,
 270    ) -> gpui::Subscription;
 271    fn to_searchable_item_handle(&self, cx: &AppContext) -> Option<Box<dyn SearchableItemHandle>>;
 272    fn breadcrumb_location(&self, cx: &AppContext) -> ToolbarItemLocation;
 273    fn breadcrumbs(&self, theme: &Theme, cx: &AppContext) -> Option<Vec<BreadcrumbText>>;
 274    fn serialized_item_kind(&self) -> Option<&'static str>;
 275    fn show_toolbar(&self, cx: &AppContext) -> bool;
 276    fn pixel_position_of_cursor(&self, cx: &AppContext) -> Option<Point<Pixels>>;
 277}
 278
 279pub trait WeakItemHandle: Send + Sync {
 280    fn id(&self) -> EntityId;
 281    fn upgrade(&self) -> Option<Box<dyn ItemHandle>>;
 282}
 283
 284impl dyn ItemHandle {
 285    pub fn downcast<V: 'static>(&self) -> Option<View<V>> {
 286        self.to_any().downcast().ok()
 287    }
 288
 289    pub fn act_as<V: 'static>(&self, cx: &AppContext) -> Option<View<V>> {
 290        self.act_as_type(TypeId::of::<V>(), cx)
 291            .and_then(|t| t.downcast().ok())
 292    }
 293}
 294
 295impl<T: Item> ItemHandle for View<T> {
 296    fn focus_handle(&self, cx: &WindowContext) -> FocusHandle {
 297        self.read(cx).focus_handle()
 298    }
 299
 300    fn subscribe_to_item_events(
 301        &self,
 302        cx: &mut WindowContext,
 303        handler: Box<dyn Fn(ItemEvent, &mut WindowContext) + Send>,
 304    ) -> gpui::Subscription {
 305        cx.subscribe(self, move |_, event, cx| {
 306            for item_event in T::to_item_events(event) {
 307                handler(item_event, cx)
 308            }
 309        })
 310    }
 311
 312    fn tab_tooltip_text(&self, cx: &AppContext) -> Option<SharedString> {
 313        self.read(cx).tab_tooltip_text(cx)
 314    }
 315
 316    fn tab_description(&self, detail: usize, cx: &AppContext) -> Option<SharedString> {
 317        self.read(cx).tab_description(detail, cx)
 318    }
 319
 320    fn tab_content(&self, detail: Option<usize>, cx: &AppContext) -> AnyElement<Pane> {
 321        self.read(cx).tab_content(detail, cx)
 322    }
 323
 324    fn dragged_tab_content(&self, detail: Option<usize>, cx: &AppContext) -> AnyElement<Workspace> {
 325        self.read(cx).tab_content(detail, cx)
 326    }
 327
 328    fn project_path(&self, cx: &AppContext) -> Option<ProjectPath> {
 329        let this = self.read(cx);
 330        let mut result = None;
 331        if this.is_singleton(cx) {
 332            this.for_each_project_item(cx, &mut |_, item| {
 333                result = item.project_path(cx);
 334            });
 335        }
 336        result
 337    }
 338
 339    fn project_entry_ids(&self, cx: &AppContext) -> SmallVec<[ProjectEntryId; 3]> {
 340        let mut result = SmallVec::new();
 341        self.read(cx).for_each_project_item(cx, &mut |_, item| {
 342            if let Some(id) = item.entry_id(cx) {
 343                result.push(id);
 344            }
 345        });
 346        result
 347    }
 348
 349    fn project_item_model_ids(&self, cx: &AppContext) -> SmallVec<[EntityId; 3]> {
 350        let mut result = SmallVec::new();
 351        self.read(cx).for_each_project_item(cx, &mut |id, _| {
 352            result.push(id);
 353        });
 354        result
 355    }
 356
 357    fn for_each_project_item(
 358        &self,
 359        cx: &AppContext,
 360        f: &mut dyn FnMut(EntityId, &dyn project2::Item),
 361    ) {
 362        self.read(cx).for_each_project_item(cx, f)
 363    }
 364
 365    fn is_singleton(&self, cx: &AppContext) -> bool {
 366        self.read(cx).is_singleton(cx)
 367    }
 368
 369    fn boxed_clone(&self) -> Box<dyn ItemHandle> {
 370        Box::new(self.clone())
 371    }
 372
 373    fn clone_on_split(
 374        &self,
 375        workspace_id: WorkspaceId,
 376        cx: &mut WindowContext,
 377    ) -> Option<Box<dyn ItemHandle>> {
 378        self.update(cx, |item, cx| item.clone_on_split(workspace_id, cx))
 379            .map(|handle| Box::new(handle) as Box<dyn ItemHandle>)
 380    }
 381
 382    fn added_to_pane(
 383        &self,
 384        workspace: &mut Workspace,
 385        pane: View<Pane>,
 386        cx: &mut ViewContext<Workspace>,
 387    ) {
 388        let history = pane.read(cx).nav_history_for_item(self);
 389        self.update(cx, |this, cx| {
 390            this.set_nav_history(history, cx);
 391            this.added_to_workspace(workspace, cx);
 392        });
 393
 394        if let Some(followed_item) = self.to_followable_item_handle(cx) {
 395            if let Some(message) = followed_item.to_state_proto(cx) {
 396                workspace.update_followers(
 397                    followed_item.is_project_item(cx),
 398                    proto::update_followers::Variant::CreateView(proto::View {
 399                        id: followed_item
 400                            .remote_id(&workspace.app_state.client, cx)
 401                            .map(|id| id.to_proto()),
 402                        variant: Some(message),
 403                        leader_id: workspace.leader_for_pane(&pane),
 404                    }),
 405                    cx,
 406                );
 407            }
 408        }
 409
 410        if workspace
 411            .panes_by_item
 412            .insert(self.id(), pane.downgrade())
 413            .is_none()
 414        {
 415            let mut pending_autosave = DelayedDebouncedEditAction::new();
 416            let pending_update = Rc::new(RefCell::new(None));
 417            let pending_update_scheduled = Arc::new(AtomicBool::new(false));
 418
 419            let mut event_subscription =
 420                Some(cx.subscribe(self, move |workspace, item, event, cx| {
 421                    let pane = if let Some(pane) = workspace
 422                        .panes_by_item
 423                        .get(&item.id())
 424                        .and_then(|pane| pane.upgrade())
 425                    {
 426                        pane
 427                    } else {
 428                        log::error!("unexpected item event after pane was dropped");
 429                        return;
 430                    };
 431
 432                    if let Some(item) = item.to_followable_item_handle(cx) {
 433                        let is_project_item = item.is_project_item(cx);
 434                        let leader_id = workspace.leader_for_pane(&pane);
 435
 436                        if leader_id.is_some() && item.should_unfollow_on_event(event, cx) {
 437                            workspace.unfollow(&pane, cx);
 438                        }
 439
 440                        if item.add_event_to_update_proto(
 441                            event,
 442                            &mut *pending_update.borrow_mut(),
 443                            cx,
 444                        ) && !pending_update_scheduled.load(Ordering::SeqCst)
 445                        {
 446                            pending_update_scheduled.store(true, Ordering::SeqCst);
 447                            cx.on_next_frame({
 448                                let pending_update = pending_update.clone();
 449                                let pending_update_scheduled = pending_update_scheduled.clone();
 450                                move |this, cx| {
 451                                    pending_update_scheduled.store(false, Ordering::SeqCst);
 452                                    this.update_followers(
 453                                        is_project_item,
 454                                        proto::update_followers::Variant::UpdateView(
 455                                            proto::UpdateView {
 456                                                id: item
 457                                                    .remote_id(&this.app_state.client, cx)
 458                                                    .map(|id| id.to_proto()),
 459                                                variant: pending_update.borrow_mut().take(),
 460                                                leader_id,
 461                                            },
 462                                        ),
 463                                        cx,
 464                                    );
 465                                }
 466                            });
 467                        }
 468                    }
 469
 470                    for item_event in T::to_item_events(event).into_iter() {
 471                        match item_event {
 472                            ItemEvent::CloseItem => {
 473                                pane.update(cx, |pane, cx| {
 474                                    pane.close_item_by_id(item.id(), crate::SaveIntent::Close, cx)
 475                                })
 476                                .detach_and_log_err(cx);
 477                                return;
 478                            }
 479
 480                            ItemEvent::UpdateTab => {
 481                                pane.update(cx, |_, cx| {
 482                                    cx.emit(pane::Event::ChangeItemTitle);
 483                                    cx.notify();
 484                                });
 485                            }
 486
 487                            ItemEvent::Edit => {
 488                                let autosave = WorkspaceSettings::get_global(cx).autosave;
 489                                if let AutosaveSetting::AfterDelay { milliseconds } = autosave {
 490                                    let delay = Duration::from_millis(milliseconds);
 491                                    let item = item.clone();
 492                                    pending_autosave.fire_new(delay, cx, move |workspace, cx| {
 493                                        Pane::autosave_item(&item, workspace.project().clone(), cx)
 494                                    });
 495                                }
 496                            }
 497
 498                            _ => {}
 499                        }
 500                    }
 501                }));
 502
 503            // todo!()
 504            // cx.observe_focus(self, move |workspace, item, focused, cx| {
 505            //     if !focused
 506            //         && WorkspaceSettings::get_global(cx).autosave == AutosaveSetting::OnFocusChange
 507            //     {
 508            //         Pane::autosave_item(&item, workspace.project.clone(), cx)
 509            //             .detach_and_log_err(cx);
 510            //     }
 511            // })
 512            // .detach();
 513
 514            let item_id = self.id();
 515            cx.observe_release(self, move |workspace, _, _| {
 516                workspace.panes_by_item.remove(&item_id);
 517                event_subscription.take();
 518            })
 519            .detach();
 520        }
 521
 522        cx.defer(|workspace, cx| {
 523            workspace.serialize_workspace(cx);
 524        });
 525    }
 526
 527    fn deactivated(&self, cx: &mut WindowContext) {
 528        self.update(cx, |this, cx| this.deactivated(cx));
 529    }
 530
 531    fn workspace_deactivated(&self, cx: &mut WindowContext) {
 532        self.update(cx, |this, cx| this.workspace_deactivated(cx));
 533    }
 534
 535    fn navigate(&self, data: Box<dyn Any>, cx: &mut WindowContext) -> bool {
 536        self.update(cx, |this, cx| this.navigate(data, cx))
 537    }
 538
 539    fn id(&self) -> EntityId {
 540        self.entity_id()
 541    }
 542
 543    fn to_any(&self) -> AnyView {
 544        self.clone().into()
 545    }
 546
 547    fn is_dirty(&self, cx: &AppContext) -> bool {
 548        self.read(cx).is_dirty(cx)
 549    }
 550
 551    fn has_conflict(&self, cx: &AppContext) -> bool {
 552        self.read(cx).has_conflict(cx)
 553    }
 554
 555    fn can_save(&self, cx: &AppContext) -> bool {
 556        self.read(cx).can_save(cx)
 557    }
 558
 559    fn save(&self, project: Model<Project>, cx: &mut WindowContext) -> Task<Result<()>> {
 560        self.update(cx, |item, cx| item.save(project, cx))
 561    }
 562
 563    fn save_as(
 564        &self,
 565        project: Model<Project>,
 566        abs_path: PathBuf,
 567        cx: &mut WindowContext,
 568    ) -> Task<anyhow::Result<()>> {
 569        self.update(cx, |item, cx| item.save_as(project, abs_path, cx))
 570    }
 571
 572    fn reload(&self, project: Model<Project>, cx: &mut WindowContext) -> Task<Result<()>> {
 573        self.update(cx, |item, cx| item.reload(project, cx))
 574    }
 575
 576    fn act_as_type<'a>(&'a self, type_id: TypeId, cx: &'a AppContext) -> Option<AnyView> {
 577        self.read(cx).act_as_type(type_id, self, cx)
 578    }
 579
 580    fn to_followable_item_handle(&self, cx: &AppContext) -> Option<Box<dyn FollowableItemHandle>> {
 581        if cx.has_global::<FollowableItemBuilders>() {
 582            let builders = cx.global::<FollowableItemBuilders>();
 583            let item = self.to_any();
 584            Some(builders.get(&item.entity_type())?.1(&item))
 585        } else {
 586            None
 587        }
 588    }
 589
 590    fn on_release(
 591        &self,
 592        cx: &mut AppContext,
 593        callback: Box<dyn FnOnce(&mut AppContext) + Send>,
 594    ) -> gpui::Subscription {
 595        cx.observe_release(self, move |_, cx| callback(cx))
 596    }
 597
 598    fn to_searchable_item_handle(&self, cx: &AppContext) -> Option<Box<dyn SearchableItemHandle>> {
 599        self.read(cx).as_searchable(self)
 600    }
 601
 602    fn breadcrumb_location(&self, cx: &AppContext) -> ToolbarItemLocation {
 603        self.read(cx).breadcrumb_location()
 604    }
 605
 606    fn breadcrumbs(&self, theme: &Theme, cx: &AppContext) -> Option<Vec<BreadcrumbText>> {
 607        self.read(cx).breadcrumbs(theme, cx)
 608    }
 609
 610    fn serialized_item_kind(&self) -> Option<&'static str> {
 611        T::serialized_item_kind()
 612    }
 613
 614    fn show_toolbar(&self, cx: &AppContext) -> bool {
 615        self.read(cx).show_toolbar()
 616    }
 617
 618    fn pixel_position_of_cursor(&self, cx: &AppContext) -> Option<Point<Pixels>> {
 619        self.read(cx).pixel_position_of_cursor(cx)
 620    }
 621}
 622
 623impl From<Box<dyn ItemHandle>> for AnyView {
 624    fn from(val: Box<dyn ItemHandle>) -> Self {
 625        val.to_any()
 626    }
 627}
 628
 629impl From<&Box<dyn ItemHandle>> for AnyView {
 630    fn from(val: &Box<dyn ItemHandle>) -> Self {
 631        val.to_any()
 632    }
 633}
 634
 635impl Clone for Box<dyn ItemHandle> {
 636    fn clone(&self) -> Box<dyn ItemHandle> {
 637        self.boxed_clone()
 638    }
 639}
 640
 641impl<T: Item> WeakItemHandle for WeakView<T> {
 642    fn id(&self) -> EntityId {
 643        self.entity_id()
 644    }
 645
 646    fn upgrade(&self) -> Option<Box<dyn ItemHandle>> {
 647        self.upgrade().map(|v| Box::new(v) as Box<dyn ItemHandle>)
 648    }
 649}
 650
 651pub trait ProjectItem: Item {
 652    type Item: project2::Item;
 653
 654    fn for_project_item(
 655        project: Model<Project>,
 656        item: Model<Self::Item>,
 657        cx: &mut ViewContext<Self>,
 658    ) -> Self
 659    where
 660        Self: Sized;
 661}
 662
 663pub trait FollowableItem: Item {
 664    fn remote_id(&self) -> Option<ViewId>;
 665    fn to_state_proto(&self, cx: &AppContext) -> Option<proto::view::Variant>;
 666    fn from_state_proto(
 667        pane: View<Pane>,
 668        project: View<Workspace>,
 669        id: ViewId,
 670        state: &mut Option<proto::view::Variant>,
 671        cx: &mut AppContext,
 672    ) -> Option<Task<Result<View<Self>>>>;
 673    fn add_event_to_update_proto(
 674        &self,
 675        event: &Self::Event,
 676        update: &mut Option<proto::update_view::Variant>,
 677        cx: &AppContext,
 678    ) -> bool;
 679    fn apply_update_proto(
 680        &mut self,
 681        project: &Model<Project>,
 682        message: proto::update_view::Variant,
 683        cx: &mut ViewContext<Self>,
 684    ) -> Task<Result<()>>;
 685    fn is_project_item(&self, cx: &AppContext) -> bool;
 686
 687    fn set_leader_peer_id(&mut self, leader_peer_id: Option<PeerId>, cx: &mut ViewContext<Self>);
 688    fn should_unfollow_on_event(event: &Self::Event, cx: &AppContext) -> bool;
 689}
 690
 691pub trait FollowableItemHandle: ItemHandle {
 692    fn remote_id(&self, client: &Arc<Client>, cx: &AppContext) -> Option<ViewId>;
 693    fn set_leader_peer_id(&self, leader_peer_id: Option<PeerId>, cx: &mut WindowContext);
 694    fn to_state_proto(&self, cx: &AppContext) -> Option<proto::view::Variant>;
 695    fn add_event_to_update_proto(
 696        &self,
 697        event: &dyn Any,
 698        update: &mut Option<proto::update_view::Variant>,
 699        cx: &AppContext,
 700    ) -> bool;
 701    fn apply_update_proto(
 702        &self,
 703        project: &Model<Project>,
 704        message: proto::update_view::Variant,
 705        cx: &mut WindowContext,
 706    ) -> Task<Result<()>>;
 707    fn should_unfollow_on_event(&self, event: &dyn Any, cx: &AppContext) -> bool;
 708    fn is_project_item(&self, cx: &AppContext) -> bool;
 709}
 710
 711impl<T: FollowableItem> FollowableItemHandle for View<T> {
 712    fn remote_id(&self, client: &Arc<Client>, cx: &AppContext) -> Option<ViewId> {
 713        self.read(cx).remote_id().or_else(|| {
 714            client.peer_id().map(|creator| ViewId {
 715                creator,
 716                id: self.id().as_u64(),
 717            })
 718        })
 719    }
 720
 721    fn set_leader_peer_id(&self, leader_peer_id: Option<PeerId>, cx: &mut WindowContext) {
 722        self.update(cx, |this, cx| this.set_leader_peer_id(leader_peer_id, cx))
 723    }
 724
 725    fn to_state_proto(&self, cx: &AppContext) -> Option<proto::view::Variant> {
 726        self.read(cx).to_state_proto(cx)
 727    }
 728
 729    fn add_event_to_update_proto(
 730        &self,
 731        event: &dyn Any,
 732        update: &mut Option<proto::update_view::Variant>,
 733        cx: &AppContext,
 734    ) -> bool {
 735        if let Some(event) = event.downcast_ref() {
 736            self.read(cx).add_event_to_update_proto(event, update, cx)
 737        } else {
 738            false
 739        }
 740    }
 741
 742    fn apply_update_proto(
 743        &self,
 744        project: &Model<Project>,
 745        message: proto::update_view::Variant,
 746        cx: &mut WindowContext,
 747    ) -> Task<Result<()>> {
 748        self.update(cx, |this, cx| this.apply_update_proto(project, message, cx))
 749    }
 750
 751    fn should_unfollow_on_event(&self, event: &dyn Any, cx: &AppContext) -> bool {
 752        if let Some(event) = event.downcast_ref() {
 753            T::should_unfollow_on_event(event, cx)
 754        } else {
 755            false
 756        }
 757    }
 758
 759    fn is_project_item(&self, cx: &AppContext) -> bool {
 760        self.read(cx).is_project_item(cx)
 761    }
 762}
 763
 764// #[cfg(any(test, feature = "test-support"))]
 765// pub mod test {
 766//     use super::{Item, ItemEvent};
 767//     use crate::{ItemId, ItemNavHistory, Pane, Workspace, WorkspaceId};
 768//     use gpui::{
 769//         elements::Empty, AnyElement, AppContext, Element, Entity, Model, Task, View,
 770//         ViewContext, View, WeakViewHandle,
 771//     };
 772//     use project2::{Project, ProjectEntryId, ProjectPath, WorktreeId};
 773//     use smallvec::SmallVec;
 774//     use std::{any::Any, borrow::Cow, cell::Cell, path::Path};
 775
 776//     pub struct TestProjectItem {
 777//         pub entry_id: Option<ProjectEntryId>,
 778//         pub project_path: Option<ProjectPath>,
 779//     }
 780
 781//     pub struct TestItem {
 782//         pub workspace_id: WorkspaceId,
 783//         pub state: String,
 784//         pub label: String,
 785//         pub save_count: usize,
 786//         pub save_as_count: usize,
 787//         pub reload_count: usize,
 788//         pub is_dirty: bool,
 789//         pub is_singleton: bool,
 790//         pub has_conflict: bool,
 791//         pub project_items: Vec<Model<TestProjectItem>>,
 792//         pub nav_history: Option<ItemNavHistory>,
 793//         pub tab_descriptions: Option<Vec<&'static str>>,
 794//         pub tab_detail: Cell<Option<usize>>,
 795//     }
 796
 797//     impl Entity for TestProjectItem {
 798//         type Event = ();
 799//     }
 800
 801//     impl project2::Item for TestProjectItem {
 802//         fn entry_id(&self, _: &AppContext) -> Option<ProjectEntryId> {
 803//             self.entry_id
 804//         }
 805
 806//         fn project_path(&self, _: &AppContext) -> Option<ProjectPath> {
 807//             self.project_path.clone()
 808//         }
 809//     }
 810
 811//     pub enum TestItemEvent {
 812//         Edit,
 813//     }
 814
 815//     impl Clone for TestItem {
 816//         fn clone(&self) -> Self {
 817//             Self {
 818//                 state: self.state.clone(),
 819//                 label: self.label.clone(),
 820//                 save_count: self.save_count,
 821//                 save_as_count: self.save_as_count,
 822//                 reload_count: self.reload_count,
 823//                 is_dirty: self.is_dirty,
 824//                 is_singleton: self.is_singleton,
 825//                 has_conflict: self.has_conflict,
 826//                 project_items: self.project_items.clone(),
 827//                 nav_history: None,
 828//                 tab_descriptions: None,
 829//                 tab_detail: Default::default(),
 830//                 workspace_id: self.workspace_id,
 831//             }
 832//         }
 833//     }
 834
 835//     impl TestProjectItem {
 836//         pub fn new(id: u64, path: &str, cx: &mut AppContext) -> Model<Self> {
 837//             let entry_id = Some(ProjectEntryId::from_proto(id));
 838//             let project_path = Some(ProjectPath {
 839//                 worktree_id: WorktreeId::from_usize(0),
 840//                 path: Path::new(path).into(),
 841//             });
 842//             cx.add_model(|_| Self {
 843//                 entry_id,
 844//                 project_path,
 845//             })
 846//         }
 847
 848//         pub fn new_untitled(cx: &mut AppContext) -> Model<Self> {
 849//             cx.add_model(|_| Self {
 850//                 project_path: None,
 851//                 entry_id: None,
 852//             })
 853//         }
 854//     }
 855
 856//     impl TestItem {
 857//         pub fn new() -> Self {
 858//             Self {
 859//                 state: String::new(),
 860//                 label: String::new(),
 861//                 save_count: 0,
 862//                 save_as_count: 0,
 863//                 reload_count: 0,
 864//                 is_dirty: false,
 865//                 has_conflict: false,
 866//                 project_items: Vec::new(),
 867//                 is_singleton: true,
 868//                 nav_history: None,
 869//                 tab_descriptions: None,
 870//                 tab_detail: Default::default(),
 871//                 workspace_id: 0,
 872//             }
 873//         }
 874
 875//         pub fn new_deserialized(id: WorkspaceId) -> Self {
 876//             let mut this = Self::new();
 877//             this.workspace_id = id;
 878//             this
 879//         }
 880
 881//         pub fn with_label(mut self, state: &str) -> Self {
 882//             self.label = state.to_string();
 883//             self
 884//         }
 885
 886//         pub fn with_singleton(mut self, singleton: bool) -> Self {
 887//             self.is_singleton = singleton;
 888//             self
 889//         }
 890
 891//         pub fn with_dirty(mut self, dirty: bool) -> Self {
 892//             self.is_dirty = dirty;
 893//             self
 894//         }
 895
 896//         pub fn with_conflict(mut self, has_conflict: bool) -> Self {
 897//             self.has_conflict = has_conflict;
 898//             self
 899//         }
 900
 901//         pub fn with_project_items(mut self, items: &[Model<TestProjectItem>]) -> Self {
 902//             self.project_items.clear();
 903//             self.project_items.extend(items.iter().cloned());
 904//             self
 905//         }
 906
 907//         pub fn set_state(&mut self, state: String, cx: &mut ViewContext<Self>) {
 908//             self.push_to_nav_history(cx);
 909//             self.state = state;
 910//         }
 911
 912//         fn push_to_nav_history(&mut self, cx: &mut ViewContext<Self>) {
 913//             if let Some(history) = &mut self.nav_history {
 914//                 history.push(Some(Box::new(self.state.clone())), cx);
 915//             }
 916//         }
 917//     }
 918
 919//     impl Entity for TestItem {
 920//         type Event = TestItemEvent;
 921//     }
 922
 923//     impl View for TestItem {
 924//         fn ui_name() -> &'static str {
 925//             "TestItem"
 926//         }
 927
 928//         fn render(&mut self, _: &mut ViewContext<Self>) -> AnyElement<Self> {
 929//             Empty::new().into_any()
 930//         }
 931//     }
 932
 933//     impl Item for TestItem {
 934//         fn tab_description(&self, detail: usize, _: &AppContext) -> Option<Cow<str>> {
 935//             self.tab_descriptions.as_ref().and_then(|descriptions| {
 936//                 let description = *descriptions.get(detail).or_else(|| descriptions.last())?;
 937//                 Some(description.into())
 938//             })
 939//         }
 940
 941//         fn tab_content<V: 'static>(
 942//             &self,
 943//             detail: Option<usize>,
 944//             _: &theme2::Tab,
 945//             _: &AppContext,
 946//         ) -> AnyElement<V> {
 947//             self.tab_detail.set(detail);
 948//             Empty::new().into_any()
 949//         }
 950
 951//         fn for_each_project_item(
 952//             &self,
 953//             cx: &AppContext,
 954//             f: &mut dyn FnMut(usize, &dyn project2::Item),
 955//         ) {
 956//             self.project_items
 957//                 .iter()
 958//                 .for_each(|item| f(item.id(), item.read(cx)))
 959//         }
 960
 961// fn is_singleton(&self, _: &AppContext) -> bool {
 962//     self.is_singleton
 963// }
 964
 965//         fn set_nav_history(&mut self, history: ItemNavHistory, _: &mut ViewContext<Self>) {
 966//             self.nav_history = Some(history);
 967//         }
 968
 969//         fn navigate(&mut self, state: Box<dyn Any>, _: &mut ViewContext<Self>) -> bool {
 970//             let state = *state.downcast::<String>().unwrap_or_default();
 971//             if state != self.state {
 972//                 self.state = state;
 973//                 true
 974//             } else {
 975//                 false
 976//             }
 977//         }
 978
 979//         fn deactivated(&mut self, cx: &mut ViewContext<Self>) {
 980//             self.push_to_nav_history(cx);
 981//         }
 982
 983//         fn clone_on_split(
 984//             &self,
 985//             _workspace_id: WorkspaceId,
 986//             _: &mut ViewContext<Self>,
 987//         ) -> Option<Self>
 988//         where
 989//             Self: Sized,
 990//         {
 991//             Some(self.clone())
 992//         }
 993
 994//         fn is_dirty(&self, _: &AppContext) -> bool {
 995//             self.is_dirty
 996//         }
 997
 998//         fn has_conflict(&self, _: &AppContext) -> bool {
 999//             self.has_conflict
1000//         }
1001
1002//         fn can_save(&self, cx: &AppContext) -> bool {
1003//             !self.project_items.is_empty()
1004//                 && self
1005//                     .project_items
1006//                     .iter()
1007//                     .all(|item| item.read(cx).entry_id.is_some())
1008//         }
1009
1010//         fn save(
1011//             &mut self,
1012//             _: Model<Project>,
1013//             _: &mut ViewContext<Self>,
1014//         ) -> Task<anyhow::Result<()>> {
1015//             self.save_count += 1;
1016//             self.is_dirty = false;
1017//             Task::ready(Ok(()))
1018//         }
1019
1020//         fn save_as(
1021//             &mut self,
1022//             _: Model<Project>,
1023//             _: std::path::PathBuf,
1024//             _: &mut ViewContext<Self>,
1025//         ) -> Task<anyhow::Result<()>> {
1026//             self.save_as_count += 1;
1027//             self.is_dirty = false;
1028//             Task::ready(Ok(()))
1029//         }
1030
1031//         fn reload(
1032//             &mut self,
1033//             _: Model<Project>,
1034//             _: &mut ViewContext<Self>,
1035//         ) -> Task<anyhow::Result<()>> {
1036//             self.reload_count += 1;
1037//             self.is_dirty = false;
1038//             Task::ready(Ok(()))
1039//         }
1040
1041//         fn to_item_events(_: &Self::Event) -> SmallVec<[ItemEvent; 2]> {
1042//             [ItemEvent::UpdateTab, ItemEvent::Edit].into()
1043//         }
1044
1045//         fn serialized_item_kind() -> Option<&'static str> {
1046//             Some("TestItem")
1047//         }
1048
1049//         fn deserialize(
1050//             _project: Model<Project>,
1051//             _workspace: WeakViewHandle<Workspace>,
1052//             workspace_id: WorkspaceId,
1053//             _item_id: ItemId,
1054//             cx: &mut ViewContext<Pane>,
1055//         ) -> Task<anyhow::Result<View<Self>>> {
1056//             let view = cx.add_view(|_cx| Self::new_deserialized(workspace_id));
1057//             Task::Ready(Some(anyhow::Ok(view)))
1058//         }
1059//     }
1060// }