item.rs

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