workspace.rs

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