workspace.rs

   1/// NOTE: Focus only 'takes' after an update has flushed_effects. Pane sends an event in on_focus_in
   2/// which the workspace uses to change the activated pane.
   3///
   4/// This may cause issues when you're trying to write tests that use workspace focus to add items at
   5/// specific locations.
   6pub mod dock;
   7pub mod pane;
   8pub mod pane_group;
   9pub mod searchable;
  10pub mod sidebar;
  11mod status_bar;
  12mod toolbar;
  13
  14use anyhow::{anyhow, Context, Result};
  15use client::{proto, Client, Contact, PeerId, Subscription, TypedEnvelope, UserStore};
  16use collections::{hash_map, HashMap, HashSet};
  17use dock::{DefaultItemFactory, Dock, ToggleDockButton};
  18use drag_and_drop::DragAndDrop;
  19use futures::{channel::oneshot, FutureExt};
  20use gpui::{
  21    actions,
  22    elements::*,
  23    impl_actions, impl_internal_actions,
  24    platform::{CursorStyle, WindowOptions},
  25    AnyModelHandle, AnyViewHandle, AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle,
  26    MouseButton, MutableAppContext, PathPromptOptions, PromptLevel, RenderContext, Task, View,
  27    ViewContext, ViewHandle, WeakViewHandle,
  28};
  29use language::LanguageRegistry;
  30use log::{error, warn};
  31pub use pane::*;
  32pub use pane_group::*;
  33use postage::prelude::Stream;
  34use project::{fs, Fs, Project, ProjectEntryId, ProjectPath, ProjectStore, Worktree, WorktreeId};
  35use searchable::SearchableItemHandle;
  36use serde::Deserialize;
  37use settings::{Autosave, DockAnchor, Settings};
  38use sidebar::{Sidebar, SidebarButtons, SidebarSide, ToggleSidebarItem};
  39use smallvec::SmallVec;
  40use status_bar::StatusBar;
  41pub use status_bar::StatusItemView;
  42use std::{
  43    any::{Any, TypeId},
  44    borrow::Cow,
  45    cell::RefCell,
  46    fmt,
  47    future::Future,
  48    mem,
  49    path::{Path, PathBuf},
  50    rc::Rc,
  51    sync::{
  52        atomic::{AtomicBool, Ordering::SeqCst},
  53        Arc,
  54    },
  55    time::Duration,
  56};
  57use theme::{Theme, ThemeRegistry};
  58pub use toolbar::{ToolbarItemLocation, ToolbarItemView};
  59use util::ResultExt;
  60
  61type ProjectItemBuilders = HashMap<
  62    TypeId,
  63    fn(ModelHandle<Project>, AnyModelHandle, &mut ViewContext<Pane>) -> Box<dyn ItemHandle>,
  64>;
  65
  66type FollowableItemBuilder = fn(
  67    ViewHandle<Pane>,
  68    ModelHandle<Project>,
  69    &mut Option<proto::view::Variant>,
  70    &mut MutableAppContext,
  71) -> Option<Task<Result<Box<dyn FollowableItemHandle>>>>;
  72type FollowableItemBuilders = HashMap<
  73    TypeId,
  74    (
  75        FollowableItemBuilder,
  76        fn(AnyViewHandle) -> Box<dyn FollowableItemHandle>,
  77    ),
  78>;
  79
  80#[derive(Clone, PartialEq)]
  81pub struct RemoveWorktreeFromProject(pub WorktreeId);
  82
  83actions!(
  84    workspace,
  85    [
  86        Open,
  87        NewFile,
  88        NewWindow,
  89        CloseWindow,
  90        AddFolderToProject,
  91        Unfollow,
  92        Save,
  93        SaveAs,
  94        SaveAll,
  95        ActivatePreviousPane,
  96        ActivateNextPane,
  97        FollowNextCollaborator,
  98        ToggleLeftSidebar,
  99        ToggleRightSidebar,
 100        NewTerminal,
 101        NewSearch
 102    ]
 103);
 104
 105#[derive(Clone, PartialEq)]
 106pub struct OpenPaths {
 107    pub paths: Vec<PathBuf>,
 108}
 109
 110#[derive(Clone, Deserialize, PartialEq)]
 111pub struct ToggleProjectOnline {
 112    #[serde(skip_deserializing)]
 113    pub project: Option<ModelHandle<Project>>,
 114}
 115
 116#[derive(Clone, Deserialize, PartialEq)]
 117pub struct ActivatePane(pub usize);
 118
 119#[derive(Clone, PartialEq)]
 120pub struct ToggleFollow(pub PeerId);
 121
 122#[derive(Clone, PartialEq)]
 123pub struct JoinProject {
 124    pub contact: Arc<Contact>,
 125    pub project_index: usize,
 126}
 127
 128impl_internal_actions!(
 129    workspace,
 130    [
 131        OpenPaths,
 132        ToggleFollow,
 133        JoinProject,
 134        RemoveWorktreeFromProject
 135    ]
 136);
 137impl_actions!(workspace, [ToggleProjectOnline, ActivatePane]);
 138
 139pub fn init(app_state: Arc<AppState>, cx: &mut MutableAppContext) {
 140    pane::init(cx);
 141    dock::init(cx);
 142
 143    cx.add_global_action(open);
 144    cx.add_global_action({
 145        let app_state = Arc::downgrade(&app_state);
 146        move |action: &OpenPaths, cx: &mut MutableAppContext| {
 147            if let Some(app_state) = app_state.upgrade() {
 148                open_paths(&action.paths, &app_state, cx).detach();
 149            }
 150        }
 151    });
 152    cx.add_global_action({
 153        let app_state = Arc::downgrade(&app_state);
 154        move |_: &NewFile, cx: &mut MutableAppContext| {
 155            if let Some(app_state) = app_state.upgrade() {
 156                open_new(&app_state, cx)
 157            }
 158        }
 159    });
 160    cx.add_global_action({
 161        let app_state = Arc::downgrade(&app_state);
 162        move |_: &NewWindow, cx: &mut MutableAppContext| {
 163            if let Some(app_state) = app_state.upgrade() {
 164                open_new(&app_state, cx)
 165            }
 166        }
 167    });
 168
 169    cx.add_async_action(Workspace::toggle_follow);
 170    cx.add_async_action(Workspace::follow_next_collaborator);
 171    cx.add_async_action(Workspace::close);
 172    cx.add_async_action(Workspace::save_all);
 173    cx.add_action(Workspace::add_folder_to_project);
 174    cx.add_action(Workspace::remove_folder_from_project);
 175    cx.add_action(Workspace::toggle_project_online);
 176    cx.add_action(
 177        |workspace: &mut Workspace, _: &Unfollow, cx: &mut ViewContext<Workspace>| {
 178            let pane = workspace.active_pane().clone();
 179            workspace.unfollow(&pane, cx);
 180        },
 181    );
 182    cx.add_action(
 183        |workspace: &mut Workspace, _: &Save, cx: &mut ViewContext<Workspace>| {
 184            workspace.save_active_item(false, cx).detach_and_log_err(cx);
 185        },
 186    );
 187    cx.add_action(
 188        |workspace: &mut Workspace, _: &SaveAs, cx: &mut ViewContext<Workspace>| {
 189            workspace.save_active_item(true, cx).detach_and_log_err(cx);
 190        },
 191    );
 192    cx.add_action(Workspace::toggle_sidebar_item);
 193    cx.add_action(Workspace::focus_center);
 194    cx.add_action(|workspace: &mut Workspace, _: &ActivatePreviousPane, cx| {
 195        workspace.activate_previous_pane(cx)
 196    });
 197    cx.add_action(|workspace: &mut Workspace, _: &ActivateNextPane, cx| {
 198        workspace.activate_next_pane(cx)
 199    });
 200    cx.add_action(|workspace: &mut Workspace, _: &ToggleLeftSidebar, cx| {
 201        workspace.toggle_sidebar(SidebarSide::Left, cx);
 202    });
 203    cx.add_action(|workspace: &mut Workspace, _: &ToggleRightSidebar, cx| {
 204        workspace.toggle_sidebar(SidebarSide::Right, cx);
 205    });
 206    cx.add_action(Workspace::activate_pane_at_index);
 207
 208    let client = &app_state.client;
 209    client.add_view_request_handler(Workspace::handle_follow);
 210    client.add_view_message_handler(Workspace::handle_unfollow);
 211    client.add_view_message_handler(Workspace::handle_update_followers);
 212}
 213
 214pub fn register_project_item<I: ProjectItem>(cx: &mut MutableAppContext) {
 215    cx.update_default_global(|builders: &mut ProjectItemBuilders, _| {
 216        builders.insert(TypeId::of::<I::Item>(), |project, model, cx| {
 217            let item = model.downcast::<I::Item>().unwrap();
 218            Box::new(cx.add_view(|cx| I::for_project_item(project, item, cx)))
 219        });
 220    });
 221}
 222
 223pub fn register_followable_item<I: FollowableItem>(cx: &mut MutableAppContext) {
 224    cx.update_default_global(|builders: &mut FollowableItemBuilders, _| {
 225        builders.insert(
 226            TypeId::of::<I>(),
 227            (
 228                |pane, project, state, cx| {
 229                    I::from_state_proto(pane, project, state, cx).map(|task| {
 230                        cx.foreground()
 231                            .spawn(async move { Ok(Box::new(task.await?) as Box<_>) })
 232                    })
 233                },
 234                |this| Box::new(this.downcast::<I>().unwrap()),
 235            ),
 236        );
 237    });
 238}
 239
 240pub struct AppState {
 241    pub languages: Arc<LanguageRegistry>,
 242    pub themes: Arc<ThemeRegistry>,
 243    pub client: Arc<client::Client>,
 244    pub user_store: ModelHandle<client::UserStore>,
 245    pub project_store: ModelHandle<ProjectStore>,
 246    pub fs: Arc<dyn fs::Fs>,
 247    pub build_window_options: fn() -> WindowOptions<'static>,
 248    pub initialize_workspace: fn(&mut Workspace, &Arc<AppState>, &mut ViewContext<Workspace>),
 249    pub default_item_factory: DefaultItemFactory,
 250}
 251
 252#[derive(Eq, PartialEq, Hash)]
 253pub enum ItemEvent {
 254    CloseItem,
 255    UpdateTab,
 256    UpdateBreadcrumbs,
 257    Edit,
 258}
 259
 260pub trait Item: View {
 261    fn deactivated(&mut self, _: &mut ViewContext<Self>) {}
 262    fn workspace_deactivated(&mut self, _: &mut ViewContext<Self>) {}
 263    fn navigate(&mut self, _: Box<dyn Any>, _: &mut ViewContext<Self>) -> bool {
 264        false
 265    }
 266    fn tab_description<'a>(&'a self, _: usize, _: &'a AppContext) -> Option<Cow<'a, str>> {
 267        None
 268    }
 269    fn tab_content(&self, detail: Option<usize>, style: &theme::Tab, cx: &AppContext)
 270        -> ElementBox;
 271    fn project_path(&self, cx: &AppContext) -> Option<ProjectPath>;
 272    fn project_entry_ids(&self, cx: &AppContext) -> SmallVec<[ProjectEntryId; 3]>;
 273    fn is_singleton(&self, cx: &AppContext) -> bool;
 274    fn set_nav_history(&mut self, _: ItemNavHistory, _: &mut ViewContext<Self>);
 275    fn clone_on_split(&self, _: &mut ViewContext<Self>) -> Option<Self>
 276    where
 277        Self: Sized,
 278    {
 279        None
 280    }
 281    fn is_dirty(&self, _: &AppContext) -> bool {
 282        false
 283    }
 284    fn has_conflict(&self, _: &AppContext) -> bool {
 285        false
 286    }
 287    fn can_save(&self, cx: &AppContext) -> bool;
 288    fn save(
 289        &mut self,
 290        project: ModelHandle<Project>,
 291        cx: &mut ViewContext<Self>,
 292    ) -> Task<Result<()>>;
 293    fn save_as(
 294        &mut self,
 295        project: ModelHandle<Project>,
 296        abs_path: PathBuf,
 297        cx: &mut ViewContext<Self>,
 298    ) -> Task<Result<()>>;
 299    fn reload(
 300        &mut self,
 301        project: ModelHandle<Project>,
 302        cx: &mut ViewContext<Self>,
 303    ) -> Task<Result<()>>;
 304    fn to_item_events(event: &Self::Event) -> Vec<ItemEvent>;
 305    fn act_as_type(
 306        &self,
 307        type_id: TypeId,
 308        self_handle: &ViewHandle<Self>,
 309        _: &AppContext,
 310    ) -> Option<AnyViewHandle> {
 311        if TypeId::of::<Self>() == type_id {
 312            Some(self_handle.into())
 313        } else {
 314            None
 315        }
 316    }
 317    fn as_searchable(&self, _: &ViewHandle<Self>) -> Option<Box<dyn SearchableItemHandle>> {
 318        None
 319    }
 320
 321    fn breadcrumb_location(&self) -> ToolbarItemLocation {
 322        ToolbarItemLocation::Hidden
 323    }
 324    fn breadcrumbs(&self, _theme: &Theme, _cx: &AppContext) -> Option<Vec<ElementBox>> {
 325        None
 326    }
 327}
 328
 329pub trait ProjectItem: Item {
 330    type Item: project::Item;
 331
 332    fn for_project_item(
 333        project: ModelHandle<Project>,
 334        item: ModelHandle<Self::Item>,
 335        cx: &mut ViewContext<Self>,
 336    ) -> Self;
 337}
 338
 339pub trait FollowableItem: Item {
 340    fn to_state_proto(&self, cx: &AppContext) -> Option<proto::view::Variant>;
 341    fn from_state_proto(
 342        pane: ViewHandle<Pane>,
 343        project: ModelHandle<Project>,
 344        state: &mut Option<proto::view::Variant>,
 345        cx: &mut MutableAppContext,
 346    ) -> Option<Task<Result<ViewHandle<Self>>>>;
 347    fn add_event_to_update_proto(
 348        &self,
 349        event: &Self::Event,
 350        update: &mut Option<proto::update_view::Variant>,
 351        cx: &AppContext,
 352    ) -> bool;
 353    fn apply_update_proto(
 354        &mut self,
 355        message: proto::update_view::Variant,
 356        cx: &mut ViewContext<Self>,
 357    ) -> Result<()>;
 358
 359    fn set_leader_replica_id(&mut self, leader_replica_id: Option<u16>, cx: &mut ViewContext<Self>);
 360    fn should_unfollow_on_event(event: &Self::Event, cx: &AppContext) -> bool;
 361}
 362
 363pub trait FollowableItemHandle: ItemHandle {
 364    fn set_leader_replica_id(&self, leader_replica_id: Option<u16>, cx: &mut MutableAppContext);
 365    fn to_state_proto(&self, cx: &AppContext) -> Option<proto::view::Variant>;
 366    fn add_event_to_update_proto(
 367        &self,
 368        event: &dyn Any,
 369        update: &mut Option<proto::update_view::Variant>,
 370        cx: &AppContext,
 371    ) -> bool;
 372    fn apply_update_proto(
 373        &self,
 374        message: proto::update_view::Variant,
 375        cx: &mut MutableAppContext,
 376    ) -> Result<()>;
 377    fn should_unfollow_on_event(&self, event: &dyn Any, cx: &AppContext) -> bool;
 378}
 379
 380impl<T: FollowableItem> FollowableItemHandle for ViewHandle<T> {
 381    fn set_leader_replica_id(&self, leader_replica_id: Option<u16>, cx: &mut MutableAppContext) {
 382        self.update(cx, |this, cx| {
 383            this.set_leader_replica_id(leader_replica_id, cx)
 384        })
 385    }
 386
 387    fn to_state_proto(&self, cx: &AppContext) -> Option<proto::view::Variant> {
 388        self.read(cx).to_state_proto(cx)
 389    }
 390
 391    fn add_event_to_update_proto(
 392        &self,
 393        event: &dyn Any,
 394        update: &mut Option<proto::update_view::Variant>,
 395        cx: &AppContext,
 396    ) -> bool {
 397        if let Some(event) = event.downcast_ref() {
 398            self.read(cx).add_event_to_update_proto(event, update, cx)
 399        } else {
 400            false
 401        }
 402    }
 403
 404    fn apply_update_proto(
 405        &self,
 406        message: proto::update_view::Variant,
 407        cx: &mut MutableAppContext,
 408    ) -> Result<()> {
 409        self.update(cx, |this, cx| this.apply_update_proto(message, cx))
 410    }
 411
 412    fn should_unfollow_on_event(&self, event: &dyn Any, cx: &AppContext) -> bool {
 413        if let Some(event) = event.downcast_ref() {
 414            T::should_unfollow_on_event(event, cx)
 415        } else {
 416            false
 417        }
 418    }
 419}
 420
 421pub trait ItemHandle: 'static + fmt::Debug {
 422    fn subscribe_to_item_events(
 423        &self,
 424        cx: &mut MutableAppContext,
 425        handler: Box<dyn Fn(ItemEvent, &mut MutableAppContext)>,
 426    ) -> gpui::Subscription;
 427    fn tab_description<'a>(&self, detail: usize, cx: &'a AppContext) -> Option<Cow<'a, str>>;
 428    fn tab_content(&self, detail: Option<usize>, style: &theme::Tab, cx: &AppContext)
 429        -> ElementBox;
 430    fn project_path(&self, cx: &AppContext) -> Option<ProjectPath>;
 431    fn project_entry_ids(&self, cx: &AppContext) -> SmallVec<[ProjectEntryId; 3]>;
 432    fn is_singleton(&self, cx: &AppContext) -> bool;
 433    fn boxed_clone(&self) -> Box<dyn ItemHandle>;
 434    fn clone_on_split(&self, cx: &mut MutableAppContext) -> Option<Box<dyn ItemHandle>>;
 435    fn added_to_pane(
 436        &self,
 437        workspace: &mut Workspace,
 438        pane: ViewHandle<Pane>,
 439        cx: &mut ViewContext<Workspace>,
 440    );
 441    fn deactivated(&self, cx: &mut MutableAppContext);
 442    fn workspace_deactivated(&self, cx: &mut MutableAppContext);
 443    fn navigate(&self, data: Box<dyn Any>, cx: &mut MutableAppContext) -> bool;
 444    fn id(&self) -> usize;
 445    fn window_id(&self) -> usize;
 446    fn to_any(&self) -> AnyViewHandle;
 447    fn is_dirty(&self, cx: &AppContext) -> bool;
 448    fn has_conflict(&self, cx: &AppContext) -> bool;
 449    fn can_save(&self, cx: &AppContext) -> bool;
 450    fn save(&self, project: ModelHandle<Project>, cx: &mut MutableAppContext) -> Task<Result<()>>;
 451    fn save_as(
 452        &self,
 453        project: ModelHandle<Project>,
 454        abs_path: PathBuf,
 455        cx: &mut MutableAppContext,
 456    ) -> Task<Result<()>>;
 457    fn reload(&self, project: ModelHandle<Project>, cx: &mut MutableAppContext)
 458        -> Task<Result<()>>;
 459    fn act_as_type(&self, type_id: TypeId, cx: &AppContext) -> Option<AnyViewHandle>;
 460    fn to_followable_item_handle(&self, cx: &AppContext) -> Option<Box<dyn FollowableItemHandle>>;
 461    fn on_release(
 462        &self,
 463        cx: &mut MutableAppContext,
 464        callback: Box<dyn FnOnce(&mut MutableAppContext)>,
 465    ) -> gpui::Subscription;
 466    fn to_searchable_item_handle(&self, cx: &AppContext) -> Option<Box<dyn SearchableItemHandle>>;
 467    fn breadcrumb_location(&self, cx: &AppContext) -> ToolbarItemLocation;
 468    fn breadcrumbs(&self, theme: &Theme, cx: &AppContext) -> Option<Vec<ElementBox>>;
 469}
 470
 471pub trait WeakItemHandle {
 472    fn id(&self) -> usize;
 473    fn window_id(&self) -> usize;
 474    fn upgrade(&self, cx: &AppContext) -> Option<Box<dyn ItemHandle>>;
 475}
 476
 477impl dyn ItemHandle {
 478    pub fn downcast<T: View>(&self) -> Option<ViewHandle<T>> {
 479        self.to_any().downcast()
 480    }
 481
 482    pub fn act_as<T: View>(&self, cx: &AppContext) -> Option<ViewHandle<T>> {
 483        self.act_as_type(TypeId::of::<T>(), cx)
 484            .and_then(|t| t.downcast())
 485    }
 486}
 487
 488impl<T: Item> ItemHandle for ViewHandle<T> {
 489    fn subscribe_to_item_events(
 490        &self,
 491        cx: &mut MutableAppContext,
 492        handler: Box<dyn Fn(ItemEvent, &mut MutableAppContext)>,
 493    ) -> gpui::Subscription {
 494        cx.subscribe(self, move |_, event, cx| {
 495            for item_event in T::to_item_events(event) {
 496                handler(item_event, cx)
 497            }
 498        })
 499    }
 500
 501    fn tab_description<'a>(&self, detail: usize, cx: &'a AppContext) -> Option<Cow<'a, str>> {
 502        self.read(cx).tab_description(detail, cx)
 503    }
 504
 505    fn tab_content(
 506        &self,
 507        detail: Option<usize>,
 508        style: &theme::Tab,
 509        cx: &AppContext,
 510    ) -> ElementBox {
 511        self.read(cx).tab_content(detail, style, cx)
 512    }
 513
 514    fn project_path(&self, cx: &AppContext) -> Option<ProjectPath> {
 515        self.read(cx).project_path(cx)
 516    }
 517
 518    fn project_entry_ids(&self, cx: &AppContext) -> SmallVec<[ProjectEntryId; 3]> {
 519        self.read(cx).project_entry_ids(cx)
 520    }
 521
 522    fn is_singleton(&self, cx: &AppContext) -> bool {
 523        self.read(cx).is_singleton(cx)
 524    }
 525
 526    fn boxed_clone(&self) -> Box<dyn ItemHandle> {
 527        Box::new(self.clone())
 528    }
 529
 530    fn clone_on_split(&self, cx: &mut MutableAppContext) -> Option<Box<dyn ItemHandle>> {
 531        self.update(cx, |item, cx| {
 532            cx.add_option_view(|cx| item.clone_on_split(cx))
 533        })
 534        .map(|handle| Box::new(handle) as Box<dyn ItemHandle>)
 535    }
 536
 537    fn added_to_pane(
 538        &self,
 539        workspace: &mut Workspace,
 540        pane: ViewHandle<Pane>,
 541        cx: &mut ViewContext<Workspace>,
 542    ) {
 543        let history = pane.read(cx).nav_history_for_item(self);
 544        self.update(cx, |this, cx| this.set_nav_history(history, cx));
 545
 546        if let Some(followed_item) = self.to_followable_item_handle(cx) {
 547            if let Some(message) = followed_item.to_state_proto(cx) {
 548                workspace.update_followers(
 549                    proto::update_followers::Variant::CreateView(proto::View {
 550                        id: followed_item.id() as u64,
 551                        variant: Some(message),
 552                        leader_id: workspace.leader_for_pane(&pane).map(|id| id.0),
 553                    }),
 554                    cx,
 555                );
 556            }
 557        }
 558
 559        if workspace
 560            .panes_by_item
 561            .insert(self.id(), pane.downgrade())
 562            .is_none()
 563        {
 564            let mut pending_autosave = None;
 565            let mut cancel_pending_autosave = oneshot::channel::<()>().0;
 566            let pending_update = Rc::new(RefCell::new(None));
 567            let pending_update_scheduled = Rc::new(AtomicBool::new(false));
 568
 569            let mut event_subscription =
 570                Some(cx.subscribe(self, move |workspace, item, event, cx| {
 571                    let pane = if let Some(pane) = workspace
 572                        .panes_by_item
 573                        .get(&item.id())
 574                        .and_then(|pane| pane.upgrade(cx))
 575                    {
 576                        pane
 577                    } else {
 578                        log::error!("unexpected item event after pane was dropped");
 579                        return;
 580                    };
 581
 582                    if let Some(item) = item.to_followable_item_handle(cx) {
 583                        let leader_id = workspace.leader_for_pane(&pane);
 584
 585                        if leader_id.is_some() && item.should_unfollow_on_event(event, cx) {
 586                            workspace.unfollow(&pane, cx);
 587                        }
 588
 589                        if item.add_event_to_update_proto(
 590                            event,
 591                            &mut *pending_update.borrow_mut(),
 592                            cx,
 593                        ) && !pending_update_scheduled.load(SeqCst)
 594                        {
 595                            pending_update_scheduled.store(true, SeqCst);
 596                            cx.after_window_update({
 597                                let pending_update = pending_update.clone();
 598                                let pending_update_scheduled = pending_update_scheduled.clone();
 599                                move |this, cx| {
 600                                    pending_update_scheduled.store(false, SeqCst);
 601                                    this.update_followers(
 602                                        proto::update_followers::Variant::UpdateView(
 603                                            proto::UpdateView {
 604                                                id: item.id() as u64,
 605                                                variant: pending_update.borrow_mut().take(),
 606                                                leader_id: leader_id.map(|id| id.0),
 607                                            },
 608                                        ),
 609                                        cx,
 610                                    );
 611                                }
 612                            });
 613                        }
 614                    }
 615
 616                    for item_event in T::to_item_events(event).into_iter() {
 617                        match item_event {
 618                            ItemEvent::CloseItem => {
 619                                Pane::close_item(workspace, pane, item.id(), cx)
 620                                    .detach_and_log_err(cx);
 621                                return;
 622                            }
 623                            ItemEvent::UpdateTab => {
 624                                pane.update(cx, |_, cx| {
 625                                    cx.emit(pane::Event::ChangeItemTitle);
 626                                    cx.notify();
 627                                });
 628                            }
 629                            ItemEvent::Edit => {
 630                                if let Autosave::AfterDelay { milliseconds } =
 631                                    cx.global::<Settings>().autosave
 632                                {
 633                                    let prev_autosave = pending_autosave
 634                                        .take()
 635                                        .unwrap_or_else(|| Task::ready(Some(())));
 636                                    let (cancel_tx, mut cancel_rx) = oneshot::channel::<()>();
 637                                    let prev_cancel_tx =
 638                                        mem::replace(&mut cancel_pending_autosave, cancel_tx);
 639                                    let project = workspace.project.downgrade();
 640                                    let _ = prev_cancel_tx.send(());
 641                                    let item = item.clone();
 642                                    pending_autosave =
 643                                        Some(cx.spawn_weak(|_, mut cx| async move {
 644                                            let mut timer = cx
 645                                                .background()
 646                                                .timer(Duration::from_millis(milliseconds))
 647                                                .fuse();
 648                                            prev_autosave.await;
 649                                            futures::select_biased! {
 650                                                _ = cancel_rx => return None,
 651                                                    _ = timer => {}
 652                                            }
 653
 654                                            let project = project.upgrade(&cx)?;
 655                                            cx.update(|cx| Pane::autosave_item(&item, project, cx))
 656                                                .await
 657                                                .log_err();
 658                                            None
 659                                        }));
 660                                }
 661                            }
 662                            _ => {}
 663                        }
 664                    }
 665                }));
 666
 667            cx.observe_focus(self, move |workspace, item, focused, cx| {
 668                if !focused && cx.global::<Settings>().autosave == Autosave::OnFocusChange {
 669                    Pane::autosave_item(&item, workspace.project.clone(), cx)
 670                        .detach_and_log_err(cx);
 671                }
 672            })
 673            .detach();
 674
 675            let item_id = self.id();
 676            cx.observe_release(self, move |workspace, _, _| {
 677                workspace.panes_by_item.remove(&item_id);
 678                event_subscription.take();
 679            })
 680            .detach();
 681        }
 682    }
 683
 684    fn deactivated(&self, cx: &mut MutableAppContext) {
 685        self.update(cx, |this, cx| this.deactivated(cx));
 686    }
 687
 688    fn workspace_deactivated(&self, cx: &mut MutableAppContext) {
 689        self.update(cx, |this, cx| this.workspace_deactivated(cx));
 690    }
 691
 692    fn navigate(&self, data: Box<dyn Any>, cx: &mut MutableAppContext) -> bool {
 693        self.update(cx, |this, cx| this.navigate(data, cx))
 694    }
 695
 696    fn id(&self) -> usize {
 697        self.id()
 698    }
 699
 700    fn window_id(&self) -> usize {
 701        self.window_id()
 702    }
 703
 704    fn to_any(&self) -> AnyViewHandle {
 705        self.into()
 706    }
 707
 708    fn is_dirty(&self, cx: &AppContext) -> bool {
 709        self.read(cx).is_dirty(cx)
 710    }
 711
 712    fn has_conflict(&self, cx: &AppContext) -> bool {
 713        self.read(cx).has_conflict(cx)
 714    }
 715
 716    fn can_save(&self, cx: &AppContext) -> bool {
 717        self.read(cx).can_save(cx)
 718    }
 719
 720    fn save(&self, project: ModelHandle<Project>, cx: &mut MutableAppContext) -> Task<Result<()>> {
 721        self.update(cx, |item, cx| item.save(project, cx))
 722    }
 723
 724    fn save_as(
 725        &self,
 726        project: ModelHandle<Project>,
 727        abs_path: PathBuf,
 728        cx: &mut MutableAppContext,
 729    ) -> Task<anyhow::Result<()>> {
 730        self.update(cx, |item, cx| item.save_as(project, abs_path, cx))
 731    }
 732
 733    fn reload(
 734        &self,
 735        project: ModelHandle<Project>,
 736        cx: &mut MutableAppContext,
 737    ) -> Task<Result<()>> {
 738        self.update(cx, |item, cx| item.reload(project, cx))
 739    }
 740
 741    fn act_as_type(&self, type_id: TypeId, cx: &AppContext) -> Option<AnyViewHandle> {
 742        self.read(cx).act_as_type(type_id, self, cx)
 743    }
 744
 745    fn to_followable_item_handle(&self, cx: &AppContext) -> Option<Box<dyn FollowableItemHandle>> {
 746        if cx.has_global::<FollowableItemBuilders>() {
 747            let builders = cx.global::<FollowableItemBuilders>();
 748            let item = self.to_any();
 749            Some(builders.get(&item.view_type())?.1(item))
 750        } else {
 751            None
 752        }
 753    }
 754
 755    fn on_release(
 756        &self,
 757        cx: &mut MutableAppContext,
 758        callback: Box<dyn FnOnce(&mut MutableAppContext)>,
 759    ) -> gpui::Subscription {
 760        cx.observe_release(self, move |_, cx| callback(cx))
 761    }
 762
 763    fn to_searchable_item_handle(&self, cx: &AppContext) -> Option<Box<dyn SearchableItemHandle>> {
 764        self.read(cx).as_searchable(self)
 765    }
 766
 767    fn breadcrumb_location(&self, cx: &AppContext) -> ToolbarItemLocation {
 768        self.read(cx).breadcrumb_location()
 769    }
 770
 771    fn breadcrumbs(&self, theme: &Theme, cx: &AppContext) -> Option<Vec<ElementBox>> {
 772        self.read(cx).breadcrumbs(theme, cx)
 773    }
 774}
 775
 776impl From<Box<dyn ItemHandle>> for AnyViewHandle {
 777    fn from(val: Box<dyn ItemHandle>) -> Self {
 778        val.to_any()
 779    }
 780}
 781
 782impl From<&Box<dyn ItemHandle>> for AnyViewHandle {
 783    fn from(val: &Box<dyn ItemHandle>) -> Self {
 784        val.to_any()
 785    }
 786}
 787
 788impl Clone for Box<dyn ItemHandle> {
 789    fn clone(&self) -> Box<dyn ItemHandle> {
 790        self.boxed_clone()
 791    }
 792}
 793
 794impl<T: Item> WeakItemHandle for WeakViewHandle<T> {
 795    fn id(&self) -> usize {
 796        self.id()
 797    }
 798
 799    fn window_id(&self) -> usize {
 800        self.window_id()
 801    }
 802
 803    fn upgrade(&self, cx: &AppContext) -> Option<Box<dyn ItemHandle>> {
 804        self.upgrade(cx).map(|v| Box::new(v) as Box<dyn ItemHandle>)
 805    }
 806}
 807
 808pub trait Notification: View {
 809    fn should_dismiss_notification_on_event(&self, event: &<Self as Entity>::Event) -> bool;
 810}
 811
 812pub trait NotificationHandle {
 813    fn id(&self) -> usize;
 814    fn to_any(&self) -> AnyViewHandle;
 815}
 816
 817impl<T: Notification> NotificationHandle for ViewHandle<T> {
 818    fn id(&self) -> usize {
 819        self.id()
 820    }
 821
 822    fn to_any(&self) -> AnyViewHandle {
 823        self.into()
 824    }
 825}
 826
 827impl From<&dyn NotificationHandle> for AnyViewHandle {
 828    fn from(val: &dyn NotificationHandle) -> Self {
 829        val.to_any()
 830    }
 831}
 832
 833impl AppState {
 834    #[cfg(any(test, feature = "test-support"))]
 835    pub fn test(cx: &mut MutableAppContext) -> Arc<Self> {
 836        let settings = Settings::test(cx);
 837        cx.set_global(settings);
 838
 839        let fs = project::FakeFs::new(cx.background().clone());
 840        let languages = Arc::new(LanguageRegistry::test());
 841        let http_client = client::test::FakeHttpClient::with_404_response();
 842        let client = Client::new(http_client.clone());
 843        let project_store = cx.add_model(|_| ProjectStore::new(project::Db::open_fake()));
 844        let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx));
 845        let themes = ThemeRegistry::new((), cx.font_cache().clone());
 846        Arc::new(Self {
 847            client,
 848            themes,
 849            fs,
 850            languages,
 851            user_store,
 852            project_store,
 853            initialize_workspace: |_, _, _| {},
 854            build_window_options: Default::default,
 855            default_item_factory: |_, _| unimplemented!(),
 856        })
 857    }
 858}
 859
 860pub enum Event {
 861    DockAnchorChanged,
 862    PaneAdded(ViewHandle<Pane>),
 863    ContactRequestedJoin(u64),
 864}
 865
 866pub struct Workspace {
 867    weak_self: WeakViewHandle<Self>,
 868    client: Arc<Client>,
 869    user_store: ModelHandle<client::UserStore>,
 870    remote_entity_subscription: Option<Subscription>,
 871    fs: Arc<dyn Fs>,
 872    modal: Option<AnyViewHandle>,
 873    center: PaneGroup,
 874    left_sidebar: ViewHandle<Sidebar>,
 875    right_sidebar: ViewHandle<Sidebar>,
 876    panes: Vec<ViewHandle<Pane>>,
 877    panes_by_item: HashMap<usize, WeakViewHandle<Pane>>,
 878    active_pane: ViewHandle<Pane>,
 879    last_active_center_pane: Option<ViewHandle<Pane>>,
 880    status_bar: ViewHandle<StatusBar>,
 881    titlebar_item: Option<AnyViewHandle>,
 882    dock: Dock,
 883    notifications: Vec<(TypeId, usize, Box<dyn NotificationHandle>)>,
 884    project: ModelHandle<Project>,
 885    leader_state: LeaderState,
 886    follower_states_by_leader: FollowerStatesByLeader,
 887    last_leaders_by_pane: HashMap<WeakViewHandle<Pane>, PeerId>,
 888    window_edited: bool,
 889    _observe_current_user: Task<()>,
 890}
 891
 892#[derive(Default)]
 893struct LeaderState {
 894    followers: HashSet<PeerId>,
 895}
 896
 897type FollowerStatesByLeader = HashMap<PeerId, HashMap<ViewHandle<Pane>, FollowerState>>;
 898
 899#[derive(Default)]
 900struct FollowerState {
 901    active_view_id: Option<u64>,
 902    items_by_leader_view_id: HashMap<u64, FollowerItem>,
 903}
 904
 905#[derive(Debug)]
 906enum FollowerItem {
 907    Loading(Vec<proto::update_view::Variant>),
 908    Loaded(Box<dyn FollowableItemHandle>),
 909}
 910
 911impl Workspace {
 912    pub fn new(
 913        project: ModelHandle<Project>,
 914        dock_default_factory: DefaultItemFactory,
 915        cx: &mut ViewContext<Self>,
 916    ) -> Self {
 917        cx.observe_fullscreen(|_, _, cx| cx.notify()).detach();
 918
 919        cx.observe_window_activation(Self::on_window_activation_changed)
 920            .detach();
 921        cx.observe(&project, |_, _, cx| cx.notify()).detach();
 922        cx.subscribe(&project, move |this, _, event, cx| {
 923            match event {
 924                project::Event::RemoteIdChanged(remote_id) => {
 925                    this.project_remote_id_changed(*remote_id, cx);
 926                }
 927                project::Event::CollaboratorLeft(peer_id) => {
 928                    this.collaborator_left(*peer_id, cx);
 929                }
 930                project::Event::WorktreeRemoved(_) | project::Event::WorktreeAdded => {
 931                    this.update_window_title(cx);
 932                }
 933                project::Event::DisconnectedFromHost => {
 934                    this.update_window_edited(cx);
 935                    cx.blur();
 936                }
 937                _ => {}
 938            }
 939            cx.notify()
 940        })
 941        .detach();
 942
 943        let center_pane = cx.add_view(|cx| Pane::new(None, cx));
 944        let pane_id = center_pane.id();
 945        cx.subscribe(&center_pane, move |this, _, event, cx| {
 946            this.handle_pane_event(pane_id, event, cx)
 947        })
 948        .detach();
 949        cx.focus(&center_pane);
 950        cx.emit(Event::PaneAdded(center_pane.clone()));
 951
 952        let fs = project.read(cx).fs().clone();
 953        let user_store = project.read(cx).user_store();
 954        let client = project.read(cx).client();
 955        let mut current_user = user_store.read(cx).watch_current_user();
 956        let mut connection_status = client.status();
 957        let _observe_current_user = cx.spawn_weak(|this, mut cx| async move {
 958            current_user.recv().await;
 959            connection_status.recv().await;
 960            let mut stream =
 961                Stream::map(current_user, drop).merge(Stream::map(connection_status, drop));
 962
 963            while stream.recv().await.is_some() {
 964                cx.update(|cx| {
 965                    if let Some(this) = this.upgrade(cx) {
 966                        this.update(cx, |_, cx| cx.notify());
 967                    }
 968                })
 969            }
 970        });
 971
 972        let handle = cx.handle();
 973        let weak_handle = cx.weak_handle();
 974
 975        cx.emit_global(WorkspaceCreated(weak_handle.clone()));
 976
 977        let dock = Dock::new(cx, dock_default_factory);
 978        let dock_pane = dock.pane().clone();
 979
 980        let left_sidebar = cx.add_view(|_| Sidebar::new(SidebarSide::Left));
 981        let right_sidebar = cx.add_view(|_| Sidebar::new(SidebarSide::Right));
 982        let left_sidebar_buttons = cx.add_view(|cx| SidebarButtons::new(left_sidebar.clone(), cx));
 983        let toggle_dock = cx.add_view(|cx| ToggleDockButton::new(handle, cx));
 984        let right_sidebar_buttons =
 985            cx.add_view(|cx| SidebarButtons::new(right_sidebar.clone(), cx));
 986        let status_bar = cx.add_view(|cx| {
 987            let mut status_bar = StatusBar::new(&center_pane.clone(), cx);
 988            status_bar.add_left_item(left_sidebar_buttons, cx);
 989            status_bar.add_right_item(right_sidebar_buttons, cx);
 990            status_bar.add_right_item(toggle_dock, cx);
 991            status_bar
 992        });
 993
 994        cx.update_default_global::<DragAndDrop<Workspace>, _, _>(|drag_and_drop, _| {
 995            drag_and_drop.register_container(weak_handle.clone());
 996        });
 997
 998        let mut this = Workspace {
 999            modal: None,
1000            weak_self: weak_handle,
1001            center: PaneGroup::new(center_pane.clone()),
1002            dock,
1003            // When removing an item, the last element remaining in this array
1004            // is used to find where focus should fallback to. As such, the order
1005            // of these two variables is important.
1006            panes: vec![dock_pane, center_pane.clone()],
1007            panes_by_item: Default::default(),
1008            active_pane: center_pane.clone(),
1009            last_active_center_pane: Some(center_pane.clone()),
1010            status_bar,
1011            titlebar_item: None,
1012            notifications: Default::default(),
1013            client,
1014            remote_entity_subscription: None,
1015            user_store,
1016            fs,
1017            left_sidebar,
1018            right_sidebar,
1019            project,
1020            leader_state: Default::default(),
1021            follower_states_by_leader: Default::default(),
1022            last_leaders_by_pane: Default::default(),
1023            window_edited: false,
1024            _observe_current_user,
1025        };
1026        this.project_remote_id_changed(this.project.read(cx).remote_id(), cx);
1027        cx.defer(|this, cx| this.update_window_title(cx));
1028
1029        this
1030    }
1031
1032    pub fn weak_handle(&self) -> WeakViewHandle<Self> {
1033        self.weak_self.clone()
1034    }
1035
1036    pub fn left_sidebar(&self) -> &ViewHandle<Sidebar> {
1037        &self.left_sidebar
1038    }
1039
1040    pub fn right_sidebar(&self) -> &ViewHandle<Sidebar> {
1041        &self.right_sidebar
1042    }
1043
1044    pub fn status_bar(&self) -> &ViewHandle<StatusBar> {
1045        &self.status_bar
1046    }
1047
1048    pub fn user_store(&self) -> &ModelHandle<UserStore> {
1049        &self.user_store
1050    }
1051
1052    pub fn project(&self) -> &ModelHandle<Project> {
1053        &self.project
1054    }
1055
1056    pub fn client(&self) -> &Arc<Client> {
1057        &self.client
1058    }
1059
1060    pub fn set_titlebar_item(
1061        &mut self,
1062        item: impl Into<AnyViewHandle>,
1063        cx: &mut ViewContext<Self>,
1064    ) {
1065        self.titlebar_item = Some(item.into());
1066        cx.notify();
1067    }
1068
1069    /// Call the given callback with a workspace whose project is local.
1070    ///
1071    /// If the given workspace has a local project, then it will be passed
1072    /// to the callback. Otherwise, a new empty window will be created.
1073    pub fn with_local_workspace<T, F>(
1074        &mut self,
1075        cx: &mut ViewContext<Self>,
1076        app_state: Arc<AppState>,
1077        callback: F,
1078    ) -> T
1079    where
1080        T: 'static,
1081        F: FnOnce(&mut Workspace, &mut ViewContext<Workspace>) -> T,
1082    {
1083        if self.project.read(cx).is_local() {
1084            callback(self, cx)
1085        } else {
1086            let (_, workspace) = cx.add_window((app_state.build_window_options)(), |cx| {
1087                let mut workspace = Workspace::new(
1088                    Project::local(
1089                        false,
1090                        app_state.client.clone(),
1091                        app_state.user_store.clone(),
1092                        app_state.project_store.clone(),
1093                        app_state.languages.clone(),
1094                        app_state.fs.clone(),
1095                        cx,
1096                    ),
1097                    app_state.default_item_factory,
1098                    cx,
1099                );
1100                (app_state.initialize_workspace)(&mut workspace, &app_state, cx);
1101                workspace
1102            });
1103            workspace.update(cx, callback)
1104        }
1105    }
1106
1107    pub fn worktrees<'a>(
1108        &self,
1109        cx: &'a AppContext,
1110    ) -> impl 'a + Iterator<Item = ModelHandle<Worktree>> {
1111        self.project.read(cx).worktrees(cx)
1112    }
1113
1114    pub fn visible_worktrees<'a>(
1115        &self,
1116        cx: &'a AppContext,
1117    ) -> impl 'a + Iterator<Item = ModelHandle<Worktree>> {
1118        self.project.read(cx).visible_worktrees(cx)
1119    }
1120
1121    pub fn worktree_scans_complete(&self, cx: &AppContext) -> impl Future<Output = ()> + 'static {
1122        let futures = self
1123            .worktrees(cx)
1124            .filter_map(|worktree| worktree.read(cx).as_local())
1125            .map(|worktree| worktree.scan_complete())
1126            .collect::<Vec<_>>();
1127        async move {
1128            for future in futures {
1129                future.await;
1130            }
1131        }
1132    }
1133
1134    pub fn close(
1135        &mut self,
1136        _: &CloseWindow,
1137        cx: &mut ViewContext<Self>,
1138    ) -> Option<Task<Result<()>>> {
1139        let prepare = self.prepare_to_close(cx);
1140        Some(cx.spawn(|this, mut cx| async move {
1141            if prepare.await? {
1142                this.update(&mut cx, |_, cx| {
1143                    let window_id = cx.window_id();
1144                    cx.remove_window(window_id);
1145                });
1146            }
1147            Ok(())
1148        }))
1149    }
1150
1151    pub fn prepare_to_close(&mut self, cx: &mut ViewContext<Self>) -> Task<Result<bool>> {
1152        self.save_all_internal(true, cx)
1153    }
1154
1155    fn save_all(&mut self, _: &SaveAll, cx: &mut ViewContext<Self>) -> Option<Task<Result<()>>> {
1156        let save_all = self.save_all_internal(false, cx);
1157        Some(cx.foreground().spawn(async move {
1158            save_all.await?;
1159            Ok(())
1160        }))
1161    }
1162
1163    fn save_all_internal(
1164        &mut self,
1165        should_prompt_to_save: bool,
1166        cx: &mut ViewContext<Self>,
1167    ) -> Task<Result<bool>> {
1168        if self.project.read(cx).is_read_only() {
1169            return Task::ready(Ok(true));
1170        }
1171
1172        let dirty_items = self
1173            .panes
1174            .iter()
1175            .flat_map(|pane| {
1176                pane.read(cx).items().filter_map(|item| {
1177                    if item.is_dirty(cx) {
1178                        Some((pane.clone(), item.boxed_clone()))
1179                    } else {
1180                        None
1181                    }
1182                })
1183            })
1184            .collect::<Vec<_>>();
1185
1186        let project = self.project.clone();
1187        cx.spawn_weak(|_, mut cx| async move {
1188            for (pane, item) in dirty_items {
1189                let (singleton, project_entry_ids) =
1190                    cx.read(|cx| (item.is_singleton(cx), item.project_entry_ids(cx)));
1191                if singleton || !project_entry_ids.is_empty() {
1192                    if let Some(ix) =
1193                        pane.read_with(&cx, |pane, _| pane.index_for_item(item.as_ref()))
1194                    {
1195                        if !Pane::save_item(
1196                            project.clone(),
1197                            &pane,
1198                            ix,
1199                            &*item,
1200                            should_prompt_to_save,
1201                            &mut cx,
1202                        )
1203                        .await?
1204                        {
1205                            return Ok(false);
1206                        }
1207                    }
1208                }
1209            }
1210            Ok(true)
1211        })
1212    }
1213
1214    #[allow(clippy::type_complexity)]
1215    pub fn open_paths(
1216        &mut self,
1217        mut abs_paths: Vec<PathBuf>,
1218        visible: bool,
1219        cx: &mut ViewContext<Self>,
1220    ) -> Task<Vec<Option<Result<Box<dyn ItemHandle>, Arc<anyhow::Error>>>>> {
1221        let fs = self.fs.clone();
1222
1223        // Sort the paths to ensure we add worktrees for parents before their children.
1224        abs_paths.sort_unstable();
1225        cx.spawn(|this, mut cx| async move {
1226            let mut project_paths = Vec::new();
1227            for path in &abs_paths {
1228                project_paths.push(
1229                    this.update(&mut cx, |this, cx| {
1230                        this.project_path_for_path(path, visible, cx)
1231                    })
1232                    .await
1233                    .log_err(),
1234                );
1235            }
1236
1237            let tasks = abs_paths
1238                .iter()
1239                .cloned()
1240                .zip(project_paths.into_iter())
1241                .map(|(abs_path, project_path)| {
1242                    let this = this.clone();
1243                    cx.spawn(|mut cx| {
1244                        let fs = fs.clone();
1245                        async move {
1246                            let (_worktree, project_path) = project_path?;
1247                            if fs.is_file(&abs_path).await {
1248                                Some(
1249                                    this.update(&mut cx, |this, cx| {
1250                                        this.open_path(project_path, true, cx)
1251                                    })
1252                                    .await,
1253                                )
1254                            } else {
1255                                None
1256                            }
1257                        }
1258                    })
1259                })
1260                .collect::<Vec<_>>();
1261
1262            futures::future::join_all(tasks).await
1263        })
1264    }
1265
1266    fn add_folder_to_project(&mut self, _: &AddFolderToProject, cx: &mut ViewContext<Self>) {
1267        let mut paths = cx.prompt_for_paths(PathPromptOptions {
1268            files: false,
1269            directories: true,
1270            multiple: true,
1271        });
1272        cx.spawn(|this, mut cx| async move {
1273            if let Some(paths) = paths.recv().await.flatten() {
1274                let results = this
1275                    .update(&mut cx, |this, cx| this.open_paths(paths, true, cx))
1276                    .await;
1277                for result in results.into_iter().flatten() {
1278                    result.log_err();
1279                }
1280            }
1281        })
1282        .detach();
1283    }
1284
1285    fn remove_folder_from_project(
1286        &mut self,
1287        RemoveWorktreeFromProject(worktree_id): &RemoveWorktreeFromProject,
1288        cx: &mut ViewContext<Self>,
1289    ) {
1290        self.project
1291            .update(cx, |project, cx| project.remove_worktree(*worktree_id, cx));
1292    }
1293
1294    fn toggle_project_online(&mut self, action: &ToggleProjectOnline, cx: &mut ViewContext<Self>) {
1295        let project = action
1296            .project
1297            .clone()
1298            .unwrap_or_else(|| self.project.clone());
1299        project.update(cx, |project, cx| {
1300            let public = !project.is_online();
1301            project.set_online(public, cx);
1302        });
1303    }
1304
1305    fn project_path_for_path(
1306        &self,
1307        abs_path: &Path,
1308        visible: bool,
1309        cx: &mut ViewContext<Self>,
1310    ) -> Task<Result<(ModelHandle<Worktree>, ProjectPath)>> {
1311        let entry = self.project().update(cx, |project, cx| {
1312            project.find_or_create_local_worktree(abs_path, visible, cx)
1313        });
1314        cx.spawn(|_, cx| async move {
1315            let (worktree, path) = entry.await?;
1316            let worktree_id = worktree.read_with(&cx, |t, _| t.id());
1317            Ok((
1318                worktree,
1319                ProjectPath {
1320                    worktree_id,
1321                    path: path.into(),
1322                },
1323            ))
1324        })
1325    }
1326
1327    /// Returns the modal that was toggled closed if it was open.
1328    pub fn toggle_modal<V, F>(
1329        &mut self,
1330        cx: &mut ViewContext<Self>,
1331        add_view: F,
1332    ) -> Option<ViewHandle<V>>
1333    where
1334        V: 'static + View,
1335        F: FnOnce(&mut Self, &mut ViewContext<Self>) -> ViewHandle<V>,
1336    {
1337        cx.notify();
1338        // Whatever modal was visible is getting clobbered. If its the same type as V, then return
1339        // it. Otherwise, create a new modal and set it as active.
1340        let already_open_modal = self.modal.take().and_then(|modal| modal.downcast::<V>());
1341        if let Some(already_open_modal) = already_open_modal {
1342            cx.focus_self();
1343            Some(already_open_modal)
1344        } else {
1345            let modal = add_view(self, cx);
1346            cx.focus(&modal);
1347            self.modal = Some(modal.into());
1348            None
1349        }
1350    }
1351
1352    pub fn modal<V: 'static + View>(&self) -> Option<ViewHandle<V>> {
1353        self.modal
1354            .as_ref()
1355            .and_then(|modal| modal.clone().downcast::<V>())
1356    }
1357
1358    pub fn dismiss_modal(&mut self, cx: &mut ViewContext<Self>) {
1359        if self.modal.take().is_some() {
1360            cx.focus(&self.active_pane);
1361            cx.notify();
1362        }
1363    }
1364
1365    pub fn show_notification<V: Notification>(
1366        &mut self,
1367        id: usize,
1368        cx: &mut ViewContext<Self>,
1369        build_notification: impl FnOnce(&mut ViewContext<Self>) -> ViewHandle<V>,
1370    ) {
1371        let type_id = TypeId::of::<V>();
1372        if self
1373            .notifications
1374            .iter()
1375            .all(|(existing_type_id, existing_id, _)| {
1376                (*existing_type_id, *existing_id) != (type_id, id)
1377            })
1378        {
1379            let notification = build_notification(cx);
1380            cx.subscribe(&notification, move |this, handle, event, cx| {
1381                if handle.read(cx).should_dismiss_notification_on_event(event) {
1382                    this.dismiss_notification(type_id, id, cx);
1383                }
1384            })
1385            .detach();
1386            self.notifications
1387                .push((type_id, id, Box::new(notification)));
1388            cx.notify();
1389        }
1390    }
1391
1392    fn dismiss_notification(&mut self, type_id: TypeId, id: usize, cx: &mut ViewContext<Self>) {
1393        self.notifications
1394            .retain(|(existing_type_id, existing_id, _)| {
1395                if (*existing_type_id, *existing_id) == (type_id, id) {
1396                    cx.notify();
1397                    false
1398                } else {
1399                    true
1400                }
1401            });
1402    }
1403
1404    pub fn items<'a>(
1405        &'a self,
1406        cx: &'a AppContext,
1407    ) -> impl 'a + Iterator<Item = &Box<dyn ItemHandle>> {
1408        self.panes.iter().flat_map(|pane| pane.read(cx).items())
1409    }
1410
1411    pub fn item_of_type<T: Item>(&self, cx: &AppContext) -> Option<ViewHandle<T>> {
1412        self.items_of_type(cx).max_by_key(|item| item.id())
1413    }
1414
1415    pub fn items_of_type<'a, T: Item>(
1416        &'a self,
1417        cx: &'a AppContext,
1418    ) -> impl 'a + Iterator<Item = ViewHandle<T>> {
1419        self.panes
1420            .iter()
1421            .flat_map(|pane| pane.read(cx).items_of_type())
1422    }
1423
1424    pub fn active_item(&self, cx: &AppContext) -> Option<Box<dyn ItemHandle>> {
1425        self.active_pane().read(cx).active_item()
1426    }
1427
1428    fn active_project_path(&self, cx: &ViewContext<Self>) -> Option<ProjectPath> {
1429        self.active_item(cx).and_then(|item| item.project_path(cx))
1430    }
1431
1432    pub fn save_active_item(
1433        &mut self,
1434        force_name_change: bool,
1435        cx: &mut ViewContext<Self>,
1436    ) -> Task<Result<()>> {
1437        let project = self.project.clone();
1438        if let Some(item) = self.active_item(cx) {
1439            if !force_name_change && item.can_save(cx) {
1440                if item.has_conflict(cx.as_ref()) {
1441                    const CONFLICT_MESSAGE: &str = "This file has changed on disk since you started editing it. Do you want to overwrite it?";
1442
1443                    let mut answer = cx.prompt(
1444                        PromptLevel::Warning,
1445                        CONFLICT_MESSAGE,
1446                        &["Overwrite", "Cancel"],
1447                    );
1448                    cx.spawn(|_, mut cx| async move {
1449                        let answer = answer.recv().await;
1450                        if answer == Some(0) {
1451                            cx.update(|cx| item.save(project, cx)).await?;
1452                        }
1453                        Ok(())
1454                    })
1455                } else {
1456                    item.save(project, cx)
1457                }
1458            } else if item.is_singleton(cx) {
1459                let worktree = self.worktrees(cx).next();
1460                let start_abs_path = worktree
1461                    .and_then(|w| w.read(cx).as_local())
1462                    .map_or(Path::new(""), |w| w.abs_path())
1463                    .to_path_buf();
1464                let mut abs_path = cx.prompt_for_new_path(&start_abs_path);
1465                cx.spawn(|_, mut cx| async move {
1466                    if let Some(abs_path) = abs_path.recv().await.flatten() {
1467                        cx.update(|cx| item.save_as(project, abs_path, cx)).await?;
1468                    }
1469                    Ok(())
1470                })
1471            } else {
1472                Task::ready(Ok(()))
1473            }
1474        } else {
1475            Task::ready(Ok(()))
1476        }
1477    }
1478
1479    pub fn toggle_sidebar(&mut self, sidebar_side: SidebarSide, cx: &mut ViewContext<Self>) {
1480        let sidebar = match sidebar_side {
1481            SidebarSide::Left => &mut self.left_sidebar,
1482            SidebarSide::Right => &mut self.right_sidebar,
1483        };
1484        let open = sidebar.update(cx, |sidebar, cx| {
1485            let open = !sidebar.is_open();
1486            sidebar.set_open(open, cx);
1487            open
1488        });
1489
1490        if open {
1491            Dock::hide_on_sidebar_shown(self, sidebar_side, cx);
1492        }
1493
1494        cx.focus_self();
1495        cx.notify();
1496    }
1497
1498    pub fn toggle_sidebar_item(&mut self, action: &ToggleSidebarItem, cx: &mut ViewContext<Self>) {
1499        let sidebar = match action.sidebar_side {
1500            SidebarSide::Left => &mut self.left_sidebar,
1501            SidebarSide::Right => &mut self.right_sidebar,
1502        };
1503        let active_item = sidebar.update(cx, move |sidebar, cx| {
1504            if sidebar.is_open() && sidebar.active_item_ix() == action.item_index {
1505                sidebar.set_open(false, cx);
1506                None
1507            } else {
1508                sidebar.set_open(true, cx);
1509                sidebar.activate_item(action.item_index, cx);
1510                sidebar.active_item().cloned()
1511            }
1512        });
1513
1514        if let Some(active_item) = active_item {
1515            Dock::hide_on_sidebar_shown(self, action.sidebar_side, cx);
1516
1517            if active_item.is_focused(cx) {
1518                cx.focus_self();
1519            } else {
1520                cx.focus(active_item.to_any());
1521            }
1522        } else {
1523            cx.focus_self();
1524        }
1525        cx.notify();
1526    }
1527
1528    pub fn toggle_sidebar_item_focus(
1529        &mut self,
1530        sidebar_side: SidebarSide,
1531        item_index: usize,
1532        cx: &mut ViewContext<Self>,
1533    ) {
1534        let sidebar = match sidebar_side {
1535            SidebarSide::Left => &mut self.left_sidebar,
1536            SidebarSide::Right => &mut self.right_sidebar,
1537        };
1538        let active_item = sidebar.update(cx, |sidebar, cx| {
1539            sidebar.set_open(true, cx);
1540            sidebar.activate_item(item_index, cx);
1541            sidebar.active_item().cloned()
1542        });
1543        if let Some(active_item) = active_item {
1544            Dock::hide_on_sidebar_shown(self, sidebar_side, cx);
1545
1546            if active_item.is_focused(cx) {
1547                cx.focus_self();
1548            } else {
1549                cx.focus(active_item.to_any());
1550            }
1551        }
1552        cx.notify();
1553    }
1554
1555    pub fn focus_center(&mut self, _: &menu::Cancel, cx: &mut ViewContext<Self>) {
1556        cx.focus_self();
1557        cx.notify();
1558    }
1559
1560    fn add_pane(&mut self, cx: &mut ViewContext<Self>) -> ViewHandle<Pane> {
1561        let pane = cx.add_view(|cx| Pane::new(None, cx));
1562        let pane_id = pane.id();
1563        cx.subscribe(&pane, move |this, _, event, cx| {
1564            this.handle_pane_event(pane_id, event, cx)
1565        })
1566        .detach();
1567        self.panes.push(pane.clone());
1568        cx.focus(pane.clone());
1569        cx.emit(Event::PaneAdded(pane.clone()));
1570        pane
1571    }
1572
1573    pub fn add_item(&mut self, item: Box<dyn ItemHandle>, cx: &mut ViewContext<Self>) {
1574        let active_pane = self.active_pane().clone();
1575        Pane::add_item(self, &active_pane, item, true, true, None, cx);
1576    }
1577
1578    pub fn open_path(
1579        &mut self,
1580        path: impl Into<ProjectPath>,
1581        focus_item: bool,
1582        cx: &mut ViewContext<Self>,
1583    ) -> Task<Result<Box<dyn ItemHandle>, Arc<anyhow::Error>>> {
1584        let pane = self.active_pane().downgrade();
1585        let task = self.load_path(path.into(), cx);
1586        cx.spawn(|this, mut cx| async move {
1587            let (project_entry_id, build_item) = task.await?;
1588            let pane = pane
1589                .upgrade(&cx)
1590                .ok_or_else(|| anyhow!("pane was closed"))?;
1591            this.update(&mut cx, |this, cx| {
1592                Ok(Pane::open_item(
1593                    this,
1594                    pane,
1595                    project_entry_id,
1596                    focus_item,
1597                    cx,
1598                    build_item,
1599                ))
1600            })
1601        })
1602    }
1603
1604    pub(crate) fn load_path(
1605        &mut self,
1606        path: ProjectPath,
1607        cx: &mut ViewContext<Self>,
1608    ) -> Task<
1609        Result<(
1610            ProjectEntryId,
1611            impl 'static + FnOnce(&mut ViewContext<Pane>) -> Box<dyn ItemHandle>,
1612        )>,
1613    > {
1614        let project = self.project().clone();
1615        let project_item = project.update(cx, |project, cx| project.open_path(path, cx));
1616        cx.as_mut().spawn(|mut cx| async move {
1617            let (project_entry_id, project_item) = project_item.await?;
1618            let build_item = cx.update(|cx| {
1619                cx.default_global::<ProjectItemBuilders>()
1620                    .get(&project_item.model_type())
1621                    .ok_or_else(|| anyhow!("no item builder for project item"))
1622                    .cloned()
1623            })?;
1624            let build_item =
1625                move |cx: &mut ViewContext<Pane>| build_item(project, project_item, cx);
1626            Ok((project_entry_id, build_item))
1627        })
1628    }
1629
1630    pub fn open_project_item<T>(
1631        &mut self,
1632        project_item: ModelHandle<T::Item>,
1633        cx: &mut ViewContext<Self>,
1634    ) -> ViewHandle<T>
1635    where
1636        T: ProjectItem,
1637    {
1638        use project::Item as _;
1639
1640        let entry_id = project_item.read(cx).entry_id(cx);
1641        if let Some(item) = entry_id
1642            .and_then(|entry_id| self.active_pane().read(cx).item_for_entry(entry_id, cx))
1643            .and_then(|item| item.downcast())
1644        {
1645            self.activate_item(&item, cx);
1646            return item;
1647        }
1648
1649        let item = cx.add_view(|cx| T::for_project_item(self.project().clone(), project_item, cx));
1650        self.add_item(Box::new(item.clone()), cx);
1651        item
1652    }
1653
1654    pub fn activate_item(&mut self, item: &dyn ItemHandle, cx: &mut ViewContext<Self>) -> bool {
1655        let result = self.panes.iter().find_map(|pane| {
1656            pane.read(cx)
1657                .index_for_item(item)
1658                .map(|ix| (pane.clone(), ix))
1659        });
1660        if let Some((pane, ix)) = result {
1661            pane.update(cx, |pane, cx| pane.activate_item(ix, true, true, cx));
1662            true
1663        } else {
1664            false
1665        }
1666    }
1667
1668    fn activate_pane_at_index(&mut self, action: &ActivatePane, cx: &mut ViewContext<Self>) {
1669        let panes = self.center.panes();
1670        if let Some(pane) = panes.get(action.0).map(|p| (*p).clone()) {
1671            cx.focus(pane);
1672        } else {
1673            self.split_pane(self.active_pane.clone(), SplitDirection::Right, cx);
1674        }
1675    }
1676
1677    pub fn activate_next_pane(&mut self, cx: &mut ViewContext<Self>) {
1678        let next_pane = {
1679            let panes = self.center.panes();
1680            let ix = panes
1681                .iter()
1682                .position(|pane| **pane == self.active_pane)
1683                .unwrap();
1684            let next_ix = (ix + 1) % panes.len();
1685            panes[next_ix].clone()
1686        };
1687        cx.focus(next_pane);
1688    }
1689
1690    pub fn activate_previous_pane(&mut self, cx: &mut ViewContext<Self>) {
1691        let prev_pane = {
1692            let panes = self.center.panes();
1693            let ix = panes
1694                .iter()
1695                .position(|pane| **pane == self.active_pane)
1696                .unwrap();
1697            let prev_ix = if ix == 0 { panes.len() - 1 } else { ix - 1 };
1698            panes[prev_ix].clone()
1699        };
1700        cx.focus(prev_pane);
1701    }
1702
1703    fn handle_pane_focused(&mut self, pane: ViewHandle<Pane>, cx: &mut ViewContext<Self>) {
1704        if self.active_pane != pane {
1705            self.active_pane
1706                .update(cx, |pane, cx| pane.set_active(false, cx));
1707            self.active_pane = pane.clone();
1708            self.active_pane
1709                .update(cx, |pane, cx| pane.set_active(true, cx));
1710            self.status_bar.update(cx, |status_bar, cx| {
1711                status_bar.set_active_pane(&self.active_pane, cx);
1712            });
1713            self.active_item_path_changed(cx);
1714
1715            if &pane == self.dock_pane() {
1716                Dock::show(self, cx);
1717            } else {
1718                self.last_active_center_pane = Some(pane.clone());
1719                if self.dock.is_anchored_at(DockAnchor::Expanded) {
1720                    Dock::hide(self, cx);
1721                }
1722            }
1723            cx.notify();
1724        }
1725
1726        self.update_followers(
1727            proto::update_followers::Variant::UpdateActiveView(proto::UpdateActiveView {
1728                id: self.active_item(cx).map(|item| item.id() as u64),
1729                leader_id: self.leader_for_pane(&pane).map(|id| id.0),
1730            }),
1731            cx,
1732        );
1733    }
1734
1735    fn handle_pane_event(
1736        &mut self,
1737        pane_id: usize,
1738        event: &pane::Event,
1739        cx: &mut ViewContext<Self>,
1740    ) {
1741        if let Some(pane) = self.pane(pane_id) {
1742            let is_dock = &pane == self.dock.pane();
1743            match event {
1744                pane::Event::Split(direction) if !is_dock => {
1745                    self.split_pane(pane, *direction, cx);
1746                }
1747                pane::Event::Remove if !is_dock => self.remove_pane(pane, cx),
1748                pane::Event::Remove if is_dock => Dock::hide(self, cx),
1749                pane::Event::Focused => self.handle_pane_focused(pane, cx),
1750                pane::Event::ActivateItem { local } => {
1751                    if *local {
1752                        self.unfollow(&pane, cx);
1753                    }
1754                    if &pane == self.active_pane() {
1755                        self.active_item_path_changed(cx);
1756                    }
1757                }
1758                pane::Event::ChangeItemTitle => {
1759                    if pane == self.active_pane {
1760                        self.active_item_path_changed(cx);
1761                    }
1762                    self.update_window_edited(cx);
1763                }
1764                pane::Event::RemoveItem { item_id } => {
1765                    self.update_window_edited(cx);
1766                    if let hash_map::Entry::Occupied(entry) = self.panes_by_item.entry(*item_id) {
1767                        if entry.get().id() == pane.id() {
1768                            entry.remove();
1769                        }
1770                    }
1771                }
1772                _ => {}
1773            }
1774        } else if self.dock.visible_pane().is_none() {
1775            error!("pane {} not found", pane_id);
1776        }
1777    }
1778
1779    pub fn split_pane(
1780        &mut self,
1781        pane: ViewHandle<Pane>,
1782        direction: SplitDirection,
1783        cx: &mut ViewContext<Self>,
1784    ) -> Option<ViewHandle<Pane>> {
1785        if &pane == self.dock_pane() {
1786            warn!("Can't split dock pane.");
1787            return None;
1788        }
1789
1790        pane.read(cx).active_item().map(|item| {
1791            let new_pane = self.add_pane(cx);
1792            if let Some(clone) = item.clone_on_split(cx.as_mut()) {
1793                Pane::add_item(self, &new_pane, clone, true, true, None, cx);
1794            }
1795            self.center.split(&pane, &new_pane, direction).unwrap();
1796            cx.notify();
1797            new_pane
1798        })
1799    }
1800
1801    fn remove_pane(&mut self, pane: ViewHandle<Pane>, cx: &mut ViewContext<Self>) {
1802        if self.center.remove(&pane).unwrap() {
1803            self.panes.retain(|p| p != &pane);
1804            cx.focus(self.panes.last().unwrap().clone());
1805            self.unfollow(&pane, cx);
1806            self.last_leaders_by_pane.remove(&pane.downgrade());
1807            for removed_item in pane.read(cx).items() {
1808                self.panes_by_item.remove(&removed_item.id());
1809            }
1810            if self.last_active_center_pane == Some(pane) {
1811                self.last_active_center_pane = None;
1812            }
1813
1814            cx.notify();
1815        } else {
1816            self.active_item_path_changed(cx);
1817        }
1818    }
1819
1820    pub fn panes(&self) -> &[ViewHandle<Pane>] {
1821        &self.panes
1822    }
1823
1824    fn pane(&self, pane_id: usize) -> Option<ViewHandle<Pane>> {
1825        self.panes.iter().find(|pane| pane.id() == pane_id).cloned()
1826    }
1827
1828    pub fn active_pane(&self) -> &ViewHandle<Pane> {
1829        &self.active_pane
1830    }
1831
1832    pub fn dock_pane(&self) -> &ViewHandle<Pane> {
1833        self.dock.pane()
1834    }
1835
1836    fn project_remote_id_changed(&mut self, remote_id: Option<u64>, cx: &mut ViewContext<Self>) {
1837        if let Some(remote_id) = remote_id {
1838            self.remote_entity_subscription =
1839                Some(self.client.add_view_for_remote_entity(remote_id, cx));
1840        } else {
1841            self.remote_entity_subscription.take();
1842        }
1843    }
1844
1845    fn collaborator_left(&mut self, peer_id: PeerId, cx: &mut ViewContext<Self>) {
1846        self.leader_state.followers.remove(&peer_id);
1847        if let Some(states_by_pane) = self.follower_states_by_leader.remove(&peer_id) {
1848            for state in states_by_pane.into_values() {
1849                for item in state.items_by_leader_view_id.into_values() {
1850                    if let FollowerItem::Loaded(item) = item {
1851                        item.set_leader_replica_id(None, cx);
1852                    }
1853                }
1854            }
1855        }
1856        cx.notify();
1857    }
1858
1859    pub fn toggle_follow(
1860        &mut self,
1861        ToggleFollow(leader_id): &ToggleFollow,
1862        cx: &mut ViewContext<Self>,
1863    ) -> Option<Task<Result<()>>> {
1864        let leader_id = *leader_id;
1865        let pane = self.active_pane().clone();
1866
1867        if let Some(prev_leader_id) = self.unfollow(&pane, cx) {
1868            if leader_id == prev_leader_id {
1869                return None;
1870            }
1871        }
1872
1873        self.last_leaders_by_pane
1874            .insert(pane.downgrade(), leader_id);
1875        self.follower_states_by_leader
1876            .entry(leader_id)
1877            .or_default()
1878            .insert(pane.clone(), Default::default());
1879        cx.notify();
1880
1881        let project_id = self.project.read(cx).remote_id()?;
1882        let request = self.client.request(proto::Follow {
1883            project_id,
1884            leader_id: leader_id.0,
1885        });
1886        Some(cx.spawn_weak(|this, mut cx| async move {
1887            let response = request.await?;
1888            if let Some(this) = this.upgrade(&cx) {
1889                this.update(&mut cx, |this, _| {
1890                    let state = this
1891                        .follower_states_by_leader
1892                        .get_mut(&leader_id)
1893                        .and_then(|states_by_pane| states_by_pane.get_mut(&pane))
1894                        .ok_or_else(|| anyhow!("following interrupted"))?;
1895                    state.active_view_id = response.active_view_id;
1896                    Ok::<_, anyhow::Error>(())
1897                })?;
1898                Self::add_views_from_leader(this, leader_id, vec![pane], response.views, &mut cx)
1899                    .await?;
1900            }
1901            Ok(())
1902        }))
1903    }
1904
1905    pub fn follow_next_collaborator(
1906        &mut self,
1907        _: &FollowNextCollaborator,
1908        cx: &mut ViewContext<Self>,
1909    ) -> Option<Task<Result<()>>> {
1910        let collaborators = self.project.read(cx).collaborators();
1911        let next_leader_id = if let Some(leader_id) = self.leader_for_pane(&self.active_pane) {
1912            let mut collaborators = collaborators.keys().copied();
1913            for peer_id in collaborators.by_ref() {
1914                if peer_id == leader_id {
1915                    break;
1916                }
1917            }
1918            collaborators.next()
1919        } else if let Some(last_leader_id) =
1920            self.last_leaders_by_pane.get(&self.active_pane.downgrade())
1921        {
1922            if collaborators.contains_key(last_leader_id) {
1923                Some(*last_leader_id)
1924            } else {
1925                None
1926            }
1927        } else {
1928            None
1929        };
1930
1931        next_leader_id
1932            .or_else(|| collaborators.keys().copied().next())
1933            .and_then(|leader_id| self.toggle_follow(&ToggleFollow(leader_id), cx))
1934    }
1935
1936    pub fn unfollow(
1937        &mut self,
1938        pane: &ViewHandle<Pane>,
1939        cx: &mut ViewContext<Self>,
1940    ) -> Option<PeerId> {
1941        for (leader_id, states_by_pane) in &mut self.follower_states_by_leader {
1942            let leader_id = *leader_id;
1943            if let Some(state) = states_by_pane.remove(pane) {
1944                for (_, item) in state.items_by_leader_view_id {
1945                    if let FollowerItem::Loaded(item) = item {
1946                        item.set_leader_replica_id(None, cx);
1947                    }
1948                }
1949
1950                if states_by_pane.is_empty() {
1951                    self.follower_states_by_leader.remove(&leader_id);
1952                    if let Some(project_id) = self.project.read(cx).remote_id() {
1953                        self.client
1954                            .send(proto::Unfollow {
1955                                project_id,
1956                                leader_id: leader_id.0,
1957                            })
1958                            .log_err();
1959                    }
1960                }
1961
1962                cx.notify();
1963                return Some(leader_id);
1964            }
1965        }
1966        None
1967    }
1968
1969    pub fn is_following(&self, peer_id: PeerId) -> bool {
1970        self.follower_states_by_leader.contains_key(&peer_id)
1971    }
1972
1973    fn render_titlebar(&self, theme: &Theme, cx: &mut RenderContext<Self>) -> ElementBox {
1974        let project = &self.project.read(cx);
1975        let mut worktree_root_names = String::new();
1976        for (i, name) in project.worktree_root_names(cx).enumerate() {
1977            if i > 0 {
1978                worktree_root_names.push_str(", ");
1979            }
1980            worktree_root_names.push_str(name);
1981        }
1982
1983        // TODO: There should be a better system in place for this
1984        // (https://github.com/zed-industries/zed/issues/1290)
1985        let is_fullscreen = cx.window_is_fullscreen(cx.window_id());
1986        let container_theme = if is_fullscreen {
1987            let mut container_theme = theme.workspace.titlebar.container;
1988            container_theme.padding.left = container_theme.padding.right;
1989            container_theme
1990        } else {
1991            theme.workspace.titlebar.container
1992        };
1993
1994        enum TitleBar {}
1995        ConstrainedBox::new(
1996            MouseEventHandler::<TitleBar>::new(0, cx, |_, _| {
1997                Container::new(
1998                    Stack::new()
1999                        .with_child(
2000                            Label::new(worktree_root_names, theme.workspace.titlebar.title.clone())
2001                                .aligned()
2002                                .left()
2003                                .boxed(),
2004                        )
2005                        .with_children(
2006                            self.titlebar_item
2007                                .as_ref()
2008                                .map(|item| ChildView::new(item).aligned().right().boxed()),
2009                        )
2010                        .boxed(),
2011                )
2012                .with_style(container_theme)
2013                .boxed()
2014            })
2015            .on_click(MouseButton::Left, |event, cx| {
2016                if event.click_count == 2 {
2017                    cx.zoom_window(cx.window_id());
2018                }
2019            })
2020            .boxed(),
2021        )
2022        .with_height(theme.workspace.titlebar.height)
2023        .named("titlebar")
2024    }
2025
2026    fn active_item_path_changed(&mut self, cx: &mut ViewContext<Self>) {
2027        let active_entry = self.active_project_path(cx);
2028        self.project
2029            .update(cx, |project, cx| project.set_active_path(active_entry, cx));
2030        self.update_window_title(cx);
2031    }
2032
2033    fn update_window_title(&mut self, cx: &mut ViewContext<Self>) {
2034        let mut title = String::new();
2035        let project = self.project().read(cx);
2036        if let Some(path) = self.active_item(cx).and_then(|item| item.project_path(cx)) {
2037            let filename = path
2038                .path
2039                .file_name()
2040                .map(|s| s.to_string_lossy())
2041                .or_else(|| {
2042                    Some(Cow::Borrowed(
2043                        project
2044                            .worktree_for_id(path.worktree_id, cx)?
2045                            .read(cx)
2046                            .root_name(),
2047                    ))
2048                });
2049            if let Some(filename) = filename {
2050                title.push_str(filename.as_ref());
2051                title.push_str("");
2052            }
2053        }
2054        for (i, name) in project.worktree_root_names(cx).enumerate() {
2055            if i > 0 {
2056                title.push_str(", ");
2057            }
2058            title.push_str(name);
2059        }
2060        if title.is_empty() {
2061            title = "empty project".to_string();
2062        }
2063        cx.set_window_title(&title);
2064    }
2065
2066    fn update_window_edited(&mut self, cx: &mut ViewContext<Self>) {
2067        let is_edited = !self.project.read(cx).is_read_only()
2068            && self
2069                .items(cx)
2070                .any(|item| item.has_conflict(cx) || item.is_dirty(cx));
2071        if is_edited != self.window_edited {
2072            self.window_edited = is_edited;
2073            cx.set_window_edited(self.window_edited)
2074        }
2075    }
2076
2077    fn render_disconnected_overlay(&self, cx: &mut RenderContext<Workspace>) -> Option<ElementBox> {
2078        if self.project.read(cx).is_read_only() {
2079            enum DisconnectedOverlay {}
2080            Some(
2081                MouseEventHandler::<DisconnectedOverlay>::new(0, cx, |_, cx| {
2082                    let theme = &cx.global::<Settings>().theme;
2083                    Label::new(
2084                        "Your connection to the remote project has been lost.".to_string(),
2085                        theme.workspace.disconnected_overlay.text.clone(),
2086                    )
2087                    .aligned()
2088                    .contained()
2089                    .with_style(theme.workspace.disconnected_overlay.container)
2090                    .boxed()
2091                })
2092                .with_cursor_style(CursorStyle::Arrow)
2093                .capture_all()
2094                .boxed(),
2095            )
2096        } else {
2097            None
2098        }
2099    }
2100
2101    fn render_notifications(&self, theme: &theme::Workspace) -> Option<ElementBox> {
2102        if self.notifications.is_empty() {
2103            None
2104        } else {
2105            Some(
2106                Flex::column()
2107                    .with_children(self.notifications.iter().map(|(_, _, notification)| {
2108                        ChildView::new(notification.as_ref())
2109                            .contained()
2110                            .with_style(theme.notification)
2111                            .boxed()
2112                    }))
2113                    .constrained()
2114                    .with_width(theme.notifications.width)
2115                    .contained()
2116                    .with_style(theme.notifications.container)
2117                    .aligned()
2118                    .bottom()
2119                    .right()
2120                    .boxed(),
2121            )
2122        }
2123    }
2124
2125    // RPC handlers
2126
2127    async fn handle_follow(
2128        this: ViewHandle<Self>,
2129        envelope: TypedEnvelope<proto::Follow>,
2130        _: Arc<Client>,
2131        mut cx: AsyncAppContext,
2132    ) -> Result<proto::FollowResponse> {
2133        this.update(&mut cx, |this, cx| {
2134            this.leader_state
2135                .followers
2136                .insert(envelope.original_sender_id()?);
2137
2138            let active_view_id = this
2139                .active_item(cx)
2140                .and_then(|i| i.to_followable_item_handle(cx))
2141                .map(|i| i.id() as u64);
2142            Ok(proto::FollowResponse {
2143                active_view_id,
2144                views: this
2145                    .panes()
2146                    .iter()
2147                    .flat_map(|pane| {
2148                        let leader_id = this.leader_for_pane(pane).map(|id| id.0);
2149                        pane.read(cx).items().filter_map({
2150                            let cx = &cx;
2151                            move |item| {
2152                                let id = item.id() as u64;
2153                                let item = item.to_followable_item_handle(cx)?;
2154                                let variant = item.to_state_proto(cx)?;
2155                                Some(proto::View {
2156                                    id,
2157                                    leader_id,
2158                                    variant: Some(variant),
2159                                })
2160                            }
2161                        })
2162                    })
2163                    .collect(),
2164            })
2165        })
2166    }
2167
2168    async fn handle_unfollow(
2169        this: ViewHandle<Self>,
2170        envelope: TypedEnvelope<proto::Unfollow>,
2171        _: Arc<Client>,
2172        mut cx: AsyncAppContext,
2173    ) -> Result<()> {
2174        this.update(&mut cx, |this, _| {
2175            this.leader_state
2176                .followers
2177                .remove(&envelope.original_sender_id()?);
2178            Ok(())
2179        })
2180    }
2181
2182    async fn handle_update_followers(
2183        this: ViewHandle<Self>,
2184        envelope: TypedEnvelope<proto::UpdateFollowers>,
2185        _: Arc<Client>,
2186        mut cx: AsyncAppContext,
2187    ) -> Result<()> {
2188        let leader_id = envelope.original_sender_id()?;
2189        match envelope
2190            .payload
2191            .variant
2192            .ok_or_else(|| anyhow!("invalid update"))?
2193        {
2194            proto::update_followers::Variant::UpdateActiveView(update_active_view) => {
2195                this.update(&mut cx, |this, cx| {
2196                    this.update_leader_state(leader_id, cx, |state, _| {
2197                        state.active_view_id = update_active_view.id;
2198                    });
2199                    Ok::<_, anyhow::Error>(())
2200                })
2201            }
2202            proto::update_followers::Variant::UpdateView(update_view) => {
2203                this.update(&mut cx, |this, cx| {
2204                    let variant = update_view
2205                        .variant
2206                        .ok_or_else(|| anyhow!("missing update view variant"))?;
2207                    this.update_leader_state(leader_id, cx, |state, cx| {
2208                        let variant = variant.clone();
2209                        match state
2210                            .items_by_leader_view_id
2211                            .entry(update_view.id)
2212                            .or_insert(FollowerItem::Loading(Vec::new()))
2213                        {
2214                            FollowerItem::Loaded(item) => {
2215                                item.apply_update_proto(variant, cx).log_err();
2216                            }
2217                            FollowerItem::Loading(updates) => updates.push(variant),
2218                        }
2219                    });
2220                    Ok(())
2221                })
2222            }
2223            proto::update_followers::Variant::CreateView(view) => {
2224                let panes = this.read_with(&cx, |this, _| {
2225                    this.follower_states_by_leader
2226                        .get(&leader_id)
2227                        .into_iter()
2228                        .flat_map(|states_by_pane| states_by_pane.keys())
2229                        .cloned()
2230                        .collect()
2231                });
2232                Self::add_views_from_leader(this.clone(), leader_id, panes, vec![view], &mut cx)
2233                    .await?;
2234                Ok(())
2235            }
2236        }
2237        .log_err();
2238
2239        Ok(())
2240    }
2241
2242    async fn add_views_from_leader(
2243        this: ViewHandle<Self>,
2244        leader_id: PeerId,
2245        panes: Vec<ViewHandle<Pane>>,
2246        views: Vec<proto::View>,
2247        cx: &mut AsyncAppContext,
2248    ) -> Result<()> {
2249        let project = this.read_with(cx, |this, _| this.project.clone());
2250        let replica_id = project
2251            .read_with(cx, |project, _| {
2252                project
2253                    .collaborators()
2254                    .get(&leader_id)
2255                    .map(|c| c.replica_id)
2256            })
2257            .ok_or_else(|| anyhow!("no such collaborator {}", leader_id))?;
2258
2259        let item_builders = cx.update(|cx| {
2260            cx.default_global::<FollowableItemBuilders>()
2261                .values()
2262                .map(|b| b.0)
2263                .collect::<Vec<_>>()
2264        });
2265
2266        let mut item_tasks_by_pane = HashMap::default();
2267        for pane in panes {
2268            let mut item_tasks = Vec::new();
2269            let mut leader_view_ids = Vec::new();
2270            for view in &views {
2271                let mut variant = view.variant.clone();
2272                if variant.is_none() {
2273                    Err(anyhow!("missing variant"))?;
2274                }
2275                for build_item in &item_builders {
2276                    let task =
2277                        cx.update(|cx| build_item(pane.clone(), project.clone(), &mut variant, cx));
2278                    if let Some(task) = task {
2279                        item_tasks.push(task);
2280                        leader_view_ids.push(view.id);
2281                        break;
2282                    } else {
2283                        assert!(variant.is_some());
2284                    }
2285                }
2286            }
2287
2288            item_tasks_by_pane.insert(pane, (item_tasks, leader_view_ids));
2289        }
2290
2291        for (pane, (item_tasks, leader_view_ids)) in item_tasks_by_pane {
2292            let items = futures::future::try_join_all(item_tasks).await?;
2293            this.update(cx, |this, cx| {
2294                let state = this
2295                    .follower_states_by_leader
2296                    .get_mut(&leader_id)?
2297                    .get_mut(&pane)?;
2298
2299                for (id, item) in leader_view_ids.into_iter().zip(items) {
2300                    item.set_leader_replica_id(Some(replica_id), cx);
2301                    match state.items_by_leader_view_id.entry(id) {
2302                        hash_map::Entry::Occupied(e) => {
2303                            let e = e.into_mut();
2304                            if let FollowerItem::Loading(updates) = e {
2305                                for update in updates.drain(..) {
2306                                    item.apply_update_proto(update, cx)
2307                                        .context("failed to apply view update")
2308                                        .log_err();
2309                                }
2310                            }
2311                            *e = FollowerItem::Loaded(item);
2312                        }
2313                        hash_map::Entry::Vacant(e) => {
2314                            e.insert(FollowerItem::Loaded(item));
2315                        }
2316                    }
2317                }
2318
2319                Some(())
2320            });
2321        }
2322        this.update(cx, |this, cx| this.leader_updated(leader_id, cx));
2323
2324        Ok(())
2325    }
2326
2327    fn update_followers(
2328        &self,
2329        update: proto::update_followers::Variant,
2330        cx: &AppContext,
2331    ) -> Option<()> {
2332        let project_id = self.project.read(cx).remote_id()?;
2333        if !self.leader_state.followers.is_empty() {
2334            self.client
2335                .send(proto::UpdateFollowers {
2336                    project_id,
2337                    follower_ids: self.leader_state.followers.iter().map(|f| f.0).collect(),
2338                    variant: Some(update),
2339                })
2340                .log_err();
2341        }
2342        None
2343    }
2344
2345    pub fn leader_for_pane(&self, pane: &ViewHandle<Pane>) -> Option<PeerId> {
2346        self.follower_states_by_leader
2347            .iter()
2348            .find_map(|(leader_id, state)| {
2349                if state.contains_key(pane) {
2350                    Some(*leader_id)
2351                } else {
2352                    None
2353                }
2354            })
2355    }
2356
2357    fn update_leader_state(
2358        &mut self,
2359        leader_id: PeerId,
2360        cx: &mut ViewContext<Self>,
2361        mut update_fn: impl FnMut(&mut FollowerState, &mut ViewContext<Self>),
2362    ) {
2363        for (_, state) in self
2364            .follower_states_by_leader
2365            .get_mut(&leader_id)
2366            .into_iter()
2367            .flatten()
2368        {
2369            update_fn(state, cx);
2370        }
2371        self.leader_updated(leader_id, cx);
2372    }
2373
2374    fn leader_updated(&mut self, leader_id: PeerId, cx: &mut ViewContext<Self>) -> Option<()> {
2375        let mut items_to_add = Vec::new();
2376        for (pane, state) in self.follower_states_by_leader.get(&leader_id)? {
2377            if let Some(FollowerItem::Loaded(item)) = state
2378                .active_view_id
2379                .and_then(|id| state.items_by_leader_view_id.get(&id))
2380            {
2381                items_to_add.push((pane.clone(), item.boxed_clone()));
2382            }
2383        }
2384
2385        for (pane, item) in items_to_add {
2386            Pane::add_item(self, &pane, item.boxed_clone(), false, false, None, cx);
2387            if pane == self.active_pane {
2388                pane.update(cx, |pane, cx| pane.focus_active_item(cx));
2389            }
2390            cx.notify();
2391        }
2392        None
2393    }
2394
2395    pub fn on_window_activation_changed(&mut self, active: bool, cx: &mut ViewContext<Self>) {
2396        if !active {
2397            for pane in &self.panes {
2398                pane.update(cx, |pane, cx| {
2399                    if let Some(item) = pane.active_item() {
2400                        item.workspace_deactivated(cx);
2401                    }
2402                    if matches!(
2403                        cx.global::<Settings>().autosave,
2404                        Autosave::OnWindowChange | Autosave::OnFocusChange
2405                    ) {
2406                        for item in pane.items() {
2407                            Pane::autosave_item(item.as_ref(), self.project.clone(), cx)
2408                                .detach_and_log_err(cx);
2409                        }
2410                    }
2411                });
2412            }
2413        }
2414    }
2415}
2416
2417impl Entity for Workspace {
2418    type Event = Event;
2419}
2420
2421impl View for Workspace {
2422    fn ui_name() -> &'static str {
2423        "Workspace"
2424    }
2425
2426    fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
2427        let theme = cx.global::<Settings>().theme.clone();
2428        Stack::new()
2429            .with_child(
2430                Flex::column()
2431                    .with_child(self.render_titlebar(&theme, cx))
2432                    .with_child(
2433                        Stack::new()
2434                            .with_child({
2435                                Flex::row()
2436                                    .with_children(
2437                                        if self.left_sidebar.read(cx).active_item().is_some() {
2438                                            Some(
2439                                                ChildView::new(&self.left_sidebar)
2440                                                    .flex(0.8, false)
2441                                                    .boxed(),
2442                                            )
2443                                        } else {
2444                                            None
2445                                        },
2446                                    )
2447                                    .with_child(
2448                                        FlexItem::new(
2449                                            Flex::column()
2450                                                .with_child(
2451                                                    FlexItem::new(self.center.render(
2452                                                        &theme,
2453                                                        &self.follower_states_by_leader,
2454                                                        self.project.read(cx).collaborators(),
2455                                                    ))
2456                                                    .flex(1., true)
2457                                                    .boxed(),
2458                                                )
2459                                                .with_children(self.dock.render(
2460                                                    &theme,
2461                                                    DockAnchor::Bottom,
2462                                                    cx,
2463                                                ))
2464                                                .boxed(),
2465                                        )
2466                                        .flex(1., true)
2467                                        .boxed(),
2468                                    )
2469                                    .with_children(self.dock.render(&theme, DockAnchor::Right, cx))
2470                                    .with_children(
2471                                        if self.right_sidebar.read(cx).active_item().is_some() {
2472                                            Some(
2473                                                ChildView::new(&self.right_sidebar)
2474                                                    .flex(0.8, false)
2475                                                    .boxed(),
2476                                            )
2477                                        } else {
2478                                            None
2479                                        },
2480                                    )
2481                                    .boxed()
2482                            })
2483                            .with_child(
2484                                Overlay::new(
2485                                    Stack::new()
2486                                        .with_children(self.dock.render(
2487                                            &theme,
2488                                            DockAnchor::Expanded,
2489                                            cx,
2490                                        ))
2491                                        .with_children(self.modal.as_ref().map(|m| {
2492                                            ChildView::new(m)
2493                                                .contained()
2494                                                .with_style(theme.workspace.modal)
2495                                                .aligned()
2496                                                .top()
2497                                                .boxed()
2498                                        }))
2499                                        .with_children(self.render_notifications(&theme.workspace))
2500                                        .boxed(),
2501                                )
2502                                .boxed(),
2503                            )
2504                            .flex(1.0, true)
2505                            .boxed(),
2506                    )
2507                    .with_child(ChildView::new(&self.status_bar).boxed())
2508                    .contained()
2509                    .with_background_color(theme.workspace.background)
2510                    .boxed(),
2511            )
2512            .with_children(DragAndDrop::render(cx))
2513            .with_children(self.render_disconnected_overlay(cx))
2514            .named("workspace")
2515    }
2516
2517    fn on_focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
2518        if cx.is_self_focused() {
2519            cx.focus(&self.active_pane);
2520        }
2521    }
2522
2523    fn keymap_context(&self, _: &AppContext) -> gpui::keymap::Context {
2524        let mut keymap = Self::default_keymap_context();
2525        if self.active_pane() == self.dock_pane() {
2526            keymap.set.insert("Dock".into());
2527        }
2528        keymap
2529    }
2530}
2531
2532pub trait WorkspaceHandle {
2533    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath>;
2534}
2535
2536impl WorkspaceHandle for ViewHandle<Workspace> {
2537    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath> {
2538        self.read(cx)
2539            .worktrees(cx)
2540            .flat_map(|worktree| {
2541                let worktree_id = worktree.read(cx).id();
2542                worktree.read(cx).files(true, 0).map(move |f| ProjectPath {
2543                    worktree_id,
2544                    path: f.path.clone(),
2545                })
2546            })
2547            .collect::<Vec<_>>()
2548    }
2549}
2550
2551impl std::fmt::Debug for OpenPaths {
2552    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2553        f.debug_struct("OpenPaths")
2554            .field("paths", &self.paths)
2555            .finish()
2556    }
2557}
2558
2559fn open(_: &Open, cx: &mut MutableAppContext) {
2560    let mut paths = cx.prompt_for_paths(PathPromptOptions {
2561        files: true,
2562        directories: true,
2563        multiple: true,
2564    });
2565    cx.spawn(|mut cx| async move {
2566        if let Some(paths) = paths.recv().await.flatten() {
2567            cx.update(|cx| cx.dispatch_global_action(OpenPaths { paths }));
2568        }
2569    })
2570    .detach();
2571}
2572
2573pub struct WorkspaceCreated(WeakViewHandle<Workspace>);
2574
2575pub fn activate_workspace_for_project(
2576    cx: &mut MutableAppContext,
2577    predicate: impl Fn(&mut Project, &mut ModelContext<Project>) -> bool,
2578) -> Option<ViewHandle<Workspace>> {
2579    for window_id in cx.window_ids().collect::<Vec<_>>() {
2580        if let Some(workspace_handle) = cx.root_view::<Workspace>(window_id) {
2581            let project = workspace_handle.read(cx).project.clone();
2582            if project.update(cx, &predicate) {
2583                cx.activate_window(window_id);
2584                return Some(workspace_handle);
2585            }
2586        }
2587    }
2588    None
2589}
2590
2591#[allow(clippy::type_complexity)]
2592pub fn open_paths(
2593    abs_paths: &[PathBuf],
2594    app_state: &Arc<AppState>,
2595    cx: &mut MutableAppContext,
2596) -> Task<(
2597    ViewHandle<Workspace>,
2598    Vec<Option<Result<Box<dyn ItemHandle>, Arc<anyhow::Error>>>>,
2599)> {
2600    log::info!("open paths {:?}", abs_paths);
2601
2602    // Open paths in existing workspace if possible
2603    let existing =
2604        activate_workspace_for_project(cx, |project, cx| project.contains_paths(abs_paths, cx));
2605
2606    let app_state = app_state.clone();
2607    let abs_paths = abs_paths.to_vec();
2608    cx.spawn(|mut cx| async move {
2609        let mut new_project = None;
2610        let workspace = if let Some(existing) = existing {
2611            existing
2612        } else {
2613            let contains_directory =
2614                futures::future::join_all(abs_paths.iter().map(|path| app_state.fs.is_file(path)))
2615                    .await
2616                    .contains(&false);
2617
2618            cx.add_window((app_state.build_window_options)(), |cx| {
2619                let project = Project::local(
2620                    false,
2621                    app_state.client.clone(),
2622                    app_state.user_store.clone(),
2623                    app_state.project_store.clone(),
2624                    app_state.languages.clone(),
2625                    app_state.fs.clone(),
2626                    cx,
2627                );
2628                new_project = Some(project.clone());
2629                let mut workspace = Workspace::new(project, app_state.default_item_factory, cx);
2630                (app_state.initialize_workspace)(&mut workspace, &app_state, cx);
2631                if contains_directory {
2632                    workspace.toggle_sidebar(SidebarSide::Left, cx);
2633                }
2634                workspace
2635            })
2636            .1
2637        };
2638
2639        let items = workspace
2640            .update(&mut cx, |workspace, cx| {
2641                workspace.open_paths(abs_paths, true, cx)
2642            })
2643            .await;
2644
2645        if let Some(project) = new_project {
2646            project
2647                .update(&mut cx, |project, cx| project.restore_state(cx))
2648                .await
2649                .log_err();
2650        }
2651
2652        (workspace, items)
2653    })
2654}
2655
2656fn open_new(app_state: &Arc<AppState>, cx: &mut MutableAppContext) {
2657    let (window_id, workspace) = cx.add_window((app_state.build_window_options)(), |cx| {
2658        let mut workspace = Workspace::new(
2659            Project::local(
2660                false,
2661                app_state.client.clone(),
2662                app_state.user_store.clone(),
2663                app_state.project_store.clone(),
2664                app_state.languages.clone(),
2665                app_state.fs.clone(),
2666                cx,
2667            ),
2668            app_state.default_item_factory,
2669            cx,
2670        );
2671        (app_state.initialize_workspace)(&mut workspace, app_state, cx);
2672        workspace
2673    });
2674    cx.dispatch_action_at(window_id, workspace.id(), NewFile);
2675}
2676
2677#[cfg(test)]
2678mod tests {
2679    use std::cell::Cell;
2680
2681    use crate::sidebar::SidebarItem;
2682
2683    use super::*;
2684    use gpui::{executor::Deterministic, ModelHandle, TestAppContext, ViewContext};
2685    use project::{FakeFs, Project, ProjectEntryId};
2686    use serde_json::json;
2687
2688    pub fn default_item_factory(
2689        _workspace: &mut Workspace,
2690        _cx: &mut ViewContext<Workspace>,
2691    ) -> Box<dyn ItemHandle> {
2692        unimplemented!();
2693    }
2694
2695    #[gpui::test]
2696    async fn test_tab_disambiguation(cx: &mut TestAppContext) {
2697        cx.foreground().forbid_parking();
2698        Settings::test_async(cx);
2699
2700        let fs = FakeFs::new(cx.background());
2701        let project = Project::test(fs, [], cx).await;
2702        let (_, workspace) =
2703            cx.add_window(|cx| Workspace::new(project.clone(), default_item_factory, cx));
2704
2705        // Adding an item with no ambiguity renders the tab without detail.
2706        let item1 = cx.add_view(&workspace, |_| {
2707            let mut item = TestItem::new();
2708            item.tab_descriptions = Some(vec!["c", "b1/c", "a/b1/c"]);
2709            item
2710        });
2711        workspace.update(cx, |workspace, cx| {
2712            workspace.add_item(Box::new(item1.clone()), cx);
2713        });
2714        item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), None));
2715
2716        // Adding an item that creates ambiguity increases the level of detail on
2717        // both tabs.
2718        let item2 = cx.add_view(&workspace, |_| {
2719            let mut item = TestItem::new();
2720            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
2721            item
2722        });
2723        workspace.update(cx, |workspace, cx| {
2724            workspace.add_item(Box::new(item2.clone()), cx);
2725        });
2726        item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
2727        item2.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
2728
2729        // Adding an item that creates ambiguity increases the level of detail only
2730        // on the ambiguous tabs. In this case, the ambiguity can't be resolved so
2731        // we stop at the highest detail available.
2732        let item3 = cx.add_view(&workspace, |_| {
2733            let mut item = TestItem::new();
2734            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
2735            item
2736        });
2737        workspace.update(cx, |workspace, cx| {
2738            workspace.add_item(Box::new(item3.clone()), cx);
2739        });
2740        item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
2741        item2.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
2742        item3.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
2743    }
2744
2745    #[gpui::test]
2746    async fn test_tracking_active_path(cx: &mut TestAppContext) {
2747        cx.foreground().forbid_parking();
2748        Settings::test_async(cx);
2749        let fs = FakeFs::new(cx.background());
2750        fs.insert_tree(
2751            "/root1",
2752            json!({
2753                "one.txt": "",
2754                "two.txt": "",
2755            }),
2756        )
2757        .await;
2758        fs.insert_tree(
2759            "/root2",
2760            json!({
2761                "three.txt": "",
2762            }),
2763        )
2764        .await;
2765
2766        let project = Project::test(fs, ["root1".as_ref()], cx).await;
2767        let (window_id, workspace) =
2768            cx.add_window(|cx| Workspace::new(project.clone(), default_item_factory, cx));
2769        let worktree_id = project.read_with(cx, |project, cx| {
2770            project.worktrees(cx).next().unwrap().read(cx).id()
2771        });
2772
2773        let item1 = cx.add_view(&workspace, |_| {
2774            let mut item = TestItem::new();
2775            item.project_path = Some((worktree_id, "one.txt").into());
2776            item
2777        });
2778        let item2 = cx.add_view(&workspace, |_| {
2779            let mut item = TestItem::new();
2780            item.project_path = Some((worktree_id, "two.txt").into());
2781            item
2782        });
2783
2784        // Add an item to an empty pane
2785        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item1), cx));
2786        project.read_with(cx, |project, cx| {
2787            assert_eq!(
2788                project.active_entry(),
2789                project
2790                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
2791                    .map(|e| e.id)
2792            );
2793        });
2794        assert_eq!(
2795            cx.current_window_title(window_id).as_deref(),
2796            Some("one.txt — root1")
2797        );
2798
2799        // Add a second item to a non-empty pane
2800        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item2), cx));
2801        assert_eq!(
2802            cx.current_window_title(window_id).as_deref(),
2803            Some("two.txt — root1")
2804        );
2805        project.read_with(cx, |project, cx| {
2806            assert_eq!(
2807                project.active_entry(),
2808                project
2809                    .entry_for_path(&(worktree_id, "two.txt").into(), cx)
2810                    .map(|e| e.id)
2811            );
2812        });
2813
2814        // Close the active item
2815        workspace
2816            .update(cx, |workspace, cx| {
2817                Pane::close_active_item(workspace, &Default::default(), cx).unwrap()
2818            })
2819            .await
2820            .unwrap();
2821        assert_eq!(
2822            cx.current_window_title(window_id).as_deref(),
2823            Some("one.txt — root1")
2824        );
2825        project.read_with(cx, |project, cx| {
2826            assert_eq!(
2827                project.active_entry(),
2828                project
2829                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
2830                    .map(|e| e.id)
2831            );
2832        });
2833
2834        // Add a project folder
2835        project
2836            .update(cx, |project, cx| {
2837                project.find_or_create_local_worktree("/root2", true, cx)
2838            })
2839            .await
2840            .unwrap();
2841        assert_eq!(
2842            cx.current_window_title(window_id).as_deref(),
2843            Some("one.txt — root1, root2")
2844        );
2845
2846        // Remove a project folder
2847        project.update(cx, |project, cx| {
2848            project.remove_worktree(worktree_id, cx);
2849        });
2850        assert_eq!(
2851            cx.current_window_title(window_id).as_deref(),
2852            Some("one.txt — root2")
2853        );
2854    }
2855
2856    #[gpui::test]
2857    async fn test_close_window(cx: &mut TestAppContext) {
2858        cx.foreground().forbid_parking();
2859        Settings::test_async(cx);
2860        let fs = FakeFs::new(cx.background());
2861        fs.insert_tree("/root", json!({ "one": "" })).await;
2862
2863        let project = Project::test(fs, ["root".as_ref()], cx).await;
2864        let (window_id, workspace) =
2865            cx.add_window(|cx| Workspace::new(project.clone(), default_item_factory, cx));
2866
2867        // When there are no dirty items, there's nothing to do.
2868        let item1 = cx.add_view(&workspace, |_| TestItem::new());
2869        workspace.update(cx, |w, cx| w.add_item(Box::new(item1.clone()), cx));
2870        let task = workspace.update(cx, |w, cx| w.prepare_to_close(cx));
2871        assert!(task.await.unwrap());
2872
2873        // When there are dirty untitled items, prompt to save each one. If the user
2874        // cancels any prompt, then abort.
2875        let item2 = cx.add_view(&workspace, |_| {
2876            let mut item = TestItem::new();
2877            item.is_dirty = true;
2878            item
2879        });
2880        let item3 = cx.add_view(&workspace, |_| {
2881            let mut item = TestItem::new();
2882            item.is_dirty = true;
2883            item.project_entry_ids = vec![ProjectEntryId::from_proto(1)];
2884            item
2885        });
2886        workspace.update(cx, |w, cx| {
2887            w.add_item(Box::new(item2.clone()), cx);
2888            w.add_item(Box::new(item3.clone()), cx);
2889        });
2890        let task = workspace.update(cx, |w, cx| w.prepare_to_close(cx));
2891        cx.foreground().run_until_parked();
2892        cx.simulate_prompt_answer(window_id, 2 /* cancel */);
2893        cx.foreground().run_until_parked();
2894        assert!(!cx.has_pending_prompt(window_id));
2895        assert!(!task.await.unwrap());
2896    }
2897
2898    #[gpui::test]
2899    async fn test_close_pane_items(cx: &mut TestAppContext) {
2900        cx.foreground().forbid_parking();
2901        Settings::test_async(cx);
2902        let fs = FakeFs::new(cx.background());
2903
2904        let project = Project::test(fs, None, cx).await;
2905        let (window_id, workspace) =
2906            cx.add_window(|cx| Workspace::new(project, default_item_factory, cx));
2907
2908        let item1 = cx.add_view(&workspace, |_| {
2909            let mut item = TestItem::new();
2910            item.is_dirty = true;
2911            item.project_entry_ids = vec![ProjectEntryId::from_proto(1)];
2912            item
2913        });
2914        let item2 = cx.add_view(&workspace, |_| {
2915            let mut item = TestItem::new();
2916            item.is_dirty = true;
2917            item.has_conflict = true;
2918            item.project_entry_ids = vec![ProjectEntryId::from_proto(2)];
2919            item
2920        });
2921        let item3 = cx.add_view(&workspace, |_| {
2922            let mut item = TestItem::new();
2923            item.is_dirty = true;
2924            item.has_conflict = true;
2925            item.project_entry_ids = vec![ProjectEntryId::from_proto(3)];
2926            item
2927        });
2928        let item4 = cx.add_view(&workspace, |_| {
2929            let mut item = TestItem::new();
2930            item.is_dirty = true;
2931            item
2932        });
2933        let pane = workspace.update(cx, |workspace, cx| {
2934            workspace.add_item(Box::new(item1.clone()), cx);
2935            workspace.add_item(Box::new(item2.clone()), cx);
2936            workspace.add_item(Box::new(item3.clone()), cx);
2937            workspace.add_item(Box::new(item4.clone()), cx);
2938            workspace.active_pane().clone()
2939        });
2940
2941        let close_items = workspace.update(cx, |workspace, cx| {
2942            pane.update(cx, |pane, cx| {
2943                pane.activate_item(1, true, true, cx);
2944                assert_eq!(pane.active_item().unwrap().id(), item2.id());
2945            });
2946
2947            let item1_id = item1.id();
2948            let item3_id = item3.id();
2949            let item4_id = item4.id();
2950            Pane::close_items(workspace, pane.clone(), cx, move |id| {
2951                [item1_id, item3_id, item4_id].contains(&id)
2952            })
2953        });
2954
2955        cx.foreground().run_until_parked();
2956        pane.read_with(cx, |pane, _| {
2957            assert_eq!(pane.items().count(), 4);
2958            assert_eq!(pane.active_item().unwrap().id(), item1.id());
2959        });
2960
2961        cx.simulate_prompt_answer(window_id, 0);
2962        cx.foreground().run_until_parked();
2963        pane.read_with(cx, |pane, cx| {
2964            assert_eq!(item1.read(cx).save_count, 1);
2965            assert_eq!(item1.read(cx).save_as_count, 0);
2966            assert_eq!(item1.read(cx).reload_count, 0);
2967            assert_eq!(pane.items().count(), 3);
2968            assert_eq!(pane.active_item().unwrap().id(), item3.id());
2969        });
2970
2971        cx.simulate_prompt_answer(window_id, 1);
2972        cx.foreground().run_until_parked();
2973        pane.read_with(cx, |pane, cx| {
2974            assert_eq!(item3.read(cx).save_count, 0);
2975            assert_eq!(item3.read(cx).save_as_count, 0);
2976            assert_eq!(item3.read(cx).reload_count, 1);
2977            assert_eq!(pane.items().count(), 2);
2978            assert_eq!(pane.active_item().unwrap().id(), item4.id());
2979        });
2980
2981        cx.simulate_prompt_answer(window_id, 0);
2982        cx.foreground().run_until_parked();
2983        cx.simulate_new_path_selection(|_| Some(Default::default()));
2984        close_items.await.unwrap();
2985        pane.read_with(cx, |pane, cx| {
2986            assert_eq!(item4.read(cx).save_count, 0);
2987            assert_eq!(item4.read(cx).save_as_count, 1);
2988            assert_eq!(item4.read(cx).reload_count, 0);
2989            assert_eq!(pane.items().count(), 1);
2990            assert_eq!(pane.active_item().unwrap().id(), item2.id());
2991        });
2992    }
2993
2994    #[gpui::test]
2995    async fn test_prompting_to_save_only_on_last_item_for_entry(cx: &mut TestAppContext) {
2996        cx.foreground().forbid_parking();
2997        Settings::test_async(cx);
2998        let fs = FakeFs::new(cx.background());
2999
3000        let project = Project::test(fs, [], cx).await;
3001        let (window_id, workspace) =
3002            cx.add_window(|cx| Workspace::new(project, default_item_factory, cx));
3003
3004        // Create several workspace items with single project entries, and two
3005        // workspace items with multiple project entries.
3006        let single_entry_items = (0..=4)
3007            .map(|project_entry_id| {
3008                let mut item = TestItem::new();
3009                item.is_dirty = true;
3010                item.project_entry_ids = vec![ProjectEntryId::from_proto(project_entry_id)];
3011                item.is_singleton = true;
3012                item
3013            })
3014            .collect::<Vec<_>>();
3015        let item_2_3 = {
3016            let mut item = TestItem::new();
3017            item.is_dirty = true;
3018            item.is_singleton = false;
3019            item.project_entry_ids =
3020                vec![ProjectEntryId::from_proto(2), ProjectEntryId::from_proto(3)];
3021            item
3022        };
3023        let item_3_4 = {
3024            let mut item = TestItem::new();
3025            item.is_dirty = true;
3026            item.is_singleton = false;
3027            item.project_entry_ids =
3028                vec![ProjectEntryId::from_proto(3), ProjectEntryId::from_proto(4)];
3029            item
3030        };
3031
3032        // Create two panes that contain the following project entries:
3033        //   left pane:
3034        //     multi-entry items:   (2, 3)
3035        //     single-entry items:  0, 1, 2, 3, 4
3036        //   right pane:
3037        //     single-entry items:  1
3038        //     multi-entry items:   (3, 4)
3039        let left_pane = workspace.update(cx, |workspace, cx| {
3040            let left_pane = workspace.active_pane().clone();
3041            workspace.add_item(Box::new(cx.add_view(|_| item_2_3.clone())), cx);
3042            for item in &single_entry_items {
3043                workspace.add_item(Box::new(cx.add_view(|_| item.clone())), cx);
3044            }
3045            left_pane.update(cx, |pane, cx| {
3046                pane.activate_item(2, true, true, cx);
3047            });
3048
3049            workspace
3050                .split_pane(left_pane.clone(), SplitDirection::Right, cx)
3051                .unwrap();
3052
3053            left_pane
3054        });
3055
3056        //Need to cause an effect flush in order to respect new focus
3057        workspace.update(cx, |workspace, cx| {
3058            workspace.add_item(Box::new(cx.add_view(|_| item_3_4.clone())), cx);
3059            cx.focus(left_pane.clone());
3060        });
3061
3062        // When closing all of the items in the left pane, we should be prompted twice:
3063        // once for project entry 0, and once for project entry 2. After those two
3064        // prompts, the task should complete.
3065
3066        let close = workspace.update(cx, |workspace, cx| {
3067            Pane::close_items(workspace, left_pane.clone(), cx, |_| true)
3068        });
3069
3070        cx.foreground().run_until_parked();
3071        left_pane.read_with(cx, |pane, cx| {
3072            assert_eq!(
3073                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
3074                &[ProjectEntryId::from_proto(0)]
3075            );
3076        });
3077        cx.simulate_prompt_answer(window_id, 0);
3078
3079        cx.foreground().run_until_parked();
3080        left_pane.read_with(cx, |pane, cx| {
3081            assert_eq!(
3082                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
3083                &[ProjectEntryId::from_proto(2)]
3084            );
3085        });
3086        cx.simulate_prompt_answer(window_id, 0);
3087
3088        cx.foreground().run_until_parked();
3089        close.await.unwrap();
3090        left_pane.read_with(cx, |pane, _| {
3091            assert_eq!(pane.items().count(), 0);
3092        });
3093    }
3094
3095    #[gpui::test]
3096    async fn test_autosave(deterministic: Arc<Deterministic>, cx: &mut gpui::TestAppContext) {
3097        deterministic.forbid_parking();
3098
3099        Settings::test_async(cx);
3100        let fs = FakeFs::new(cx.background());
3101
3102        let project = Project::test(fs, [], cx).await;
3103        let (window_id, workspace) =
3104            cx.add_window(|cx| Workspace::new(project, default_item_factory, cx));
3105
3106        let item = cx.add_view(&workspace, |_| {
3107            let mut item = TestItem::new();
3108            item.project_entry_ids = vec![ProjectEntryId::from_proto(1)];
3109            item
3110        });
3111        let item_id = item.id();
3112        workspace.update(cx, |workspace, cx| {
3113            workspace.add_item(Box::new(item.clone()), cx);
3114        });
3115
3116        // Autosave on window change.
3117        item.update(cx, |item, cx| {
3118            cx.update_global(|settings: &mut Settings, _| {
3119                settings.autosave = Autosave::OnWindowChange;
3120            });
3121            item.is_dirty = true;
3122        });
3123
3124        // Deactivating the window saves the file.
3125        cx.simulate_window_activation(None);
3126        deterministic.run_until_parked();
3127        item.read_with(cx, |item, _| assert_eq!(item.save_count, 1));
3128
3129        // Autosave on focus change.
3130        item.update(cx, |item, cx| {
3131            cx.focus_self();
3132            cx.update_global(|settings: &mut Settings, _| {
3133                settings.autosave = Autosave::OnFocusChange;
3134            });
3135            item.is_dirty = true;
3136        });
3137
3138        // Blurring the item saves the file.
3139        item.update(cx, |_, cx| cx.blur());
3140        deterministic.run_until_parked();
3141        item.read_with(cx, |item, _| assert_eq!(item.save_count, 2));
3142
3143        // Deactivating the window still saves the file.
3144        cx.simulate_window_activation(Some(window_id));
3145        item.update(cx, |item, cx| {
3146            cx.focus_self();
3147            item.is_dirty = true;
3148        });
3149        cx.simulate_window_activation(None);
3150
3151        deterministic.run_until_parked();
3152        item.read_with(cx, |item, _| assert_eq!(item.save_count, 3));
3153
3154        // Autosave after delay.
3155        item.update(cx, |item, cx| {
3156            cx.update_global(|settings: &mut Settings, _| {
3157                settings.autosave = Autosave::AfterDelay { milliseconds: 500 };
3158            });
3159            item.is_dirty = true;
3160            cx.emit(TestItemEvent::Edit);
3161        });
3162
3163        // Delay hasn't fully expired, so the file is still dirty and unsaved.
3164        deterministic.advance_clock(Duration::from_millis(250));
3165        item.read_with(cx, |item, _| assert_eq!(item.save_count, 3));
3166
3167        // After delay expires, the file is saved.
3168        deterministic.advance_clock(Duration::from_millis(250));
3169        item.read_with(cx, |item, _| assert_eq!(item.save_count, 4));
3170
3171        // Autosave on focus change, ensuring closing the tab counts as such.
3172        item.update(cx, |item, cx| {
3173            cx.update_global(|settings: &mut Settings, _| {
3174                settings.autosave = Autosave::OnFocusChange;
3175            });
3176            item.is_dirty = true;
3177        });
3178
3179        workspace
3180            .update(cx, |workspace, cx| {
3181                let pane = workspace.active_pane().clone();
3182                Pane::close_items(workspace, pane, cx, move |id| id == item_id)
3183            })
3184            .await
3185            .unwrap();
3186        assert!(!cx.has_pending_prompt(window_id));
3187        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
3188
3189        // Add the item again, ensuring autosave is prevented if the underlying file has been deleted.
3190        workspace.update(cx, |workspace, cx| {
3191            workspace.add_item(Box::new(item.clone()), cx);
3192        });
3193        item.update(cx, |item, cx| {
3194            item.project_entry_ids = Default::default();
3195            item.is_dirty = true;
3196            cx.blur();
3197        });
3198        deterministic.run_until_parked();
3199        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
3200
3201        // Ensure autosave is prevented for deleted files also when closing the buffer.
3202        let _close_items = workspace.update(cx, |workspace, cx| {
3203            let pane = workspace.active_pane().clone();
3204            Pane::close_items(workspace, pane, cx, move |id| id == item_id)
3205        });
3206        deterministic.run_until_parked();
3207        assert!(cx.has_pending_prompt(window_id));
3208        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
3209    }
3210
3211    #[gpui::test]
3212    async fn test_pane_navigation(
3213        deterministic: Arc<Deterministic>,
3214        cx: &mut gpui::TestAppContext,
3215    ) {
3216        deterministic.forbid_parking();
3217        Settings::test_async(cx);
3218        let fs = FakeFs::new(cx.background());
3219
3220        let project = Project::test(fs, [], cx).await;
3221        let (_, workspace) = cx.add_window(|cx| Workspace::new(project, default_item_factory, cx));
3222
3223        let item = cx.add_view(&workspace, |_| {
3224            let mut item = TestItem::new();
3225            item.project_entry_ids = vec![ProjectEntryId::from_proto(1)];
3226            item
3227        });
3228        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
3229        let toolbar = pane.read_with(cx, |pane, _| pane.toolbar().clone());
3230        let toolbar_notify_count = Rc::new(RefCell::new(0));
3231
3232        workspace.update(cx, |workspace, cx| {
3233            workspace.add_item(Box::new(item.clone()), cx);
3234            let toolbar_notification_count = toolbar_notify_count.clone();
3235            cx.observe(&toolbar, move |_, _, _| {
3236                *toolbar_notification_count.borrow_mut() += 1
3237            })
3238            .detach();
3239        });
3240
3241        pane.read_with(cx, |pane, _| {
3242            assert!(!pane.can_navigate_backward());
3243            assert!(!pane.can_navigate_forward());
3244        });
3245
3246        item.update(cx, |item, cx| {
3247            item.set_state("one".to_string(), cx);
3248        });
3249
3250        // Toolbar must be notified to re-render the navigation buttons
3251        assert_eq!(*toolbar_notify_count.borrow(), 1);
3252
3253        pane.read_with(cx, |pane, _| {
3254            assert!(pane.can_navigate_backward());
3255            assert!(!pane.can_navigate_forward());
3256        });
3257
3258        workspace
3259            .update(cx, |workspace, cx| {
3260                Pane::go_back(workspace, Some(pane.clone()), cx)
3261            })
3262            .await;
3263
3264        assert_eq!(*toolbar_notify_count.borrow(), 3);
3265        pane.read_with(cx, |pane, _| {
3266            assert!(!pane.can_navigate_backward());
3267            assert!(pane.can_navigate_forward());
3268        });
3269    }
3270
3271    pub struct TestItem {
3272        state: String,
3273        pub label: String,
3274        save_count: usize,
3275        save_as_count: usize,
3276        reload_count: usize,
3277        is_dirty: bool,
3278        is_singleton: bool,
3279        has_conflict: bool,
3280        project_entry_ids: Vec<ProjectEntryId>,
3281        project_path: Option<ProjectPath>,
3282        nav_history: Option<ItemNavHistory>,
3283        tab_descriptions: Option<Vec<&'static str>>,
3284        tab_detail: Cell<Option<usize>>,
3285    }
3286
3287    pub enum TestItemEvent {
3288        Edit,
3289    }
3290
3291    impl Clone for TestItem {
3292        fn clone(&self) -> Self {
3293            Self {
3294                state: self.state.clone(),
3295                label: self.label.clone(),
3296                save_count: self.save_count,
3297                save_as_count: self.save_as_count,
3298                reload_count: self.reload_count,
3299                is_dirty: self.is_dirty,
3300                is_singleton: self.is_singleton,
3301                has_conflict: self.has_conflict,
3302                project_entry_ids: self.project_entry_ids.clone(),
3303                project_path: self.project_path.clone(),
3304                nav_history: None,
3305                tab_descriptions: None,
3306                tab_detail: Default::default(),
3307            }
3308        }
3309    }
3310
3311    impl TestItem {
3312        pub fn new() -> Self {
3313            Self {
3314                state: String::new(),
3315                label: String::new(),
3316                save_count: 0,
3317                save_as_count: 0,
3318                reload_count: 0,
3319                is_dirty: false,
3320                has_conflict: false,
3321                project_entry_ids: Vec::new(),
3322                project_path: None,
3323                is_singleton: true,
3324                nav_history: None,
3325                tab_descriptions: None,
3326                tab_detail: Default::default(),
3327            }
3328        }
3329
3330        pub fn with_label(mut self, state: &str) -> Self {
3331            self.label = state.to_string();
3332            self
3333        }
3334
3335        pub fn with_singleton(mut self, singleton: bool) -> Self {
3336            self.is_singleton = singleton;
3337            self
3338        }
3339
3340        pub fn with_project_entry_ids(mut self, project_entry_ids: &[u64]) -> Self {
3341            self.project_entry_ids.extend(
3342                project_entry_ids
3343                    .iter()
3344                    .copied()
3345                    .map(ProjectEntryId::from_proto),
3346            );
3347            self
3348        }
3349
3350        fn set_state(&mut self, state: String, cx: &mut ViewContext<Self>) {
3351            self.push_to_nav_history(cx);
3352            self.state = state;
3353        }
3354
3355        fn push_to_nav_history(&mut self, cx: &mut ViewContext<Self>) {
3356            if let Some(history) = &mut self.nav_history {
3357                history.push(Some(Box::new(self.state.clone())), cx);
3358            }
3359        }
3360    }
3361
3362    impl Entity for TestItem {
3363        type Event = TestItemEvent;
3364    }
3365
3366    impl View for TestItem {
3367        fn ui_name() -> &'static str {
3368            "TestItem"
3369        }
3370
3371        fn render(&mut self, _: &mut RenderContext<Self>) -> ElementBox {
3372            Empty::new().boxed()
3373        }
3374    }
3375
3376    impl Item for TestItem {
3377        fn tab_description<'a>(&'a self, detail: usize, _: &'a AppContext) -> Option<Cow<'a, str>> {
3378            self.tab_descriptions.as_ref().and_then(|descriptions| {
3379                let description = *descriptions.get(detail).or_else(|| descriptions.last())?;
3380                Some(description.into())
3381            })
3382        }
3383
3384        fn tab_content(&self, detail: Option<usize>, _: &theme::Tab, _: &AppContext) -> ElementBox {
3385            self.tab_detail.set(detail);
3386            Empty::new().boxed()
3387        }
3388
3389        fn project_path(&self, _: &AppContext) -> Option<ProjectPath> {
3390            self.project_path.clone()
3391        }
3392
3393        fn project_entry_ids(&self, _: &AppContext) -> SmallVec<[ProjectEntryId; 3]> {
3394            self.project_entry_ids.iter().copied().collect()
3395        }
3396
3397        fn is_singleton(&self, _: &AppContext) -> bool {
3398            self.is_singleton
3399        }
3400
3401        fn set_nav_history(&mut self, history: ItemNavHistory, _: &mut ViewContext<Self>) {
3402            self.nav_history = Some(history);
3403        }
3404
3405        fn navigate(&mut self, state: Box<dyn Any>, _: &mut ViewContext<Self>) -> bool {
3406            let state = *state.downcast::<String>().unwrap_or_default();
3407            if state != self.state {
3408                self.state = state;
3409                true
3410            } else {
3411                false
3412            }
3413        }
3414
3415        fn deactivated(&mut self, cx: &mut ViewContext<Self>) {
3416            self.push_to_nav_history(cx);
3417        }
3418
3419        fn clone_on_split(&self, _: &mut ViewContext<Self>) -> Option<Self>
3420        where
3421            Self: Sized,
3422        {
3423            Some(self.clone())
3424        }
3425
3426        fn is_dirty(&self, _: &AppContext) -> bool {
3427            self.is_dirty
3428        }
3429
3430        fn has_conflict(&self, _: &AppContext) -> bool {
3431            self.has_conflict
3432        }
3433
3434        fn can_save(&self, _: &AppContext) -> bool {
3435            !self.project_entry_ids.is_empty()
3436        }
3437
3438        fn save(
3439            &mut self,
3440            _: ModelHandle<Project>,
3441            _: &mut ViewContext<Self>,
3442        ) -> Task<anyhow::Result<()>> {
3443            self.save_count += 1;
3444            self.is_dirty = false;
3445            Task::ready(Ok(()))
3446        }
3447
3448        fn save_as(
3449            &mut self,
3450            _: ModelHandle<Project>,
3451            _: std::path::PathBuf,
3452            _: &mut ViewContext<Self>,
3453        ) -> Task<anyhow::Result<()>> {
3454            self.save_as_count += 1;
3455            self.is_dirty = false;
3456            Task::ready(Ok(()))
3457        }
3458
3459        fn reload(
3460            &mut self,
3461            _: ModelHandle<Project>,
3462            _: &mut ViewContext<Self>,
3463        ) -> Task<anyhow::Result<()>> {
3464            self.reload_count += 1;
3465            self.is_dirty = false;
3466            Task::ready(Ok(()))
3467        }
3468
3469        fn to_item_events(_: &Self::Event) -> Vec<ItemEvent> {
3470            vec![ItemEvent::UpdateTab, ItemEvent::Edit]
3471        }
3472    }
3473
3474    impl SidebarItem for TestItem {}
3475}