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