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