workspace.rs

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