item.rs

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