item.rs

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