workspace.rs

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