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