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