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                    proto::update_followers::Variant::CreateView(proto::View {
 408                        id: followed_item
 409                            .remote_id(&workspace.app_state.client, cx)
 410                            .map(|id| id.to_proto()),
 411                        variant: Some(message),
 412                        leader_id: workspace.leader_for_pane(&pane),
 413                    }),
 414                    cx,
 415                );
 416            }
 417        }
 418
 419        if workspace
 420            .panes_by_item
 421            .insert(self.id(), pane.downgrade())
 422            .is_none()
 423        {
 424            let mut pending_autosave = DelayedDebouncedEditAction::new();
 425            let pending_update = Rc::new(RefCell::new(None));
 426            let pending_update_scheduled = Rc::new(AtomicBool::new(false));
 427
 428            let mut event_subscription =
 429                Some(cx.subscribe(self, move |workspace, item, event, cx| {
 430                    let pane = if let Some(pane) = workspace
 431                        .panes_by_item
 432                        .get(&item.id())
 433                        .and_then(|pane| pane.upgrade(cx))
 434                    {
 435                        pane
 436                    } else {
 437                        log::error!("unexpected item event after pane was dropped");
 438                        return;
 439                    };
 440
 441                    if let Some(item) = item.to_followable_item_handle(cx) {
 442                        let leader_id = workspace.leader_for_pane(&pane);
 443
 444                        if leader_id.is_some() && item.should_unfollow_on_event(event, cx) {
 445                            workspace.unfollow(&pane, cx);
 446                        }
 447
 448                        if item.add_event_to_update_proto(
 449                            event,
 450                            &mut *pending_update.borrow_mut(),
 451                            cx,
 452                        ) && !pending_update_scheduled.load(Ordering::SeqCst)
 453                        {
 454                            pending_update_scheduled.store(true, Ordering::SeqCst);
 455                            cx.after_window_update({
 456                                let pending_update = pending_update.clone();
 457                                let pending_update_scheduled = pending_update_scheduled.clone();
 458                                move |this, cx| {
 459                                    pending_update_scheduled.store(false, Ordering::SeqCst);
 460                                    this.update_followers(
 461                                        proto::update_followers::Variant::UpdateView(
 462                                            proto::UpdateView {
 463                                                id: item
 464                                                    .remote_id(&this.app_state.client, cx)
 465                                                    .map(|id| id.to_proto()),
 466                                                variant: pending_update.borrow_mut().take(),
 467                                                leader_id,
 468                                            },
 469                                        ),
 470                                        cx,
 471                                    );
 472                                }
 473                            });
 474                        }
 475                    }
 476
 477                    for item_event in T::to_item_events(event).into_iter() {
 478                        match item_event {
 479                            ItemEvent::CloseItem => {
 480                                pane.update(cx, |pane, cx| {
 481                                    pane.close_item_by_id(item.id(), crate::SaveIntent::Close, cx)
 482                                })
 483                                .detach_and_log_err(cx);
 484                                return;
 485                            }
 486
 487                            ItemEvent::UpdateTab => {
 488                                pane.update(cx, |_, cx| {
 489                                    cx.emit(pane::Event::ChangeItemTitle);
 490                                    cx.notify();
 491                                });
 492                            }
 493
 494                            ItemEvent::Edit => {
 495                                let autosave = settings::get::<WorkspaceSettings>(cx).autosave;
 496                                if let AutosaveSetting::AfterDelay { milliseconds } = autosave {
 497                                    let delay = Duration::from_millis(milliseconds);
 498                                    let item = item.clone();
 499                                    pending_autosave.fire_new(delay, cx, move |workspace, cx| {
 500                                        Pane::autosave_item(&item, workspace.project().clone(), cx)
 501                                    });
 502                                }
 503                            }
 504
 505                            _ => {}
 506                        }
 507                    }
 508                }));
 509
 510            cx.observe_focus(self, move |workspace, item, focused, cx| {
 511                if !focused
 512                    && settings::get::<WorkspaceSettings>(cx).autosave
 513                        == AutosaveSetting::OnFocusChange
 514                {
 515                    Pane::autosave_item(&item, workspace.project.clone(), cx)
 516                        .detach_and_log_err(cx);
 517                }
 518            })
 519            .detach();
 520
 521            let item_id = self.id();
 522            cx.observe_release(self, move |workspace, _, _| {
 523                workspace.panes_by_item.remove(&item_id);
 524                event_subscription.take();
 525            })
 526            .detach();
 527        }
 528
 529        cx.defer(|workspace, cx| {
 530            workspace.serialize_workspace(cx);
 531        });
 532    }
 533
 534    fn deactivated(&self, cx: &mut WindowContext) {
 535        self.update(cx, |this, cx| this.deactivated(cx));
 536    }
 537
 538    fn workspace_deactivated(&self, cx: &mut WindowContext) {
 539        self.update(cx, |this, cx| this.workspace_deactivated(cx));
 540    }
 541
 542    fn navigate(&self, data: Box<dyn Any>, cx: &mut WindowContext) -> bool {
 543        self.update(cx, |this, cx| this.navigate(data, cx))
 544    }
 545
 546    fn id(&self) -> usize {
 547        self.id()
 548    }
 549
 550    fn window(&self) -> AnyWindowHandle {
 551        AnyViewHandle::window(self)
 552    }
 553
 554    fn as_any(&self) -> &AnyViewHandle {
 555        self
 556    }
 557
 558    fn is_dirty(&self, cx: &AppContext) -> bool {
 559        self.read(cx).is_dirty(cx)
 560    }
 561
 562    fn has_conflict(&self, cx: &AppContext) -> bool {
 563        self.read(cx).has_conflict(cx)
 564    }
 565
 566    fn can_save(&self, cx: &AppContext) -> bool {
 567        self.read(cx).can_save(cx)
 568    }
 569
 570    fn save(&self, project: ModelHandle<Project>, cx: &mut WindowContext) -> Task<Result<()>> {
 571        self.update(cx, |item, cx| item.save(project, cx))
 572    }
 573
 574    fn save_as(
 575        &self,
 576        project: ModelHandle<Project>,
 577        abs_path: PathBuf,
 578        cx: &mut WindowContext,
 579    ) -> Task<anyhow::Result<()>> {
 580        self.update(cx, |item, cx| item.save_as(project, abs_path, cx))
 581    }
 582
 583    fn reload(&self, project: ModelHandle<Project>, cx: &mut WindowContext) -> Task<Result<()>> {
 584        self.update(cx, |item, cx| item.reload(project, cx))
 585    }
 586
 587    fn act_as_type<'a>(&'a self, type_id: TypeId, cx: &'a AppContext) -> Option<&'a AnyViewHandle> {
 588        self.read(cx).act_as_type(type_id, self, cx)
 589    }
 590
 591    fn to_followable_item_handle(&self, cx: &AppContext) -> Option<Box<dyn FollowableItemHandle>> {
 592        if cx.has_global::<FollowableItemBuilders>() {
 593            let builders = cx.global::<FollowableItemBuilders>();
 594            let item = self.as_any();
 595            Some(builders.get(&item.view_type())?.1(item))
 596        } else {
 597            None
 598        }
 599    }
 600
 601    fn on_release(
 602        &self,
 603        cx: &mut AppContext,
 604        callback: Box<dyn FnOnce(&mut AppContext)>,
 605    ) -> gpui::Subscription {
 606        cx.observe_release(self, move |_, cx| callback(cx))
 607    }
 608
 609    fn to_searchable_item_handle(&self, cx: &AppContext) -> Option<Box<dyn SearchableItemHandle>> {
 610        self.read(cx).as_searchable(self)
 611    }
 612
 613    fn breadcrumb_location(&self, cx: &AppContext) -> ToolbarItemLocation {
 614        self.read(cx).breadcrumb_location()
 615    }
 616
 617    fn breadcrumbs(&self, theme: &Theme, cx: &AppContext) -> Option<Vec<BreadcrumbText>> {
 618        self.read(cx).breadcrumbs(theme, cx)
 619    }
 620
 621    fn serialized_item_kind(&self) -> Option<&'static str> {
 622        T::serialized_item_kind()
 623    }
 624
 625    fn show_toolbar(&self, cx: &AppContext) -> bool {
 626        self.read(cx).show_toolbar()
 627    }
 628
 629    fn pixel_position_of_cursor(&self, cx: &AppContext) -> Option<Vector2F> {
 630        self.read(cx).pixel_position_of_cursor(cx)
 631    }
 632}
 633
 634impl From<Box<dyn ItemHandle>> for AnyViewHandle {
 635    fn from(val: Box<dyn ItemHandle>) -> Self {
 636        val.as_any().clone()
 637    }
 638}
 639
 640impl From<&Box<dyn ItemHandle>> for AnyViewHandle {
 641    fn from(val: &Box<dyn ItemHandle>) -> Self {
 642        val.as_any().clone()
 643    }
 644}
 645
 646impl Clone for Box<dyn ItemHandle> {
 647    fn clone(&self) -> Box<dyn ItemHandle> {
 648        self.boxed_clone()
 649    }
 650}
 651
 652impl<T: Item> WeakItemHandle for WeakViewHandle<T> {
 653    fn id(&self) -> usize {
 654        self.id()
 655    }
 656
 657    fn window(&self) -> AnyWindowHandle {
 658        self.window()
 659    }
 660
 661    fn upgrade(&self, cx: &AppContext) -> Option<Box<dyn ItemHandle>> {
 662        self.upgrade(cx).map(|v| Box::new(v) as Box<dyn ItemHandle>)
 663    }
 664}
 665
 666pub trait ProjectItem: Item {
 667    type Item: project::Item + gpui::Entity;
 668
 669    fn for_project_item(
 670        project: ModelHandle<Project>,
 671        item: ModelHandle<Self::Item>,
 672        cx: &mut ViewContext<Self>,
 673    ) -> Self;
 674}
 675
 676pub trait FollowableItem: Item {
 677    fn remote_id(&self) -> Option<ViewId>;
 678    fn to_state_proto(&self, cx: &AppContext) -> Option<proto::view::Variant>;
 679    fn from_state_proto(
 680        pane: ViewHandle<Pane>,
 681        project: ViewHandle<Workspace>,
 682        id: ViewId,
 683        state: &mut Option<proto::view::Variant>,
 684        cx: &mut AppContext,
 685    ) -> Option<Task<Result<ViewHandle<Self>>>>;
 686    fn add_event_to_update_proto(
 687        &self,
 688        event: &Self::Event,
 689        update: &mut Option<proto::update_view::Variant>,
 690        cx: &AppContext,
 691    ) -> bool;
 692    fn apply_update_proto(
 693        &mut self,
 694        project: &ModelHandle<Project>,
 695        message: proto::update_view::Variant,
 696        cx: &mut ViewContext<Self>,
 697    ) -> Task<Result<()>>;
 698    fn is_project_item(&self, cx: &AppContext) -> bool;
 699
 700    fn set_leader_peer_id(&mut self, leader_peer_id: Option<PeerId>, cx: &mut ViewContext<Self>);
 701    fn should_unfollow_on_event(event: &Self::Event, cx: &AppContext) -> bool;
 702}
 703
 704pub trait FollowableItemHandle: ItemHandle {
 705    fn remote_id(&self, client: &Arc<Client>, cx: &AppContext) -> Option<ViewId>;
 706    fn set_leader_peer_id(&self, leader_peer_id: Option<PeerId>, cx: &mut WindowContext);
 707    fn to_state_proto(&self, cx: &AppContext) -> Option<proto::view::Variant>;
 708    fn add_event_to_update_proto(
 709        &self,
 710        event: &dyn Any,
 711        update: &mut Option<proto::update_view::Variant>,
 712        cx: &AppContext,
 713    ) -> bool;
 714    fn apply_update_proto(
 715        &self,
 716        project: &ModelHandle<Project>,
 717        message: proto::update_view::Variant,
 718        cx: &mut WindowContext,
 719    ) -> Task<Result<()>>;
 720    fn should_unfollow_on_event(&self, event: &dyn Any, cx: &AppContext) -> bool;
 721    fn is_project_item(&self, cx: &AppContext) -> bool;
 722}
 723
 724impl<T: FollowableItem> FollowableItemHandle for ViewHandle<T> {
 725    fn remote_id(&self, client: &Arc<Client>, cx: &AppContext) -> Option<ViewId> {
 726        self.read(cx).remote_id().or_else(|| {
 727            client.peer_id().map(|creator| ViewId {
 728                creator,
 729                id: self.id() as u64,
 730            })
 731        })
 732    }
 733
 734    fn set_leader_peer_id(&self, leader_peer_id: Option<PeerId>, cx: &mut WindowContext) {
 735        self.update(cx, |this, cx| this.set_leader_peer_id(leader_peer_id, cx))
 736    }
 737
 738    fn to_state_proto(&self, cx: &AppContext) -> Option<proto::view::Variant> {
 739        self.read(cx).to_state_proto(cx)
 740    }
 741
 742    fn add_event_to_update_proto(
 743        &self,
 744        event: &dyn Any,
 745        update: &mut Option<proto::update_view::Variant>,
 746        cx: &AppContext,
 747    ) -> bool {
 748        if let Some(event) = event.downcast_ref() {
 749            self.read(cx).add_event_to_update_proto(event, update, cx)
 750        } else {
 751            false
 752        }
 753    }
 754
 755    fn apply_update_proto(
 756        &self,
 757        project: &ModelHandle<Project>,
 758        message: proto::update_view::Variant,
 759        cx: &mut WindowContext,
 760    ) -> Task<Result<()>> {
 761        self.update(cx, |this, cx| this.apply_update_proto(project, message, cx))
 762    }
 763
 764    fn should_unfollow_on_event(&self, event: &dyn Any, cx: &AppContext) -> bool {
 765        if let Some(event) = event.downcast_ref() {
 766            T::should_unfollow_on_event(event, cx)
 767        } else {
 768            false
 769        }
 770    }
 771
 772    fn is_project_item(&self, cx: &AppContext) -> bool {
 773        self.read(cx).is_project_item(cx)
 774    }
 775}
 776
 777#[cfg(any(test, feature = "test-support"))]
 778pub mod test {
 779    use super::{Item, ItemEvent};
 780    use crate::{ItemId, ItemNavHistory, Pane, Workspace, WorkspaceId};
 781    use gpui::{
 782        elements::Empty, AnyElement, AppContext, Element, Entity, ModelHandle, Task, View,
 783        ViewContext, ViewHandle, WeakViewHandle,
 784    };
 785    use project::{Project, ProjectEntryId, ProjectPath, WorktreeId};
 786    use smallvec::SmallVec;
 787    use std::{any::Any, borrow::Cow, cell::Cell, path::Path};
 788
 789    pub struct TestProjectItem {
 790        pub entry_id: Option<ProjectEntryId>,
 791        pub project_path: Option<ProjectPath>,
 792    }
 793
 794    pub struct TestItem {
 795        pub workspace_id: WorkspaceId,
 796        pub state: String,
 797        pub label: String,
 798        pub save_count: usize,
 799        pub save_as_count: usize,
 800        pub reload_count: usize,
 801        pub is_dirty: bool,
 802        pub is_singleton: bool,
 803        pub has_conflict: bool,
 804        pub project_items: Vec<ModelHandle<TestProjectItem>>,
 805        pub nav_history: Option<ItemNavHistory>,
 806        pub tab_descriptions: Option<Vec<&'static str>>,
 807        pub tab_detail: Cell<Option<usize>>,
 808    }
 809
 810    impl Entity for TestProjectItem {
 811        type Event = ();
 812    }
 813
 814    impl project::Item for TestProjectItem {
 815        fn entry_id(&self, _: &AppContext) -> Option<ProjectEntryId> {
 816            self.entry_id
 817        }
 818
 819        fn project_path(&self, _: &AppContext) -> Option<ProjectPath> {
 820            self.project_path.clone()
 821        }
 822    }
 823
 824    pub enum TestItemEvent {
 825        Edit,
 826    }
 827
 828    impl Clone for TestItem {
 829        fn clone(&self) -> Self {
 830            Self {
 831                state: self.state.clone(),
 832                label: self.label.clone(),
 833                save_count: self.save_count,
 834                save_as_count: self.save_as_count,
 835                reload_count: self.reload_count,
 836                is_dirty: self.is_dirty,
 837                is_singleton: self.is_singleton,
 838                has_conflict: self.has_conflict,
 839                project_items: self.project_items.clone(),
 840                nav_history: None,
 841                tab_descriptions: None,
 842                tab_detail: Default::default(),
 843                workspace_id: self.workspace_id,
 844            }
 845        }
 846    }
 847
 848    impl TestProjectItem {
 849        pub fn new(id: u64, path: &str, cx: &mut AppContext) -> ModelHandle<Self> {
 850            let entry_id = Some(ProjectEntryId::from_proto(id));
 851            let project_path = Some(ProjectPath {
 852                worktree_id: WorktreeId::from_usize(0),
 853                path: Path::new(path).into(),
 854            });
 855            cx.add_model(|_| Self {
 856                entry_id,
 857                project_path,
 858            })
 859        }
 860
 861        pub fn new_untitled(cx: &mut AppContext) -> ModelHandle<Self> {
 862            cx.add_model(|_| Self {
 863                project_path: None,
 864                entry_id: None,
 865            })
 866        }
 867    }
 868
 869    impl TestItem {
 870        pub fn new() -> Self {
 871            Self {
 872                state: String::new(),
 873                label: String::new(),
 874                save_count: 0,
 875                save_as_count: 0,
 876                reload_count: 0,
 877                is_dirty: false,
 878                has_conflict: false,
 879                project_items: Vec::new(),
 880                is_singleton: true,
 881                nav_history: None,
 882                tab_descriptions: None,
 883                tab_detail: Default::default(),
 884                workspace_id: 0,
 885            }
 886        }
 887
 888        pub fn new_deserialized(id: WorkspaceId) -> Self {
 889            let mut this = Self::new();
 890            this.workspace_id = id;
 891            this
 892        }
 893
 894        pub fn with_label(mut self, state: &str) -> Self {
 895            self.label = state.to_string();
 896            self
 897        }
 898
 899        pub fn with_singleton(mut self, singleton: bool) -> Self {
 900            self.is_singleton = singleton;
 901            self
 902        }
 903
 904        pub fn with_dirty(mut self, dirty: bool) -> Self {
 905            self.is_dirty = dirty;
 906            self
 907        }
 908
 909        pub fn with_conflict(mut self, has_conflict: bool) -> Self {
 910            self.has_conflict = has_conflict;
 911            self
 912        }
 913
 914        pub fn with_project_items(mut self, items: &[ModelHandle<TestProjectItem>]) -> Self {
 915            self.project_items.clear();
 916            self.project_items.extend(items.iter().cloned());
 917            self
 918        }
 919
 920        pub fn set_state(&mut self, state: String, cx: &mut ViewContext<Self>) {
 921            self.push_to_nav_history(cx);
 922            self.state = state;
 923        }
 924
 925        fn push_to_nav_history(&mut self, cx: &mut ViewContext<Self>) {
 926            if let Some(history) = &mut self.nav_history {
 927                history.push(Some(Box::new(self.state.clone())), cx);
 928            }
 929        }
 930    }
 931
 932    impl Entity for TestItem {
 933        type Event = TestItemEvent;
 934    }
 935
 936    impl View for TestItem {
 937        fn ui_name() -> &'static str {
 938            "TestItem"
 939        }
 940
 941        fn render(&mut self, _: &mut ViewContext<Self>) -> AnyElement<Self> {
 942            Empty::new().into_any()
 943        }
 944    }
 945
 946    impl Item for TestItem {
 947        fn tab_description(&self, detail: usize, _: &AppContext) -> Option<Cow<str>> {
 948            self.tab_descriptions.as_ref().and_then(|descriptions| {
 949                let description = *descriptions.get(detail).or_else(|| descriptions.last())?;
 950                Some(description.into())
 951            })
 952        }
 953
 954        fn tab_content<V: 'static>(
 955            &self,
 956            detail: Option<usize>,
 957            _: &theme::Tab,
 958            _: &AppContext,
 959        ) -> AnyElement<V> {
 960            self.tab_detail.set(detail);
 961            Empty::new().into_any()
 962        }
 963
 964        fn for_each_project_item(
 965            &self,
 966            cx: &AppContext,
 967            f: &mut dyn FnMut(usize, &dyn project::Item),
 968        ) {
 969            self.project_items
 970                .iter()
 971                .for_each(|item| f(item.id(), item.read(cx)))
 972        }
 973
 974        fn is_singleton(&self, _: &AppContext) -> bool {
 975            self.is_singleton
 976        }
 977
 978        fn set_nav_history(&mut self, history: ItemNavHistory, _: &mut ViewContext<Self>) {
 979            self.nav_history = Some(history);
 980        }
 981
 982        fn navigate(&mut self, state: Box<dyn Any>, _: &mut ViewContext<Self>) -> bool {
 983            let state = *state.downcast::<String>().unwrap_or_default();
 984            if state != self.state {
 985                self.state = state;
 986                true
 987            } else {
 988                false
 989            }
 990        }
 991
 992        fn deactivated(&mut self, cx: &mut ViewContext<Self>) {
 993            self.push_to_nav_history(cx);
 994        }
 995
 996        fn clone_on_split(
 997            &self,
 998            _workspace_id: WorkspaceId,
 999            _: &mut ViewContext<Self>,
1000        ) -> Option<Self>
1001        where
1002            Self: Sized,
1003        {
1004            Some(self.clone())
1005        }
1006
1007        fn is_dirty(&self, _: &AppContext) -> bool {
1008            self.is_dirty
1009        }
1010
1011        fn has_conflict(&self, _: &AppContext) -> bool {
1012            self.has_conflict
1013        }
1014
1015        fn can_save(&self, cx: &AppContext) -> bool {
1016            !self.project_items.is_empty()
1017                && self
1018                    .project_items
1019                    .iter()
1020                    .all(|item| item.read(cx).entry_id.is_some())
1021        }
1022
1023        fn save(
1024            &mut self,
1025            _: ModelHandle<Project>,
1026            _: &mut ViewContext<Self>,
1027        ) -> Task<anyhow::Result<()>> {
1028            self.save_count += 1;
1029            self.is_dirty = false;
1030            Task::ready(Ok(()))
1031        }
1032
1033        fn save_as(
1034            &mut self,
1035            _: ModelHandle<Project>,
1036            _: std::path::PathBuf,
1037            _: &mut ViewContext<Self>,
1038        ) -> Task<anyhow::Result<()>> {
1039            self.save_as_count += 1;
1040            self.is_dirty = false;
1041            Task::ready(Ok(()))
1042        }
1043
1044        fn reload(
1045            &mut self,
1046            _: ModelHandle<Project>,
1047            _: &mut ViewContext<Self>,
1048        ) -> Task<anyhow::Result<()>> {
1049            self.reload_count += 1;
1050            self.is_dirty = false;
1051            Task::ready(Ok(()))
1052        }
1053
1054        fn to_item_events(_: &Self::Event) -> SmallVec<[ItemEvent; 2]> {
1055            [ItemEvent::UpdateTab, ItemEvent::Edit].into()
1056        }
1057
1058        fn serialized_item_kind() -> Option<&'static str> {
1059            Some("TestItem")
1060        }
1061
1062        fn deserialize(
1063            _project: ModelHandle<Project>,
1064            _workspace: WeakViewHandle<Workspace>,
1065            workspace_id: WorkspaceId,
1066            _item_id: ItemId,
1067            cx: &mut ViewContext<Pane>,
1068        ) -> Task<anyhow::Result<ViewHandle<Self>>> {
1069            let view = cx.add_view(|_cx| Self::new_deserialized(workspace_id));
1070            Task::Ready(Some(anyhow::Ok(view)))
1071        }
1072    }
1073}