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