workspace.rs

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