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