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