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