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(ModelHandle<Project>, AnyModelHandle, &mut ViewContext<Pane>) -> 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>(), |project, model, cx| {
 223            let item = model.downcast::<I::Item>().unwrap();
 224            Box::new(cx.add_view(|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 ViewContext<Pane>) -> 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        cx.as_mut().spawn(|mut cx| async move {
1484            let (project_entry_id, project_item) = project_item.await?;
1485            let build_item = cx.update(|cx| {
1486                cx.default_global::<ProjectItemBuilders>()
1487                    .get(&project_item.model_type())
1488                    .ok_or_else(|| anyhow!("no item builder for project item"))
1489                    .cloned()
1490            })?;
1491            let build_item =
1492                move |cx: &mut ViewContext<Pane>| build_item(project, project_item, cx);
1493            Ok((project_entry_id, build_item))
1494        })
1495    }
1496
1497    pub fn open_project_item<T>(
1498        &mut self,
1499        project_item: ModelHandle<T::Item>,
1500        cx: &mut ViewContext<Self>,
1501    ) -> ViewHandle<T>
1502    where
1503        T: ProjectItem,
1504    {
1505        use project::Item as _;
1506
1507        let entry_id = project_item.read(cx).entry_id(cx);
1508        if let Some(item) = entry_id
1509            .and_then(|entry_id| self.active_pane().read(cx).item_for_entry(entry_id, cx))
1510            .and_then(|item| item.downcast())
1511        {
1512            self.activate_item(&item, cx);
1513            return item;
1514        }
1515
1516        let item = cx.add_view(|cx| T::for_project_item(self.project().clone(), project_item, cx));
1517        self.add_item(Box::new(item.clone()), cx);
1518        item
1519    }
1520
1521    pub fn activate_item(&mut self, item: &dyn ItemHandle, cx: &mut ViewContext<Self>) -> bool {
1522        let result = self.panes.iter().find_map(|pane| {
1523            if let Some(ix) = pane.read(cx).index_for_item(item) {
1524                Some((pane.clone(), ix))
1525            } else {
1526                None
1527            }
1528        });
1529        if let Some((pane, ix)) = result {
1530            self.activate_pane(pane.clone(), cx);
1531            pane.update(cx, |pane, cx| pane.activate_item(ix, true, true, false, cx));
1532            true
1533        } else {
1534            false
1535        }
1536    }
1537
1538    fn activate_pane_at_index(&mut self, action: &ActivatePane, cx: &mut ViewContext<Self>) {
1539        let panes = self.center.panes();
1540        if let Some(pane) = panes.get(action.0).map(|p| (*p).clone()) {
1541            self.activate_pane(pane, cx);
1542        } else {
1543            self.split_pane(self.active_pane.clone(), SplitDirection::Right, cx);
1544        }
1545    }
1546
1547    pub fn activate_next_pane(&mut self, cx: &mut ViewContext<Self>) {
1548        let next_pane = {
1549            let panes = self.center.panes();
1550            let ix = panes
1551                .iter()
1552                .position(|pane| **pane == self.active_pane)
1553                .unwrap();
1554            let next_ix = (ix + 1) % panes.len();
1555            panes[next_ix].clone()
1556        };
1557        self.activate_pane(next_pane, cx);
1558    }
1559
1560    pub fn activate_previous_pane(&mut self, cx: &mut ViewContext<Self>) {
1561        let prev_pane = {
1562            let panes = self.center.panes();
1563            let ix = panes
1564                .iter()
1565                .position(|pane| **pane == self.active_pane)
1566                .unwrap();
1567            let prev_ix = if ix == 0 { panes.len() - 1 } else { ix - 1 };
1568            panes[prev_ix].clone()
1569        };
1570        self.activate_pane(prev_pane, cx);
1571    }
1572
1573    fn activate_pane(&mut self, pane: ViewHandle<Pane>, cx: &mut ViewContext<Self>) {
1574        if self.active_pane != pane {
1575            self.active_pane
1576                .update(cx, |pane, cx| pane.set_active(false, cx));
1577            self.active_pane = pane.clone();
1578            self.active_pane
1579                .update(cx, |pane, cx| pane.set_active(true, cx));
1580            self.status_bar.update(cx, |status_bar, cx| {
1581                status_bar.set_active_pane(&self.active_pane, cx);
1582            });
1583            self.active_item_path_changed(cx);
1584            cx.focus(&self.active_pane);
1585            cx.notify();
1586        }
1587
1588        self.update_followers(
1589            proto::update_followers::Variant::UpdateActiveView(proto::UpdateActiveView {
1590                id: self.active_item(cx).map(|item| item.id() as u64),
1591                leader_id: self.leader_for_pane(&pane).map(|id| id.0),
1592            }),
1593            cx,
1594        );
1595    }
1596
1597    fn handle_pane_event(
1598        &mut self,
1599        pane_id: usize,
1600        event: &pane::Event,
1601        cx: &mut ViewContext<Self>,
1602    ) {
1603        if let Some(pane) = self.pane(pane_id) {
1604            match event {
1605                pane::Event::Split(direction) => {
1606                    self.split_pane(pane, *direction, cx);
1607                }
1608                pane::Event::Remove => {
1609                    self.remove_pane(pane, cx);
1610                }
1611                pane::Event::Activate => {
1612                    self.activate_pane(pane, cx);
1613                }
1614                pane::Event::ActivateItem { local } => {
1615                    if *local {
1616                        self.unfollow(&pane, cx);
1617                    }
1618                    if pane == self.active_pane {
1619                        self.active_item_path_changed(cx);
1620                    }
1621                }
1622                pane::Event::ChangeItemTitle => {
1623                    if pane == self.active_pane {
1624                        self.active_item_path_changed(cx);
1625                    }
1626                    self.update_window_edited(cx);
1627                }
1628                pane::Event::RemoveItem => {
1629                    self.update_window_edited(cx);
1630                }
1631            }
1632        } else {
1633            error!("pane {} not found", pane_id);
1634        }
1635    }
1636
1637    pub fn split_pane(
1638        &mut self,
1639        pane: ViewHandle<Pane>,
1640        direction: SplitDirection,
1641        cx: &mut ViewContext<Self>,
1642    ) -> Option<ViewHandle<Pane>> {
1643        pane.read(cx).active_item().map(|item| {
1644            let new_pane = self.add_pane(cx);
1645            self.activate_pane(new_pane.clone(), cx);
1646            if let Some(clone) = item.clone_on_split(cx.as_mut()) {
1647                Pane::add_item(self, new_pane.clone(), clone, true, true, cx);
1648            }
1649            self.center.split(&pane, &new_pane, direction).unwrap();
1650            cx.notify();
1651            new_pane
1652        })
1653    }
1654
1655    fn remove_pane(&mut self, pane: ViewHandle<Pane>, cx: &mut ViewContext<Self>) {
1656        if self.center.remove(&pane).unwrap() {
1657            self.panes.retain(|p| p != &pane);
1658            self.activate_pane(self.panes.last().unwrap().clone(), cx);
1659            self.unfollow(&pane, cx);
1660            self.last_leaders_by_pane.remove(&pane.downgrade());
1661            cx.notify();
1662        } else {
1663            self.active_item_path_changed(cx);
1664        }
1665    }
1666
1667    pub fn panes(&self) -> &[ViewHandle<Pane>] {
1668        &self.panes
1669    }
1670
1671    fn pane(&self, pane_id: usize) -> Option<ViewHandle<Pane>> {
1672        self.panes.iter().find(|pane| pane.id() == pane_id).cloned()
1673    }
1674
1675    pub fn active_pane(&self) -> &ViewHandle<Pane> {
1676        &self.active_pane
1677    }
1678
1679    fn project_remote_id_changed(&mut self, remote_id: Option<u64>, cx: &mut ViewContext<Self>) {
1680        if let Some(remote_id) = remote_id {
1681            self.remote_entity_subscription =
1682                Some(self.client.add_view_for_remote_entity(remote_id, cx));
1683        } else {
1684            self.remote_entity_subscription.take();
1685        }
1686    }
1687
1688    fn collaborator_left(&mut self, peer_id: PeerId, cx: &mut ViewContext<Self>) {
1689        self.leader_state.followers.remove(&peer_id);
1690        if let Some(states_by_pane) = self.follower_states_by_leader.remove(&peer_id) {
1691            for state in states_by_pane.into_values() {
1692                for item in state.items_by_leader_view_id.into_values() {
1693                    if let FollowerItem::Loaded(item) = item {
1694                        item.set_leader_replica_id(None, cx);
1695                    }
1696                }
1697            }
1698        }
1699        cx.notify();
1700    }
1701
1702    pub fn toggle_follow(
1703        &mut self,
1704        ToggleFollow(leader_id): &ToggleFollow,
1705        cx: &mut ViewContext<Self>,
1706    ) -> Option<Task<Result<()>>> {
1707        let leader_id = *leader_id;
1708        let pane = self.active_pane().clone();
1709
1710        if let Some(prev_leader_id) = self.unfollow(&pane, cx) {
1711            if leader_id == prev_leader_id {
1712                return None;
1713            }
1714        }
1715
1716        self.last_leaders_by_pane
1717            .insert(pane.downgrade(), leader_id);
1718        self.follower_states_by_leader
1719            .entry(leader_id)
1720            .or_default()
1721            .insert(pane.clone(), Default::default());
1722        cx.notify();
1723
1724        let project_id = self.project.read(cx).remote_id()?;
1725        let request = self.client.request(proto::Follow {
1726            project_id,
1727            leader_id: leader_id.0,
1728        });
1729        Some(cx.spawn_weak(|this, mut cx| async move {
1730            let response = request.await?;
1731            if let Some(this) = this.upgrade(&cx) {
1732                this.update(&mut cx, |this, _| {
1733                    let state = this
1734                        .follower_states_by_leader
1735                        .get_mut(&leader_id)
1736                        .and_then(|states_by_pane| states_by_pane.get_mut(&pane))
1737                        .ok_or_else(|| anyhow!("following interrupted"))?;
1738                    state.active_view_id = response.active_view_id;
1739                    Ok::<_, anyhow::Error>(())
1740                })?;
1741                Self::add_views_from_leader(this, leader_id, vec![pane], response.views, &mut cx)
1742                    .await?;
1743            }
1744            Ok(())
1745        }))
1746    }
1747
1748    pub fn follow_next_collaborator(
1749        &mut self,
1750        _: &FollowNextCollaborator,
1751        cx: &mut ViewContext<Self>,
1752    ) -> Option<Task<Result<()>>> {
1753        let collaborators = self.project.read(cx).collaborators();
1754        let next_leader_id = if let Some(leader_id) = self.leader_for_pane(&self.active_pane) {
1755            let mut collaborators = collaborators.keys().copied();
1756            while let Some(peer_id) = collaborators.next() {
1757                if peer_id == leader_id {
1758                    break;
1759                }
1760            }
1761            collaborators.next()
1762        } else if let Some(last_leader_id) =
1763            self.last_leaders_by_pane.get(&self.active_pane.downgrade())
1764        {
1765            if collaborators.contains_key(last_leader_id) {
1766                Some(*last_leader_id)
1767            } else {
1768                None
1769            }
1770        } else {
1771            None
1772        };
1773
1774        next_leader_id
1775            .or_else(|| collaborators.keys().copied().next())
1776            .and_then(|leader_id| self.toggle_follow(&ToggleFollow(leader_id), cx))
1777    }
1778
1779    pub fn unfollow(
1780        &mut self,
1781        pane: &ViewHandle<Pane>,
1782        cx: &mut ViewContext<Self>,
1783    ) -> Option<PeerId> {
1784        for (leader_id, states_by_pane) in &mut self.follower_states_by_leader {
1785            let leader_id = *leader_id;
1786            if let Some(state) = states_by_pane.remove(&pane) {
1787                for (_, item) in state.items_by_leader_view_id {
1788                    if let FollowerItem::Loaded(item) = item {
1789                        item.set_leader_replica_id(None, cx);
1790                    }
1791                }
1792
1793                if states_by_pane.is_empty() {
1794                    self.follower_states_by_leader.remove(&leader_id);
1795                    if let Some(project_id) = self.project.read(cx).remote_id() {
1796                        self.client
1797                            .send(proto::Unfollow {
1798                                project_id,
1799                                leader_id: leader_id.0,
1800                            })
1801                            .log_err();
1802                    }
1803                }
1804
1805                cx.notify();
1806                return Some(leader_id);
1807            }
1808        }
1809        None
1810    }
1811
1812    fn render_connection_status(&self, cx: &mut RenderContext<Self>) -> Option<ElementBox> {
1813        let theme = &cx.global::<Settings>().theme;
1814        match &*self.client.status().borrow() {
1815            client::Status::ConnectionError
1816            | client::Status::ConnectionLost
1817            | client::Status::Reauthenticating { .. }
1818            | client::Status::Reconnecting { .. }
1819            | client::Status::ReconnectionError { .. } => Some(
1820                Container::new(
1821                    Align::new(
1822                        ConstrainedBox::new(
1823                            Svg::new("icons/cloud_slash_12.svg")
1824                                .with_color(theme.workspace.titlebar.offline_icon.color)
1825                                .boxed(),
1826                        )
1827                        .with_width(theme.workspace.titlebar.offline_icon.width)
1828                        .boxed(),
1829                    )
1830                    .boxed(),
1831                )
1832                .with_style(theme.workspace.titlebar.offline_icon.container)
1833                .boxed(),
1834            ),
1835            client::Status::UpgradeRequired => Some(
1836                Label::new(
1837                    "Please update Zed to collaborate".to_string(),
1838                    theme.workspace.titlebar.outdated_warning.text.clone(),
1839                )
1840                .contained()
1841                .with_style(theme.workspace.titlebar.outdated_warning.container)
1842                .aligned()
1843                .boxed(),
1844            ),
1845            _ => None,
1846        }
1847    }
1848
1849    fn render_titlebar(&self, theme: &Theme, cx: &mut RenderContext<Self>) -> ElementBox {
1850        let project = &self.project.read(cx);
1851        let replica_id = project.replica_id();
1852        let mut worktree_root_names = String::new();
1853        for (i, name) in project.worktree_root_names(cx).enumerate() {
1854            if i > 0 {
1855                worktree_root_names.push_str(", ");
1856            }
1857            worktree_root_names.push_str(name);
1858        }
1859
1860        // TODO: There should be a better system in place for this
1861        // (https://github.com/zed-industries/zed/issues/1290)
1862        let is_fullscreen = cx.window_is_fullscreen(cx.window_id());
1863        let container_theme = if is_fullscreen {
1864            let mut container_theme = theme.workspace.titlebar.container;
1865            container_theme.padding.left = container_theme.padding.right;
1866            container_theme
1867        } else {
1868            theme.workspace.titlebar.container
1869        };
1870
1871        ConstrainedBox::new(
1872            Container::new(
1873                Stack::new()
1874                    .with_child(
1875                        Label::new(worktree_root_names, theme.workspace.titlebar.title.clone())
1876                            .aligned()
1877                            .left()
1878                            .boxed(),
1879                    )
1880                    .with_child(
1881                        Align::new(
1882                            Flex::row()
1883                                .with_children(self.render_collaborators(theme, cx))
1884                                .with_children(self.render_current_user(
1885                                    self.user_store.read(cx).current_user().as_ref(),
1886                                    replica_id,
1887                                    theme,
1888                                    cx,
1889                                ))
1890                                .with_children(self.render_connection_status(cx))
1891                                .boxed(),
1892                        )
1893                        .right()
1894                        .boxed(),
1895                    )
1896                    .boxed(),
1897            )
1898            .with_style(container_theme)
1899            .boxed(),
1900        )
1901        .with_height(theme.workspace.titlebar.height)
1902        .named("titlebar")
1903    }
1904
1905    fn active_item_path_changed(&mut self, cx: &mut ViewContext<Self>) {
1906        let active_entry = self.active_project_path(cx);
1907        self.project
1908            .update(cx, |project, cx| project.set_active_path(active_entry, cx));
1909        self.update_window_title(cx);
1910    }
1911
1912    fn update_window_title(&mut self, cx: &mut ViewContext<Self>) {
1913        let mut title = String::new();
1914        let project = self.project().read(cx);
1915        if let Some(path) = self.active_item(cx).and_then(|item| item.project_path(cx)) {
1916            let filename = path
1917                .path
1918                .file_name()
1919                .map(|s| s.to_string_lossy())
1920                .or_else(|| {
1921                    Some(Cow::Borrowed(
1922                        project
1923                            .worktree_for_id(path.worktree_id, cx)?
1924                            .read(cx)
1925                            .root_name(),
1926                    ))
1927                });
1928            if let Some(filename) = filename {
1929                title.push_str(filename.as_ref());
1930                title.push_str("");
1931            }
1932        }
1933        for (i, name) in project.worktree_root_names(cx).enumerate() {
1934            if i > 0 {
1935                title.push_str(", ");
1936            }
1937            title.push_str(name);
1938        }
1939        if title.is_empty() {
1940            title = "empty project".to_string();
1941        }
1942        cx.set_window_title(&title);
1943    }
1944
1945    fn update_window_edited(&mut self, cx: &mut ViewContext<Self>) {
1946        let is_edited = !self.project.read(cx).is_read_only()
1947            && self
1948                .items(cx)
1949                .any(|item| item.has_conflict(cx) || item.is_dirty(cx));
1950        if is_edited != self.window_edited {
1951            self.window_edited = is_edited;
1952            cx.set_window_edited(self.window_edited)
1953        }
1954    }
1955
1956    fn render_collaborators(&self, theme: &Theme, cx: &mut RenderContext<Self>) -> Vec<ElementBox> {
1957        let mut collaborators = self
1958            .project
1959            .read(cx)
1960            .collaborators()
1961            .values()
1962            .cloned()
1963            .collect::<Vec<_>>();
1964        collaborators.sort_unstable_by_key(|collaborator| collaborator.replica_id);
1965        collaborators
1966            .into_iter()
1967            .filter_map(|collaborator| {
1968                Some(self.render_avatar(
1969                    collaborator.user.avatar.clone()?,
1970                    collaborator.replica_id,
1971                    Some((collaborator.peer_id, &collaborator.user.github_login)),
1972                    theme,
1973                    cx,
1974                ))
1975            })
1976            .collect()
1977    }
1978
1979    fn render_current_user(
1980        &self,
1981        user: Option<&Arc<User>>,
1982        replica_id: ReplicaId,
1983        theme: &Theme,
1984        cx: &mut RenderContext<Self>,
1985    ) -> Option<ElementBox> {
1986        let status = *self.client.status().borrow();
1987        if let Some(avatar) = user.and_then(|user| user.avatar.clone()) {
1988            Some(self.render_avatar(avatar, replica_id, None, theme, cx))
1989        } else if matches!(status, client::Status::UpgradeRequired) {
1990            None
1991        } else {
1992            Some(
1993                MouseEventHandler::new::<Authenticate, _, _>(0, cx, |state, _| {
1994                    let style = theme
1995                        .workspace
1996                        .titlebar
1997                        .sign_in_prompt
1998                        .style_for(state, false);
1999                    Label::new("Sign in".to_string(), style.text.clone())
2000                        .contained()
2001                        .with_style(style.container)
2002                        .boxed()
2003                })
2004                .on_click(MouseButton::Left, |_, cx| cx.dispatch_action(Authenticate))
2005                .with_cursor_style(CursorStyle::PointingHand)
2006                .aligned()
2007                .boxed(),
2008            )
2009        }
2010    }
2011
2012    fn render_avatar(
2013        &self,
2014        avatar: Arc<ImageData>,
2015        replica_id: ReplicaId,
2016        peer: Option<(PeerId, &str)>,
2017        theme: &Theme,
2018        cx: &mut RenderContext<Self>,
2019    ) -> ElementBox {
2020        let replica_color = theme.editor.replica_selection_style(replica_id).cursor;
2021        let is_followed = peer.map_or(false, |(peer_id, _)| {
2022            self.follower_states_by_leader.contains_key(&peer_id)
2023        });
2024        let mut avatar_style = theme.workspace.titlebar.avatar;
2025        if is_followed {
2026            avatar_style.border = Border::all(1.0, replica_color);
2027        }
2028        let content = Stack::new()
2029            .with_child(
2030                Image::new(avatar)
2031                    .with_style(avatar_style)
2032                    .constrained()
2033                    .with_width(theme.workspace.titlebar.avatar_width)
2034                    .aligned()
2035                    .boxed(),
2036            )
2037            .with_child(
2038                AvatarRibbon::new(replica_color)
2039                    .constrained()
2040                    .with_width(theme.workspace.titlebar.avatar_ribbon.width)
2041                    .with_height(theme.workspace.titlebar.avatar_ribbon.height)
2042                    .aligned()
2043                    .bottom()
2044                    .boxed(),
2045            )
2046            .constrained()
2047            .with_width(theme.workspace.titlebar.avatar_width)
2048            .contained()
2049            .with_margin_left(theme.workspace.titlebar.avatar_margin)
2050            .boxed();
2051
2052        if let Some((peer_id, peer_github_login)) = peer {
2053            MouseEventHandler::new::<ToggleFollow, _, _>(replica_id.into(), cx, move |_, _| content)
2054                .with_cursor_style(CursorStyle::PointingHand)
2055                .on_click(MouseButton::Left, move |_, cx| {
2056                    cx.dispatch_action(ToggleFollow(peer_id))
2057                })
2058                .with_tooltip::<ToggleFollow, _>(
2059                    peer_id.0 as usize,
2060                    if is_followed {
2061                        format!("Unfollow {}", peer_github_login)
2062                    } else {
2063                        format!("Follow {}", peer_github_login)
2064                    },
2065                    Some(Box::new(FollowNextCollaborator)),
2066                    theme.tooltip.clone(),
2067                    cx,
2068                )
2069                .boxed()
2070        } else {
2071            content
2072        }
2073    }
2074
2075    fn render_disconnected_overlay(&self, cx: &AppContext) -> Option<ElementBox> {
2076        if self.project.read(cx).is_read_only() {
2077            let theme = &cx.global::<Settings>().theme;
2078            Some(
2079                EventHandler::new(
2080                    Label::new(
2081                        "Your connection to the remote project has been lost.".to_string(),
2082                        theme.workspace.disconnected_overlay.text.clone(),
2083                    )
2084                    .aligned()
2085                    .contained()
2086                    .with_style(theme.workspace.disconnected_overlay.container)
2087                    .boxed(),
2088                )
2089                .capture_all::<Self>(0)
2090                .boxed(),
2091            )
2092        } else {
2093            None
2094        }
2095    }
2096
2097    fn render_notifications(&self, theme: &theme::Workspace) -> Option<ElementBox> {
2098        if self.notifications.is_empty() {
2099            None
2100        } else {
2101            Some(
2102                Flex::column()
2103                    .with_children(self.notifications.iter().map(|(_, _, notification)| {
2104                        ChildView::new(notification.as_ref())
2105                            .contained()
2106                            .with_style(theme.notification)
2107                            .boxed()
2108                    }))
2109                    .constrained()
2110                    .with_width(theme.notifications.width)
2111                    .contained()
2112                    .with_style(theme.notifications.container)
2113                    .aligned()
2114                    .bottom()
2115                    .right()
2116                    .boxed(),
2117            )
2118        }
2119    }
2120
2121    // RPC handlers
2122
2123    async fn handle_follow(
2124        this: ViewHandle<Self>,
2125        envelope: TypedEnvelope<proto::Follow>,
2126        _: Arc<Client>,
2127        mut cx: AsyncAppContext,
2128    ) -> Result<proto::FollowResponse> {
2129        this.update(&mut cx, |this, cx| {
2130            this.leader_state
2131                .followers
2132                .insert(envelope.original_sender_id()?);
2133
2134            let active_view_id = this
2135                .active_item(cx)
2136                .and_then(|i| i.to_followable_item_handle(cx))
2137                .map(|i| i.id() as u64);
2138            Ok(proto::FollowResponse {
2139                active_view_id,
2140                views: this
2141                    .panes()
2142                    .iter()
2143                    .flat_map(|pane| {
2144                        let leader_id = this.leader_for_pane(pane).map(|id| id.0);
2145                        pane.read(cx).items().filter_map({
2146                            let cx = &cx;
2147                            move |item| {
2148                                let id = item.id() as u64;
2149                                let item = item.to_followable_item_handle(cx)?;
2150                                let variant = item.to_state_proto(cx)?;
2151                                Some(proto::View {
2152                                    id,
2153                                    leader_id,
2154                                    variant: Some(variant),
2155                                })
2156                            }
2157                        })
2158                    })
2159                    .collect(),
2160            })
2161        })
2162    }
2163
2164    async fn handle_unfollow(
2165        this: ViewHandle<Self>,
2166        envelope: TypedEnvelope<proto::Unfollow>,
2167        _: Arc<Client>,
2168        mut cx: AsyncAppContext,
2169    ) -> Result<()> {
2170        this.update(&mut cx, |this, _| {
2171            this.leader_state
2172                .followers
2173                .remove(&envelope.original_sender_id()?);
2174            Ok(())
2175        })
2176    }
2177
2178    async fn handle_update_followers(
2179        this: ViewHandle<Self>,
2180        envelope: TypedEnvelope<proto::UpdateFollowers>,
2181        _: Arc<Client>,
2182        mut cx: AsyncAppContext,
2183    ) -> Result<()> {
2184        let leader_id = envelope.original_sender_id()?;
2185        match envelope
2186            .payload
2187            .variant
2188            .ok_or_else(|| anyhow!("invalid update"))?
2189        {
2190            proto::update_followers::Variant::UpdateActiveView(update_active_view) => {
2191                this.update(&mut cx, |this, cx| {
2192                    this.update_leader_state(leader_id, cx, |state, _| {
2193                        state.active_view_id = update_active_view.id;
2194                    });
2195                    Ok::<_, anyhow::Error>(())
2196                })
2197            }
2198            proto::update_followers::Variant::UpdateView(update_view) => {
2199                this.update(&mut cx, |this, cx| {
2200                    let variant = update_view
2201                        .variant
2202                        .ok_or_else(|| anyhow!("missing update view variant"))?;
2203                    this.update_leader_state(leader_id, cx, |state, cx| {
2204                        let variant = variant.clone();
2205                        match state
2206                            .items_by_leader_view_id
2207                            .entry(update_view.id)
2208                            .or_insert(FollowerItem::Loading(Vec::new()))
2209                        {
2210                            FollowerItem::Loaded(item) => {
2211                                item.apply_update_proto(variant, cx).log_err();
2212                            }
2213                            FollowerItem::Loading(updates) => updates.push(variant),
2214                        }
2215                    });
2216                    Ok(())
2217                })
2218            }
2219            proto::update_followers::Variant::CreateView(view) => {
2220                let panes = this.read_with(&cx, |this, _| {
2221                    this.follower_states_by_leader
2222                        .get(&leader_id)
2223                        .into_iter()
2224                        .flat_map(|states_by_pane| states_by_pane.keys())
2225                        .cloned()
2226                        .collect()
2227                });
2228                Self::add_views_from_leader(this.clone(), leader_id, panes, vec![view], &mut cx)
2229                    .await?;
2230                Ok(())
2231            }
2232        }
2233        .log_err();
2234
2235        Ok(())
2236    }
2237
2238    async fn add_views_from_leader(
2239        this: ViewHandle<Self>,
2240        leader_id: PeerId,
2241        panes: Vec<ViewHandle<Pane>>,
2242        views: Vec<proto::View>,
2243        cx: &mut AsyncAppContext,
2244    ) -> Result<()> {
2245        let project = this.read_with(cx, |this, _| this.project.clone());
2246        let replica_id = project
2247            .read_with(cx, |project, _| {
2248                project
2249                    .collaborators()
2250                    .get(&leader_id)
2251                    .map(|c| c.replica_id)
2252            })
2253            .ok_or_else(|| anyhow!("no such collaborator {}", leader_id))?;
2254
2255        let item_builders = cx.update(|cx| {
2256            cx.default_global::<FollowableItemBuilders>()
2257                .values()
2258                .map(|b| b.0)
2259                .collect::<Vec<_>>()
2260                .clone()
2261        });
2262
2263        let mut item_tasks_by_pane = HashMap::default();
2264        for pane in panes {
2265            let mut item_tasks = Vec::new();
2266            let mut leader_view_ids = Vec::new();
2267            for view in &views {
2268                let mut variant = view.variant.clone();
2269                if variant.is_none() {
2270                    Err(anyhow!("missing variant"))?;
2271                }
2272                for build_item in &item_builders {
2273                    let task =
2274                        cx.update(|cx| build_item(pane.clone(), project.clone(), &mut variant, cx));
2275                    if let Some(task) = task {
2276                        item_tasks.push(task);
2277                        leader_view_ids.push(view.id);
2278                        break;
2279                    } else {
2280                        assert!(variant.is_some());
2281                    }
2282                }
2283            }
2284
2285            item_tasks_by_pane.insert(pane, (item_tasks, leader_view_ids));
2286        }
2287
2288        for (pane, (item_tasks, leader_view_ids)) in item_tasks_by_pane {
2289            let items = futures::future::try_join_all(item_tasks).await?;
2290            this.update(cx, |this, cx| {
2291                let state = this
2292                    .follower_states_by_leader
2293                    .get_mut(&leader_id)?
2294                    .get_mut(&pane)?;
2295
2296                for (id, item) in leader_view_ids.into_iter().zip(items) {
2297                    item.set_leader_replica_id(Some(replica_id), cx);
2298                    match state.items_by_leader_view_id.entry(id) {
2299                        hash_map::Entry::Occupied(e) => {
2300                            let e = e.into_mut();
2301                            if let FollowerItem::Loading(updates) = e {
2302                                for update in updates.drain(..) {
2303                                    item.apply_update_proto(update, cx)
2304                                        .context("failed to apply view update")
2305                                        .log_err();
2306                                }
2307                            }
2308                            *e = FollowerItem::Loaded(item);
2309                        }
2310                        hash_map::Entry::Vacant(e) => {
2311                            e.insert(FollowerItem::Loaded(item));
2312                        }
2313                    }
2314                }
2315
2316                Some(())
2317            });
2318        }
2319        this.update(cx, |this, cx| this.leader_updated(leader_id, cx));
2320
2321        Ok(())
2322    }
2323
2324    fn update_followers(
2325        &self,
2326        update: proto::update_followers::Variant,
2327        cx: &AppContext,
2328    ) -> Option<()> {
2329        let project_id = self.project.read(cx).remote_id()?;
2330        if !self.leader_state.followers.is_empty() {
2331            self.client
2332                .send(proto::UpdateFollowers {
2333                    project_id,
2334                    follower_ids: self.leader_state.followers.iter().map(|f| f.0).collect(),
2335                    variant: Some(update),
2336                })
2337                .log_err();
2338        }
2339        None
2340    }
2341
2342    pub fn leader_for_pane(&self, pane: &ViewHandle<Pane>) -> Option<PeerId> {
2343        self.follower_states_by_leader
2344            .iter()
2345            .find_map(|(leader_id, state)| {
2346                if state.contains_key(pane) {
2347                    Some(*leader_id)
2348                } else {
2349                    None
2350                }
2351            })
2352    }
2353
2354    fn update_leader_state(
2355        &mut self,
2356        leader_id: PeerId,
2357        cx: &mut ViewContext<Self>,
2358        mut update_fn: impl FnMut(&mut FollowerState, &mut ViewContext<Self>),
2359    ) {
2360        for (_, state) in self
2361            .follower_states_by_leader
2362            .get_mut(&leader_id)
2363            .into_iter()
2364            .flatten()
2365        {
2366            update_fn(state, cx);
2367        }
2368        self.leader_updated(leader_id, cx);
2369    }
2370
2371    fn leader_updated(&mut self, leader_id: PeerId, cx: &mut ViewContext<Self>) -> Option<()> {
2372        let mut items_to_add = Vec::new();
2373        for (pane, state) in self.follower_states_by_leader.get(&leader_id)? {
2374            if let Some(active_item) = state
2375                .active_view_id
2376                .and_then(|id| state.items_by_leader_view_id.get(&id))
2377            {
2378                if let FollowerItem::Loaded(item) = active_item {
2379                    items_to_add.push((pane.clone(), item.boxed_clone()));
2380                }
2381            }
2382        }
2383
2384        for (pane, item) in items_to_add {
2385            Pane::add_item(self, pane.clone(), item.boxed_clone(), false, false, cx);
2386            if pane == self.active_pane {
2387                pane.update(cx, |pane, cx| pane.focus_active_item(cx));
2388            }
2389            cx.notify();
2390        }
2391        None
2392    }
2393
2394    fn on_window_activation_changed(&mut self, active: bool, cx: &mut ViewContext<Self>) {
2395        if !active
2396            && matches!(
2397                cx.global::<Settings>().autosave,
2398                Autosave::OnWindowChange | Autosave::OnFocusChange
2399            )
2400        {
2401            for pane in &self.panes {
2402                pane.update(cx, |pane, cx| {
2403                    for item in pane.items() {
2404                        Pane::autosave_item(item.as_ref(), self.project.clone(), cx)
2405                            .detach_and_log_err(cx);
2406                    }
2407                });
2408            }
2409        }
2410    }
2411}
2412
2413impl Entity for Workspace {
2414    type Event = Event;
2415}
2416
2417impl View for Workspace {
2418    fn ui_name() -> &'static str {
2419        "Workspace"
2420    }
2421
2422    fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
2423        let theme = cx.global::<Settings>().theme.clone();
2424        Stack::new()
2425            .with_child(
2426                Flex::column()
2427                    .with_child(self.render_titlebar(&theme, cx))
2428                    .with_child(
2429                        Stack::new()
2430                            .with_child({
2431                                Flex::row()
2432                                    .with_children(
2433                                        if self.left_sidebar.read(cx).active_item().is_some() {
2434                                            Some(
2435                                                ChildView::new(&self.left_sidebar)
2436                                                    .flex(0.8, false)
2437                                                    .boxed(),
2438                                            )
2439                                        } else {
2440                                            None
2441                                        },
2442                                    )
2443                                    .with_child(
2444                                        FlexItem::new(self.center.render(
2445                                            &theme,
2446                                            &self.follower_states_by_leader,
2447                                            self.project.read(cx).collaborators(),
2448                                        ))
2449                                        .flex(1., true)
2450                                        .boxed(),
2451                                    )
2452                                    .with_children(
2453                                        if self.right_sidebar.read(cx).active_item().is_some() {
2454                                            Some(
2455                                                ChildView::new(&self.right_sidebar)
2456                                                    .flex(0.8, false)
2457                                                    .boxed(),
2458                                            )
2459                                        } else {
2460                                            None
2461                                        },
2462                                    )
2463                                    .boxed()
2464                            })
2465                            .with_children(self.modal.as_ref().map(|m| {
2466                                ChildView::new(m)
2467                                    .contained()
2468                                    .with_style(theme.workspace.modal)
2469                                    .aligned()
2470                                    .top()
2471                                    .boxed()
2472                            }))
2473                            .with_children(self.render_notifications(&theme.workspace))
2474                            .flex(1.0, true)
2475                            .boxed(),
2476                    )
2477                    .with_child(ChildView::new(&self.status_bar).boxed())
2478                    .contained()
2479                    .with_background_color(theme.workspace.background)
2480                    .boxed(),
2481            )
2482            .with_children(self.render_disconnected_overlay(cx))
2483            .named("workspace")
2484    }
2485
2486    fn on_focus(&mut self, cx: &mut ViewContext<Self>) {
2487        cx.focus(&self.active_pane);
2488    }
2489}
2490
2491pub trait WorkspaceHandle {
2492    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath>;
2493}
2494
2495impl WorkspaceHandle for ViewHandle<Workspace> {
2496    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath> {
2497        self.read(cx)
2498            .worktrees(cx)
2499            .flat_map(|worktree| {
2500                let worktree_id = worktree.read(cx).id();
2501                worktree.read(cx).files(true, 0).map(move |f| ProjectPath {
2502                    worktree_id,
2503                    path: f.path.clone(),
2504                })
2505            })
2506            .collect::<Vec<_>>()
2507    }
2508}
2509
2510pub struct AvatarRibbon {
2511    color: Color,
2512}
2513
2514impl AvatarRibbon {
2515    pub fn new(color: Color) -> AvatarRibbon {
2516        AvatarRibbon { color }
2517    }
2518}
2519
2520impl Element for AvatarRibbon {
2521    type LayoutState = ();
2522
2523    type PaintState = ();
2524
2525    fn layout(
2526        &mut self,
2527        constraint: gpui::SizeConstraint,
2528        _: &mut gpui::LayoutContext,
2529    ) -> (gpui::geometry::vector::Vector2F, Self::LayoutState) {
2530        (constraint.max, ())
2531    }
2532
2533    fn paint(
2534        &mut self,
2535        bounds: gpui::geometry::rect::RectF,
2536        _: gpui::geometry::rect::RectF,
2537        _: &mut Self::LayoutState,
2538        cx: &mut gpui::PaintContext,
2539    ) -> Self::PaintState {
2540        let mut path = PathBuilder::new();
2541        path.reset(bounds.lower_left());
2542        path.curve_to(
2543            bounds.origin() + vec2f(bounds.height(), 0.),
2544            bounds.origin(),
2545        );
2546        path.line_to(bounds.upper_right() - vec2f(bounds.height(), 0.));
2547        path.curve_to(bounds.lower_right(), bounds.upper_right());
2548        path.line_to(bounds.lower_left());
2549        cx.scene.push_path(path.build(self.color, None));
2550    }
2551
2552    fn dispatch_event(
2553        &mut self,
2554        _: &gpui::Event,
2555        _: RectF,
2556        _: RectF,
2557        _: &mut Self::LayoutState,
2558        _: &mut Self::PaintState,
2559        _: &mut gpui::EventContext,
2560    ) -> bool {
2561        false
2562    }
2563
2564    fn rect_for_text_range(
2565        &self,
2566        _: Range<usize>,
2567        _: RectF,
2568        _: RectF,
2569        _: &Self::LayoutState,
2570        _: &Self::PaintState,
2571        _: &gpui::MeasurementContext,
2572    ) -> Option<RectF> {
2573        None
2574    }
2575
2576    fn debug(
2577        &self,
2578        bounds: gpui::geometry::rect::RectF,
2579        _: &Self::LayoutState,
2580        _: &Self::PaintState,
2581        _: &gpui::DebugContext,
2582    ) -> gpui::json::Value {
2583        json::json!({
2584            "type": "AvatarRibbon",
2585            "bounds": bounds.to_json(),
2586            "color": self.color.to_json(),
2587        })
2588    }
2589}
2590
2591impl std::fmt::Debug for OpenPaths {
2592    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2593        f.debug_struct("OpenPaths")
2594            .field("paths", &self.paths)
2595            .finish()
2596    }
2597}
2598
2599fn open(_: &Open, cx: &mut MutableAppContext) {
2600    let mut paths = cx.prompt_for_paths(PathPromptOptions {
2601        files: true,
2602        directories: true,
2603        multiple: true,
2604    });
2605    cx.spawn(|mut cx| async move {
2606        if let Some(paths) = paths.recv().await.flatten() {
2607            cx.update(|cx| cx.dispatch_global_action(OpenPaths { paths }));
2608        }
2609    })
2610    .detach();
2611}
2612
2613pub struct WorkspaceCreated(WeakViewHandle<Workspace>);
2614
2615pub fn activate_workspace_for_project(
2616    cx: &mut MutableAppContext,
2617    predicate: impl Fn(&mut Project, &mut ModelContext<Project>) -> bool,
2618) -> Option<ViewHandle<Workspace>> {
2619    for window_id in cx.window_ids().collect::<Vec<_>>() {
2620        if let Some(workspace_handle) = cx.root_view::<Workspace>(window_id) {
2621            let project = workspace_handle.read(cx).project.clone();
2622            if project.update(cx, &predicate) {
2623                cx.activate_window(window_id);
2624                return Some(workspace_handle);
2625            }
2626        }
2627    }
2628    None
2629}
2630
2631pub fn open_paths(
2632    abs_paths: &[PathBuf],
2633    app_state: &Arc<AppState>,
2634    cx: &mut MutableAppContext,
2635) -> Task<(
2636    ViewHandle<Workspace>,
2637    Vec<Option<Result<Box<dyn ItemHandle>, Arc<anyhow::Error>>>>,
2638)> {
2639    log::info!("open paths {:?}", abs_paths);
2640
2641    // Open paths in existing workspace if possible
2642    let existing =
2643        activate_workspace_for_project(cx, |project, cx| project.contains_paths(abs_paths, cx));
2644
2645    let app_state = app_state.clone();
2646    let abs_paths = abs_paths.to_vec();
2647    cx.spawn(|mut cx| async move {
2648        let mut new_project = None;
2649        let workspace = if let Some(existing) = existing {
2650            existing
2651        } else {
2652            let contains_directory =
2653                futures::future::join_all(abs_paths.iter().map(|path| app_state.fs.is_file(path)))
2654                    .await
2655                    .contains(&false);
2656
2657            cx.add_window((app_state.build_window_options)(), |cx| {
2658                let project = Project::local(
2659                    false,
2660                    app_state.client.clone(),
2661                    app_state.user_store.clone(),
2662                    app_state.project_store.clone(),
2663                    app_state.languages.clone(),
2664                    app_state.fs.clone(),
2665                    cx,
2666                );
2667                new_project = Some(project.clone());
2668                let mut workspace = Workspace::new(project, cx);
2669                (app_state.initialize_workspace)(&mut workspace, &app_state, cx);
2670                if contains_directory {
2671                    workspace.toggle_sidebar(Side::Left, cx);
2672                }
2673                workspace
2674            })
2675            .1
2676        };
2677
2678        let items = workspace
2679            .update(&mut cx, |workspace, cx| {
2680                workspace.open_paths(abs_paths, true, cx)
2681            })
2682            .await;
2683
2684        if let Some(project) = new_project {
2685            project
2686                .update(&mut cx, |project, cx| project.restore_state(cx))
2687                .await
2688                .log_err();
2689        }
2690
2691        (workspace, items)
2692    })
2693}
2694
2695pub fn join_project(
2696    contact: Arc<Contact>,
2697    project_index: usize,
2698    app_state: &Arc<AppState>,
2699    cx: &mut MutableAppContext,
2700) {
2701    let project_id = contact.projects[project_index].id;
2702
2703    for window_id in cx.window_ids().collect::<Vec<_>>() {
2704        if let Some(workspace) = cx.root_view::<Workspace>(window_id) {
2705            if workspace.read(cx).project().read(cx).remote_id() == Some(project_id) {
2706                cx.activate_window(window_id);
2707                return;
2708            }
2709        }
2710    }
2711
2712    cx.add_window((app_state.build_window_options)(), |cx| {
2713        WaitingRoom::new(contact, project_index, app_state.clone(), cx)
2714    });
2715}
2716
2717fn open_new(app_state: &Arc<AppState>, cx: &mut MutableAppContext) {
2718    let (window_id, workspace) = cx.add_window((app_state.build_window_options)(), |cx| {
2719        let mut workspace = Workspace::new(
2720            Project::local(
2721                false,
2722                app_state.client.clone(),
2723                app_state.user_store.clone(),
2724                app_state.project_store.clone(),
2725                app_state.languages.clone(),
2726                app_state.fs.clone(),
2727                cx,
2728            ),
2729            cx,
2730        );
2731        (app_state.initialize_workspace)(&mut workspace, app_state, cx);
2732        workspace
2733    });
2734    cx.dispatch_action_at(window_id, workspace.id(), NewFile);
2735}
2736
2737#[cfg(test)]
2738mod tests {
2739    use std::cell::Cell;
2740
2741    use super::*;
2742    use gpui::{executor::Deterministic, ModelHandle, TestAppContext, ViewContext};
2743    use project::{FakeFs, Project, ProjectEntryId};
2744    use serde_json::json;
2745
2746    #[gpui::test]
2747    async fn test_tab_disambiguation(cx: &mut TestAppContext) {
2748        cx.foreground().forbid_parking();
2749        Settings::test_async(cx);
2750
2751        let fs = FakeFs::new(cx.background());
2752        let project = Project::test(fs, [], cx).await;
2753        let (_, workspace) = cx.add_window(|cx| Workspace::new(project.clone(), cx));
2754
2755        // Adding an item with no ambiguity renders the tab without detail.
2756        let item1 = cx.add_view(&workspace, |_| {
2757            let mut item = TestItem::new();
2758            item.tab_descriptions = Some(vec!["c", "b1/c", "a/b1/c"]);
2759            item
2760        });
2761        workspace.update(cx, |workspace, cx| {
2762            workspace.add_item(Box::new(item1.clone()), cx);
2763        });
2764        item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), None));
2765
2766        // Adding an item that creates ambiguity increases the level of detail on
2767        // both tabs.
2768        let item2 = cx.add_view(&workspace, |_| {
2769            let mut item = TestItem::new();
2770            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
2771            item
2772        });
2773        workspace.update(cx, |workspace, cx| {
2774            workspace.add_item(Box::new(item2.clone()), cx);
2775        });
2776        item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
2777        item2.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
2778
2779        // Adding an item that creates ambiguity increases the level of detail only
2780        // on the ambiguous tabs. In this case, the ambiguity can't be resolved so
2781        // we stop at the highest detail available.
2782        let item3 = cx.add_view(&workspace, |_| {
2783            let mut item = TestItem::new();
2784            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
2785            item
2786        });
2787        workspace.update(cx, |workspace, cx| {
2788            workspace.add_item(Box::new(item3.clone()), cx);
2789        });
2790        item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
2791        item2.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
2792        item3.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
2793    }
2794
2795    #[gpui::test]
2796    async fn test_tracking_active_path(cx: &mut TestAppContext) {
2797        cx.foreground().forbid_parking();
2798        Settings::test_async(cx);
2799        let fs = FakeFs::new(cx.background());
2800        fs.insert_tree(
2801            "/root1",
2802            json!({
2803                "one.txt": "",
2804                "two.txt": "",
2805            }),
2806        )
2807        .await;
2808        fs.insert_tree(
2809            "/root2",
2810            json!({
2811                "three.txt": "",
2812            }),
2813        )
2814        .await;
2815
2816        let project = Project::test(fs, ["root1".as_ref()], cx).await;
2817        let (window_id, workspace) = cx.add_window(|cx| Workspace::new(project.clone(), cx));
2818        let worktree_id = project.read_with(cx, |project, cx| {
2819            project.worktrees(cx).next().unwrap().read(cx).id()
2820        });
2821
2822        let item1 = cx.add_view(&workspace, |_| {
2823            let mut item = TestItem::new();
2824            item.project_path = Some((worktree_id, "one.txt").into());
2825            item
2826        });
2827        let item2 = cx.add_view(&workspace, |_| {
2828            let mut item = TestItem::new();
2829            item.project_path = Some((worktree_id, "two.txt").into());
2830            item
2831        });
2832
2833        // Add an item to an empty pane
2834        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item1), cx));
2835        project.read_with(cx, |project, cx| {
2836            assert_eq!(
2837                project.active_entry(),
2838                project
2839                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
2840                    .map(|e| e.id)
2841            );
2842        });
2843        assert_eq!(
2844            cx.current_window_title(window_id).as_deref(),
2845            Some("one.txt — root1")
2846        );
2847
2848        // Add a second item to a non-empty pane
2849        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item2), cx));
2850        assert_eq!(
2851            cx.current_window_title(window_id).as_deref(),
2852            Some("two.txt — root1")
2853        );
2854        project.read_with(cx, |project, cx| {
2855            assert_eq!(
2856                project.active_entry(),
2857                project
2858                    .entry_for_path(&(worktree_id, "two.txt").into(), cx)
2859                    .map(|e| e.id)
2860            );
2861        });
2862
2863        // Close the active item
2864        workspace
2865            .update(cx, |workspace, cx| {
2866                Pane::close_active_item(workspace, &Default::default(), cx).unwrap()
2867            })
2868            .await
2869            .unwrap();
2870        assert_eq!(
2871            cx.current_window_title(window_id).as_deref(),
2872            Some("one.txt — root1")
2873        );
2874        project.read_with(cx, |project, cx| {
2875            assert_eq!(
2876                project.active_entry(),
2877                project
2878                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
2879                    .map(|e| e.id)
2880            );
2881        });
2882
2883        // Add a project folder
2884        project
2885            .update(cx, |project, cx| {
2886                project.find_or_create_local_worktree("/root2", true, cx)
2887            })
2888            .await
2889            .unwrap();
2890        assert_eq!(
2891            cx.current_window_title(window_id).as_deref(),
2892            Some("one.txt — root1, root2")
2893        );
2894
2895        // Remove a project folder
2896        project.update(cx, |project, cx| {
2897            project.remove_worktree(worktree_id, cx);
2898        });
2899        assert_eq!(
2900            cx.current_window_title(window_id).as_deref(),
2901            Some("one.txt — root2")
2902        );
2903    }
2904
2905    #[gpui::test]
2906    async fn test_close_window(cx: &mut TestAppContext) {
2907        cx.foreground().forbid_parking();
2908        Settings::test_async(cx);
2909        let fs = FakeFs::new(cx.background());
2910        fs.insert_tree("/root", json!({ "one": "" })).await;
2911
2912        let project = Project::test(fs, ["root".as_ref()], cx).await;
2913        let (window_id, workspace) = cx.add_window(|cx| Workspace::new(project.clone(), cx));
2914
2915        // When there are no dirty items, there's nothing to do.
2916        let item1 = cx.add_view(&workspace, |_| TestItem::new());
2917        workspace.update(cx, |w, cx| w.add_item(Box::new(item1.clone()), cx));
2918        let task = workspace.update(cx, |w, cx| w.prepare_to_close(cx));
2919        assert_eq!(task.await.unwrap(), true);
2920
2921        // When there are dirty untitled items, prompt to save each one. If the user
2922        // cancels any prompt, then abort.
2923        let item2 = cx.add_view(&workspace, |_| {
2924            let mut item = TestItem::new();
2925            item.is_dirty = true;
2926            item
2927        });
2928        let item3 = cx.add_view(&workspace, |_| {
2929            let mut item = TestItem::new();
2930            item.is_dirty = true;
2931            item.project_entry_ids = vec![ProjectEntryId::from_proto(1)];
2932            item
2933        });
2934        workspace.update(cx, |w, cx| {
2935            w.add_item(Box::new(item2.clone()), cx);
2936            w.add_item(Box::new(item3.clone()), cx);
2937        });
2938        let task = workspace.update(cx, |w, cx| w.prepare_to_close(cx));
2939        cx.foreground().run_until_parked();
2940        cx.simulate_prompt_answer(window_id, 2 /* cancel */);
2941        cx.foreground().run_until_parked();
2942        assert!(!cx.has_pending_prompt(window_id));
2943        assert_eq!(task.await.unwrap(), false);
2944    }
2945
2946    #[gpui::test]
2947    async fn test_close_pane_items(cx: &mut TestAppContext) {
2948        cx.foreground().forbid_parking();
2949        Settings::test_async(cx);
2950        let fs = FakeFs::new(cx.background());
2951
2952        let project = Project::test(fs, None, cx).await;
2953        let (window_id, workspace) = cx.add_window(|cx| Workspace::new(project, cx));
2954
2955        let item1 = cx.add_view(&workspace, |_| {
2956            let mut item = TestItem::new();
2957            item.is_dirty = true;
2958            item.project_entry_ids = vec![ProjectEntryId::from_proto(1)];
2959            item
2960        });
2961        let item2 = cx.add_view(&workspace, |_| {
2962            let mut item = TestItem::new();
2963            item.is_dirty = true;
2964            item.has_conflict = true;
2965            item.project_entry_ids = vec![ProjectEntryId::from_proto(2)];
2966            item
2967        });
2968        let item3 = cx.add_view(&workspace, |_| {
2969            let mut item = TestItem::new();
2970            item.is_dirty = true;
2971            item.has_conflict = true;
2972            item.project_entry_ids = vec![ProjectEntryId::from_proto(3)];
2973            item
2974        });
2975        let item4 = cx.add_view(&workspace, |_| {
2976            let mut item = TestItem::new();
2977            item.is_dirty = true;
2978            item
2979        });
2980        let pane = workspace.update(cx, |workspace, cx| {
2981            workspace.add_item(Box::new(item1.clone()), cx);
2982            workspace.add_item(Box::new(item2.clone()), cx);
2983            workspace.add_item(Box::new(item3.clone()), cx);
2984            workspace.add_item(Box::new(item4.clone()), cx);
2985            workspace.active_pane().clone()
2986        });
2987
2988        let close_items = workspace.update(cx, |workspace, cx| {
2989            pane.update(cx, |pane, cx| {
2990                pane.activate_item(1, true, true, false, cx);
2991                assert_eq!(pane.active_item().unwrap().id(), item2.id());
2992            });
2993
2994            let item1_id = item1.id();
2995            let item3_id = item3.id();
2996            let item4_id = item4.id();
2997            Pane::close_items(workspace, pane.clone(), cx, move |id| {
2998                [item1_id, item3_id, item4_id].contains(&id)
2999            })
3000        });
3001
3002        cx.foreground().run_until_parked();
3003        pane.read_with(cx, |pane, _| {
3004            assert_eq!(pane.items().count(), 4);
3005            assert_eq!(pane.active_item().unwrap().id(), item1.id());
3006        });
3007
3008        cx.simulate_prompt_answer(window_id, 0);
3009        cx.foreground().run_until_parked();
3010        pane.read_with(cx, |pane, cx| {
3011            assert_eq!(item1.read(cx).save_count, 1);
3012            assert_eq!(item1.read(cx).save_as_count, 0);
3013            assert_eq!(item1.read(cx).reload_count, 0);
3014            assert_eq!(pane.items().count(), 3);
3015            assert_eq!(pane.active_item().unwrap().id(), item3.id());
3016        });
3017
3018        cx.simulate_prompt_answer(window_id, 1);
3019        cx.foreground().run_until_parked();
3020        pane.read_with(cx, |pane, cx| {
3021            assert_eq!(item3.read(cx).save_count, 0);
3022            assert_eq!(item3.read(cx).save_as_count, 0);
3023            assert_eq!(item3.read(cx).reload_count, 1);
3024            assert_eq!(pane.items().count(), 2);
3025            assert_eq!(pane.active_item().unwrap().id(), item4.id());
3026        });
3027
3028        cx.simulate_prompt_answer(window_id, 0);
3029        cx.foreground().run_until_parked();
3030        cx.simulate_new_path_selection(|_| Some(Default::default()));
3031        close_items.await.unwrap();
3032        pane.read_with(cx, |pane, cx| {
3033            assert_eq!(item4.read(cx).save_count, 0);
3034            assert_eq!(item4.read(cx).save_as_count, 1);
3035            assert_eq!(item4.read(cx).reload_count, 0);
3036            assert_eq!(pane.items().count(), 1);
3037            assert_eq!(pane.active_item().unwrap().id(), item2.id());
3038        });
3039    }
3040
3041    #[gpui::test]
3042    async fn test_prompting_to_save_only_on_last_item_for_entry(cx: &mut TestAppContext) {
3043        cx.foreground().forbid_parking();
3044        Settings::test_async(cx);
3045        let fs = FakeFs::new(cx.background());
3046
3047        let project = Project::test(fs, [], cx).await;
3048        let (window_id, workspace) = cx.add_window(|cx| Workspace::new(project, cx));
3049
3050        // Create several workspace items with single project entries, and two
3051        // workspace items with multiple project entries.
3052        let single_entry_items = (0..=4)
3053            .map(|project_entry_id| {
3054                let mut item = TestItem::new();
3055                item.is_dirty = true;
3056                item.project_entry_ids = vec![ProjectEntryId::from_proto(project_entry_id)];
3057                item.is_singleton = true;
3058                item
3059            })
3060            .collect::<Vec<_>>();
3061        let item_2_3 = {
3062            let mut item = TestItem::new();
3063            item.is_dirty = true;
3064            item.is_singleton = false;
3065            item.project_entry_ids =
3066                vec![ProjectEntryId::from_proto(2), ProjectEntryId::from_proto(3)];
3067            item
3068        };
3069        let item_3_4 = {
3070            let mut item = TestItem::new();
3071            item.is_dirty = true;
3072            item.is_singleton = false;
3073            item.project_entry_ids =
3074                vec![ProjectEntryId::from_proto(3), ProjectEntryId::from_proto(4)];
3075            item
3076        };
3077
3078        // Create two panes that contain the following project entries:
3079        //   left pane:
3080        //     multi-entry items:   (2, 3)
3081        //     single-entry items:  0, 1, 2, 3, 4
3082        //   right pane:
3083        //     single-entry items:  1
3084        //     multi-entry items:   (3, 4)
3085        let left_pane = workspace.update(cx, |workspace, cx| {
3086            let left_pane = workspace.active_pane().clone();
3087            workspace.add_item(Box::new(cx.add_view(|_| item_2_3.clone())), cx);
3088            for item in &single_entry_items {
3089                workspace.add_item(Box::new(cx.add_view(|_| item.clone())), cx);
3090            }
3091            left_pane.update(cx, |pane, cx| {
3092                pane.activate_item(2, true, true, false, cx);
3093            });
3094
3095            workspace
3096                .split_pane(left_pane.clone(), SplitDirection::Right, cx)
3097                .unwrap();
3098            workspace.add_item(Box::new(cx.add_view(|_| item_3_4.clone())), cx);
3099
3100            left_pane
3101        });
3102
3103        // When closing all of the items in the left pane, we should be prompted twice:
3104        // once for project entry 0, and once for project entry 2. After those two
3105        // prompts, the task should complete.
3106        let close = workspace.update(cx, |workspace, cx| {
3107            workspace.activate_pane(left_pane.clone(), cx);
3108            Pane::close_items(workspace, left_pane.clone(), cx, |_| true)
3109        });
3110
3111        cx.foreground().run_until_parked();
3112        left_pane.read_with(cx, |pane, cx| {
3113            assert_eq!(
3114                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
3115                &[ProjectEntryId::from_proto(0)]
3116            );
3117        });
3118        cx.simulate_prompt_answer(window_id, 0);
3119
3120        cx.foreground().run_until_parked();
3121        left_pane.read_with(cx, |pane, cx| {
3122            assert_eq!(
3123                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
3124                &[ProjectEntryId::from_proto(2)]
3125            );
3126        });
3127        cx.simulate_prompt_answer(window_id, 0);
3128
3129        cx.foreground().run_until_parked();
3130        close.await.unwrap();
3131        left_pane.read_with(cx, |pane, _| {
3132            assert_eq!(pane.items().count(), 0);
3133        });
3134    }
3135
3136    #[gpui::test]
3137    async fn test_autosave(deterministic: Arc<Deterministic>, cx: &mut gpui::TestAppContext) {
3138        deterministic.forbid_parking();
3139
3140        Settings::test_async(cx);
3141        let fs = FakeFs::new(cx.background());
3142
3143        let project = Project::test(fs, [], cx).await;
3144        let (window_id, workspace) = cx.add_window(|cx| Workspace::new(project, cx));
3145
3146        let item = cx.add_view(&workspace, |_| {
3147            let mut item = TestItem::new();
3148            item.project_entry_ids = vec![ProjectEntryId::from_proto(1)];
3149            item
3150        });
3151        let item_id = item.id();
3152        workspace.update(cx, |workspace, cx| {
3153            workspace.add_item(Box::new(item.clone()), cx);
3154        });
3155
3156        // Autosave on window change.
3157        item.update(cx, |item, cx| {
3158            cx.update_global(|settings: &mut Settings, _| {
3159                settings.autosave = Autosave::OnWindowChange;
3160            });
3161            item.is_dirty = true;
3162        });
3163
3164        // Deactivating the window saves the file.
3165        cx.simulate_window_activation(None);
3166        deterministic.run_until_parked();
3167        item.read_with(cx, |item, _| assert_eq!(item.save_count, 1));
3168
3169        // Autosave on focus change.
3170        item.update(cx, |item, cx| {
3171            cx.focus_self();
3172            cx.update_global(|settings: &mut Settings, _| {
3173                settings.autosave = Autosave::OnFocusChange;
3174            });
3175            item.is_dirty = true;
3176        });
3177
3178        // Blurring the item saves the file.
3179        item.update(cx, |_, cx| cx.blur());
3180        deterministic.run_until_parked();
3181        item.read_with(cx, |item, _| assert_eq!(item.save_count, 2));
3182
3183        // Deactivating the window still saves the file.
3184        cx.simulate_window_activation(Some(window_id));
3185        item.update(cx, |item, cx| {
3186            cx.focus_self();
3187            item.is_dirty = true;
3188        });
3189        cx.simulate_window_activation(None);
3190
3191        deterministic.run_until_parked();
3192        item.read_with(cx, |item, _| assert_eq!(item.save_count, 3));
3193
3194        // Autosave after delay.
3195        item.update(cx, |item, cx| {
3196            cx.update_global(|settings: &mut Settings, _| {
3197                settings.autosave = Autosave::AfterDelay { milliseconds: 500 };
3198            });
3199            item.is_dirty = true;
3200            cx.emit(TestItemEvent::Edit);
3201        });
3202
3203        // Delay hasn't fully expired, so the file is still dirty and unsaved.
3204        deterministic.advance_clock(Duration::from_millis(250));
3205        item.read_with(cx, |item, _| assert_eq!(item.save_count, 3));
3206
3207        // After delay expires, the file is saved.
3208        deterministic.advance_clock(Duration::from_millis(250));
3209        item.read_with(cx, |item, _| assert_eq!(item.save_count, 4));
3210
3211        // Autosave on focus change, ensuring closing the tab counts as such.
3212        item.update(cx, |item, cx| {
3213            cx.update_global(|settings: &mut Settings, _| {
3214                settings.autosave = Autosave::OnFocusChange;
3215            });
3216            item.is_dirty = true;
3217        });
3218
3219        workspace
3220            .update(cx, |workspace, cx| {
3221                let pane = workspace.active_pane().clone();
3222                Pane::close_items(workspace, pane, cx, move |id| id == item_id)
3223            })
3224            .await
3225            .unwrap();
3226        assert!(!cx.has_pending_prompt(window_id));
3227        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
3228
3229        // Add the item again, ensuring autosave is prevented if the underlying file has been deleted.
3230        workspace.update(cx, |workspace, cx| {
3231            workspace.add_item(Box::new(item.clone()), cx);
3232        });
3233        item.update(cx, |item, cx| {
3234            item.project_entry_ids = Default::default();
3235            item.is_dirty = true;
3236            cx.blur();
3237        });
3238        deterministic.run_until_parked();
3239        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
3240
3241        // Ensure autosave is prevented for deleted files also when closing the buffer.
3242        let _close_items = workspace.update(cx, |workspace, cx| {
3243            let pane = workspace.active_pane().clone();
3244            Pane::close_items(workspace, pane, cx, move |id| id == item_id)
3245        });
3246        deterministic.run_until_parked();
3247        assert!(cx.has_pending_prompt(window_id));
3248        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
3249    }
3250
3251    #[gpui::test]
3252    async fn test_pane_navigation(
3253        deterministic: Arc<Deterministic>,
3254        cx: &mut gpui::TestAppContext,
3255    ) {
3256        deterministic.forbid_parking();
3257        Settings::test_async(cx);
3258        let fs = FakeFs::new(cx.background());
3259
3260        let project = Project::test(fs, [], cx).await;
3261        let (_, workspace) = cx.add_window(|cx| Workspace::new(project, cx));
3262
3263        let item = cx.add_view(&workspace, |_| {
3264            let mut item = TestItem::new();
3265            item.project_entry_ids = vec![ProjectEntryId::from_proto(1)];
3266            item
3267        });
3268        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
3269        let toolbar = pane.read_with(cx, |pane, _| pane.toolbar().clone());
3270        let toolbar_notify_count = Rc::new(RefCell::new(0));
3271
3272        workspace.update(cx, |workspace, cx| {
3273            workspace.add_item(Box::new(item.clone()), cx);
3274            let toolbar_notification_count = toolbar_notify_count.clone();
3275            cx.observe(&toolbar, move |_, _, _| {
3276                *toolbar_notification_count.borrow_mut() += 1
3277            })
3278            .detach();
3279        });
3280
3281        pane.read_with(cx, |pane, _| {
3282            assert!(!pane.can_navigate_backward());
3283            assert!(!pane.can_navigate_forward());
3284        });
3285
3286        item.update(cx, |item, cx| {
3287            item.set_state("one".to_string(), cx);
3288        });
3289
3290        // Toolbar must be notified to re-render the navigation buttons
3291        assert_eq!(*toolbar_notify_count.borrow(), 1);
3292
3293        pane.read_with(cx, |pane, _| {
3294            assert!(pane.can_navigate_backward());
3295            assert!(!pane.can_navigate_forward());
3296        });
3297
3298        workspace
3299            .update(cx, |workspace, cx| {
3300                Pane::go_back(workspace, Some(pane.clone()), cx)
3301            })
3302            .await;
3303
3304        assert_eq!(*toolbar_notify_count.borrow(), 3);
3305        pane.read_with(cx, |pane, _| {
3306            assert!(!pane.can_navigate_backward());
3307            assert!(pane.can_navigate_forward());
3308        });
3309    }
3310
3311    struct TestItem {
3312        state: String,
3313        save_count: usize,
3314        save_as_count: usize,
3315        reload_count: usize,
3316        is_dirty: bool,
3317        is_singleton: bool,
3318        has_conflict: bool,
3319        project_entry_ids: Vec<ProjectEntryId>,
3320        project_path: Option<ProjectPath>,
3321        nav_history: Option<ItemNavHistory>,
3322        tab_descriptions: Option<Vec<&'static str>>,
3323        tab_detail: Cell<Option<usize>>,
3324    }
3325
3326    enum TestItemEvent {
3327        Edit,
3328    }
3329
3330    impl Clone for TestItem {
3331        fn clone(&self) -> Self {
3332            Self {
3333                state: self.state.clone(),
3334                save_count: self.save_count,
3335                save_as_count: self.save_as_count,
3336                reload_count: self.reload_count,
3337                is_dirty: self.is_dirty,
3338                is_singleton: self.is_singleton,
3339                has_conflict: self.has_conflict,
3340                project_entry_ids: self.project_entry_ids.clone(),
3341                project_path: self.project_path.clone(),
3342                nav_history: None,
3343                tab_descriptions: None,
3344                tab_detail: Default::default(),
3345            }
3346        }
3347    }
3348
3349    impl TestItem {
3350        fn new() -> Self {
3351            Self {
3352                state: String::new(),
3353                save_count: 0,
3354                save_as_count: 0,
3355                reload_count: 0,
3356                is_dirty: false,
3357                has_conflict: false,
3358                project_entry_ids: Vec::new(),
3359                project_path: None,
3360                is_singleton: true,
3361                nav_history: None,
3362                tab_descriptions: None,
3363                tab_detail: Default::default(),
3364            }
3365        }
3366
3367        fn set_state(&mut self, state: String, cx: &mut ViewContext<Self>) {
3368            self.push_to_nav_history(cx);
3369            self.state = state;
3370        }
3371
3372        fn push_to_nav_history(&mut self, cx: &mut ViewContext<Self>) {
3373            if let Some(history) = &mut self.nav_history {
3374                history.push(Some(Box::new(self.state.clone())), cx);
3375            }
3376        }
3377    }
3378
3379    impl Entity for TestItem {
3380        type Event = TestItemEvent;
3381    }
3382
3383    impl View for TestItem {
3384        fn ui_name() -> &'static str {
3385            "TestItem"
3386        }
3387
3388        fn render(&mut self, _: &mut RenderContext<Self>) -> ElementBox {
3389            Empty::new().boxed()
3390        }
3391    }
3392
3393    impl Item for TestItem {
3394        fn tab_description<'a>(&'a self, detail: usize, _: &'a AppContext) -> Option<Cow<'a, str>> {
3395            self.tab_descriptions.as_ref().and_then(|descriptions| {
3396                let description = *descriptions.get(detail).or(descriptions.last())?;
3397                Some(description.into())
3398            })
3399        }
3400
3401        fn tab_content(&self, detail: Option<usize>, _: &theme::Tab, _: &AppContext) -> ElementBox {
3402            self.tab_detail.set(detail);
3403            Empty::new().boxed()
3404        }
3405
3406        fn project_path(&self, _: &AppContext) -> Option<ProjectPath> {
3407            self.project_path.clone()
3408        }
3409
3410        fn project_entry_ids(&self, _: &AppContext) -> SmallVec<[ProjectEntryId; 3]> {
3411            self.project_entry_ids.iter().copied().collect()
3412        }
3413
3414        fn is_singleton(&self, _: &AppContext) -> bool {
3415            self.is_singleton
3416        }
3417
3418        fn set_nav_history(&mut self, history: ItemNavHistory, _: &mut ViewContext<Self>) {
3419            self.nav_history = Some(history);
3420        }
3421
3422        fn navigate(&mut self, state: Box<dyn Any>, _: &mut ViewContext<Self>) -> bool {
3423            let state = *state.downcast::<String>().unwrap_or_default();
3424            if state != self.state {
3425                self.state = state;
3426                true
3427            } else {
3428                false
3429            }
3430        }
3431
3432        fn deactivated(&mut self, cx: &mut ViewContext<Self>) {
3433            self.push_to_nav_history(cx);
3434        }
3435
3436        fn clone_on_split(&self, _: &mut ViewContext<Self>) -> Option<Self>
3437        where
3438            Self: Sized,
3439        {
3440            Some(self.clone())
3441        }
3442
3443        fn is_dirty(&self, _: &AppContext) -> bool {
3444            self.is_dirty
3445        }
3446
3447        fn has_conflict(&self, _: &AppContext) -> bool {
3448            self.has_conflict
3449        }
3450
3451        fn can_save(&self, _: &AppContext) -> bool {
3452            self.project_entry_ids.len() > 0
3453        }
3454
3455        fn save(
3456            &mut self,
3457            _: ModelHandle<Project>,
3458            _: &mut ViewContext<Self>,
3459        ) -> Task<anyhow::Result<()>> {
3460            self.save_count += 1;
3461            self.is_dirty = false;
3462            Task::ready(Ok(()))
3463        }
3464
3465        fn save_as(
3466            &mut self,
3467            _: ModelHandle<Project>,
3468            _: std::path::PathBuf,
3469            _: &mut ViewContext<Self>,
3470        ) -> Task<anyhow::Result<()>> {
3471            self.save_as_count += 1;
3472            self.is_dirty = false;
3473            Task::ready(Ok(()))
3474        }
3475
3476        fn reload(
3477            &mut self,
3478            _: ModelHandle<Project>,
3479            _: &mut ViewContext<Self>,
3480        ) -> Task<anyhow::Result<()>> {
3481            self.reload_count += 1;
3482            self.is_dirty = false;
3483            Task::ready(Ok(()))
3484        }
3485
3486        fn should_update_tab_on_event(_: &Self::Event) -> bool {
3487            true
3488        }
3489
3490        fn is_edit_event(event: &Self::Event) -> bool {
3491            matches!(event, TestItemEvent::Edit)
3492        }
3493    }
3494}