workspace.rs

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