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(&project, |_, project, cx| {
 809            if project.read(cx).is_read_only() {
 810                cx.blur();
 811            }
 812            cx.notify()
 813        })
 814        .detach();
 815        cx.observe_window_activation(Self::on_window_activation_changed)
 816            .detach();
 817
 818        cx.subscribe(&project, move |this, project, event, cx| {
 819            match event {
 820                project::Event::RemoteIdChanged(remote_id) => {
 821                    this.project_remote_id_changed(*remote_id, cx);
 822                }
 823                project::Event::CollaboratorLeft(peer_id) => {
 824                    this.collaborator_left(*peer_id, cx);
 825                }
 826                project::Event::WorktreeRemoved(_) | project::Event::WorktreeAdded => {
 827                    this.update_window_title(cx);
 828                }
 829                _ => {}
 830            }
 831            if project.read(cx).is_read_only() {
 832                cx.blur();
 833            }
 834            cx.notify()
 835        })
 836        .detach();
 837
 838        let pane = cx.add_view(|cx| Pane::new(cx));
 839        let pane_id = pane.id();
 840        cx.subscribe(&pane, move |this, _, event, cx| {
 841            this.handle_pane_event(pane_id, event, cx)
 842        })
 843        .detach();
 844        cx.focus(&pane);
 845        cx.emit(Event::PaneAdded(pane.clone()));
 846
 847        let fs = project.read(cx).fs().clone();
 848        let user_store = project.read(cx).user_store();
 849        let client = project.read(cx).client();
 850        let mut current_user = user_store.read(cx).watch_current_user().clone();
 851        let mut connection_status = client.status().clone();
 852        let _observe_current_user = cx.spawn_weak(|this, mut cx| async move {
 853            current_user.recv().await;
 854            connection_status.recv().await;
 855            let mut stream =
 856                Stream::map(current_user, drop).merge(Stream::map(connection_status, drop));
 857
 858            while stream.recv().await.is_some() {
 859                cx.update(|cx| {
 860                    if let Some(this) = this.upgrade(cx) {
 861                        this.update(cx, |_, cx| cx.notify());
 862                    }
 863                })
 864            }
 865        });
 866
 867        let weak_self = cx.weak_handle();
 868
 869        cx.emit_global(WorkspaceCreated(weak_self.clone()));
 870
 871        let left_sidebar = cx.add_view(|_| Sidebar::new(Side::Left));
 872        let right_sidebar = cx.add_view(|_| Sidebar::new(Side::Right));
 873        let left_sidebar_buttons = cx.add_view(|cx| SidebarButtons::new(left_sidebar.clone(), cx));
 874        let right_sidebar_buttons =
 875            cx.add_view(|cx| SidebarButtons::new(right_sidebar.clone(), cx));
 876        let status_bar = cx.add_view(|cx| {
 877            let mut status_bar = StatusBar::new(&pane.clone(), cx);
 878            status_bar.add_left_item(left_sidebar_buttons, cx);
 879            status_bar.add_right_item(right_sidebar_buttons, cx);
 880            status_bar
 881        });
 882
 883        let mut this = Workspace {
 884            modal: None,
 885            weak_self,
 886            center: PaneGroup::new(pane.clone()),
 887            panes: vec![pane.clone()],
 888            active_pane: pane.clone(),
 889            status_bar,
 890            notifications: Default::default(),
 891            client,
 892            remote_entity_subscription: None,
 893            user_store,
 894            fs,
 895            left_sidebar,
 896            right_sidebar,
 897            project,
 898            leader_state: Default::default(),
 899            follower_states_by_leader: Default::default(),
 900            last_leaders_by_pane: Default::default(),
 901            window_edited: false,
 902            _observe_current_user,
 903        };
 904        this.project_remote_id_changed(this.project.read(cx).remote_id(), cx);
 905        cx.defer(|this, cx| this.update_window_title(cx));
 906
 907        this
 908    }
 909
 910    pub fn weak_handle(&self) -> WeakViewHandle<Self> {
 911        self.weak_self.clone()
 912    }
 913
 914    pub fn left_sidebar(&self) -> &ViewHandle<Sidebar> {
 915        &self.left_sidebar
 916    }
 917
 918    pub fn right_sidebar(&self) -> &ViewHandle<Sidebar> {
 919        &self.right_sidebar
 920    }
 921
 922    pub fn status_bar(&self) -> &ViewHandle<StatusBar> {
 923        &self.status_bar
 924    }
 925
 926    pub fn user_store(&self) -> &ModelHandle<UserStore> {
 927        &self.user_store
 928    }
 929
 930    pub fn project(&self) -> &ModelHandle<Project> {
 931        &self.project
 932    }
 933
 934    /// Call the given callback with a workspace whose project is local.
 935    ///
 936    /// If the given workspace has a local project, then it will be passed
 937    /// to the callback. Otherwise, a new empty window will be created.
 938    pub fn with_local_workspace<T, F>(
 939        &mut self,
 940        cx: &mut ViewContext<Self>,
 941        app_state: Arc<AppState>,
 942        mut callback: F,
 943    ) -> T
 944    where
 945        T: 'static,
 946        F: FnMut(&mut Workspace, &mut ViewContext<Workspace>) -> T,
 947    {
 948        if self.project.read(cx).is_local() {
 949            callback(self, cx)
 950        } else {
 951            let (_, workspace) = cx.add_window((app_state.build_window_options)(), |cx| {
 952                let mut workspace = Workspace::new(
 953                    Project::local(
 954                        false,
 955                        app_state.client.clone(),
 956                        app_state.user_store.clone(),
 957                        app_state.project_store.clone(),
 958                        app_state.languages.clone(),
 959                        app_state.fs.clone(),
 960                        cx,
 961                    ),
 962                    cx,
 963                );
 964                (app_state.initialize_workspace)(&mut workspace, &app_state, cx);
 965                workspace
 966            });
 967            workspace.update(cx, callback)
 968        }
 969    }
 970
 971    pub fn worktrees<'a>(
 972        &self,
 973        cx: &'a AppContext,
 974    ) -> impl 'a + Iterator<Item = ModelHandle<Worktree>> {
 975        self.project.read(cx).worktrees(cx)
 976    }
 977
 978    pub fn visible_worktrees<'a>(
 979        &self,
 980        cx: &'a AppContext,
 981    ) -> impl 'a + Iterator<Item = ModelHandle<Worktree>> {
 982        self.project.read(cx).visible_worktrees(cx)
 983    }
 984
 985    pub fn worktree_scans_complete(&self, cx: &AppContext) -> impl Future<Output = ()> + 'static {
 986        let futures = self
 987            .worktrees(cx)
 988            .filter_map(|worktree| worktree.read(cx).as_local())
 989            .map(|worktree| worktree.scan_complete())
 990            .collect::<Vec<_>>();
 991        async move {
 992            for future in futures {
 993                future.await;
 994            }
 995        }
 996    }
 997
 998    pub fn close(
 999        &mut self,
1000        _: &CloseWindow,
1001        cx: &mut ViewContext<Self>,
1002    ) -> Option<Task<Result<()>>> {
1003        let prepare = self.prepare_to_close(cx);
1004        Some(cx.spawn(|this, mut cx| async move {
1005            if prepare.await? {
1006                this.update(&mut cx, |_, cx| {
1007                    let window_id = cx.window_id();
1008                    cx.remove_window(window_id);
1009                });
1010            }
1011            Ok(())
1012        }))
1013    }
1014
1015    pub fn prepare_to_close(&mut self, cx: &mut ViewContext<Self>) -> Task<Result<bool>> {
1016        self.save_all_internal(true, cx)
1017    }
1018
1019    fn save_all(&mut self, _: &SaveAll, cx: &mut ViewContext<Self>) -> Option<Task<Result<()>>> {
1020        let save_all = self.save_all_internal(false, cx);
1021        Some(cx.foreground().spawn(async move {
1022            save_all.await?;
1023            Ok(())
1024        }))
1025    }
1026
1027    fn save_all_internal(
1028        &mut self,
1029        should_prompt_to_save: bool,
1030        cx: &mut ViewContext<Self>,
1031    ) -> Task<Result<bool>> {
1032        let dirty_items = self
1033            .panes
1034            .iter()
1035            .flat_map(|pane| {
1036                pane.read(cx).items().filter_map(|item| {
1037                    if item.is_dirty(cx) {
1038                        Some((pane.clone(), item.boxed_clone()))
1039                    } else {
1040                        None
1041                    }
1042                })
1043            })
1044            .collect::<Vec<_>>();
1045
1046        let project = self.project.clone();
1047        cx.spawn_weak(|_, mut cx| async move {
1048            // let mut saved_project_entry_ids = HashSet::default();
1049            for (pane, item) in dirty_items {
1050                let (is_singl, project_entry_ids) =
1051                    cx.read(|cx| (item.is_singleton(cx), item.project_entry_ids(cx)));
1052                if is_singl || !project_entry_ids.is_empty() {
1053                    if let Some(ix) =
1054                        pane.read_with(&cx, |pane, _| pane.index_for_item(item.as_ref()))
1055                    {
1056                        if !Pane::save_item(
1057                            project.clone(),
1058                            &pane,
1059                            ix,
1060                            &item,
1061                            should_prompt_to_save,
1062                            &mut cx,
1063                        )
1064                        .await?
1065                        {
1066                            return Ok(false);
1067                        }
1068                    }
1069                }
1070            }
1071            Ok(true)
1072        })
1073    }
1074
1075    pub fn open_paths(
1076        &mut self,
1077        mut abs_paths: Vec<PathBuf>,
1078        visible: bool,
1079        cx: &mut ViewContext<Self>,
1080    ) -> Task<Vec<Option<Result<Box<dyn ItemHandle>, Arc<anyhow::Error>>>>> {
1081        let fs = self.fs.clone();
1082
1083        // Sort the paths to ensure we add worktrees for parents before their children.
1084        abs_paths.sort_unstable();
1085        cx.spawn(|this, mut cx| async move {
1086            let mut project_paths = Vec::new();
1087            for path in &abs_paths {
1088                project_paths.push(
1089                    this.update(&mut cx, |this, cx| {
1090                        this.project_path_for_path(path, visible, cx)
1091                    })
1092                    .await
1093                    .log_err(),
1094                );
1095            }
1096
1097            let tasks = abs_paths
1098                .iter()
1099                .cloned()
1100                .zip(project_paths.into_iter())
1101                .map(|(abs_path, project_path)| {
1102                    let this = this.clone();
1103                    cx.spawn(|mut cx| {
1104                        let fs = fs.clone();
1105                        async move {
1106                            let (_worktree, project_path) = project_path?;
1107                            if fs.is_file(&abs_path).await {
1108                                Some(
1109                                    this.update(&mut cx, |this, cx| {
1110                                        this.open_path(project_path, true, cx)
1111                                    })
1112                                    .await,
1113                                )
1114                            } else {
1115                                None
1116                            }
1117                        }
1118                    })
1119                })
1120                .collect::<Vec<_>>();
1121
1122            futures::future::join_all(tasks).await
1123        })
1124    }
1125
1126    fn add_folder_to_project(&mut self, _: &AddFolderToProject, cx: &mut ViewContext<Self>) {
1127        let mut paths = cx.prompt_for_paths(PathPromptOptions {
1128            files: false,
1129            directories: true,
1130            multiple: true,
1131        });
1132        cx.spawn(|this, mut cx| async move {
1133            if let Some(paths) = paths.recv().await.flatten() {
1134                let results = this
1135                    .update(&mut cx, |this, cx| this.open_paths(paths, true, cx))
1136                    .await;
1137                for result in results {
1138                    if let Some(result) = result {
1139                        result.log_err();
1140                    }
1141                }
1142            }
1143        })
1144        .detach();
1145    }
1146
1147    fn remove_folder_from_project(
1148        &mut self,
1149        RemoveWorktreeFromProject(worktree_id): &RemoveWorktreeFromProject,
1150        cx: &mut ViewContext<Self>,
1151    ) {
1152        self.project
1153            .update(cx, |project, cx| project.remove_worktree(*worktree_id, cx));
1154    }
1155
1156    fn toggle_project_online(&mut self, action: &ToggleProjectOnline, cx: &mut ViewContext<Self>) {
1157        let project = action
1158            .project
1159            .clone()
1160            .unwrap_or_else(|| self.project.clone());
1161        project.update(cx, |project, cx| {
1162            let public = !project.is_online();
1163            project.set_online(public, cx);
1164        });
1165    }
1166
1167    fn project_path_for_path(
1168        &self,
1169        abs_path: &Path,
1170        visible: bool,
1171        cx: &mut ViewContext<Self>,
1172    ) -> Task<Result<(ModelHandle<Worktree>, ProjectPath)>> {
1173        let entry = self.project().update(cx, |project, cx| {
1174            project.find_or_create_local_worktree(abs_path, visible, cx)
1175        });
1176        cx.spawn(|_, cx| async move {
1177            let (worktree, path) = entry.await?;
1178            let worktree_id = worktree.read_with(&cx, |t, _| t.id());
1179            Ok((
1180                worktree,
1181                ProjectPath {
1182                    worktree_id,
1183                    path: path.into(),
1184                },
1185            ))
1186        })
1187    }
1188
1189    /// Returns the modal that was toggled closed if it was open.
1190    pub fn toggle_modal<V, F>(
1191        &mut self,
1192        cx: &mut ViewContext<Self>,
1193        add_view: F,
1194    ) -> Option<ViewHandle<V>>
1195    where
1196        V: 'static + View,
1197        F: FnOnce(&mut Self, &mut ViewContext<Self>) -> ViewHandle<V>,
1198    {
1199        cx.notify();
1200        // Whatever modal was visible is getting clobbered. If its the same type as V, then return
1201        // it. Otherwise, create a new modal and set it as active.
1202        let already_open_modal = self.modal.take().and_then(|modal| modal.downcast::<V>());
1203        if let Some(already_open_modal) = already_open_modal {
1204            cx.focus_self();
1205            Some(already_open_modal)
1206        } else {
1207            let modal = add_view(self, cx);
1208            cx.focus(&modal);
1209            self.modal = Some(modal.into());
1210            None
1211        }
1212    }
1213
1214    pub fn modal(&self) -> Option<&AnyViewHandle> {
1215        self.modal.as_ref()
1216    }
1217
1218    pub fn dismiss_modal(&mut self, cx: &mut ViewContext<Self>) {
1219        if self.modal.take().is_some() {
1220            cx.focus(&self.active_pane);
1221            cx.notify();
1222        }
1223    }
1224
1225    pub fn show_notification<V: Notification>(
1226        &mut self,
1227        id: usize,
1228        cx: &mut ViewContext<Self>,
1229        build_notification: impl FnOnce(&mut ViewContext<Self>) -> ViewHandle<V>,
1230    ) {
1231        let type_id = TypeId::of::<V>();
1232        if self
1233            .notifications
1234            .iter()
1235            .all(|(existing_type_id, existing_id, _)| {
1236                (*existing_type_id, *existing_id) != (type_id, id)
1237            })
1238        {
1239            let notification = build_notification(cx);
1240            cx.subscribe(&notification, move |this, handle, event, cx| {
1241                if handle.read(cx).should_dismiss_notification_on_event(event) {
1242                    this.dismiss_notification(type_id, id, cx);
1243                }
1244            })
1245            .detach();
1246            self.notifications
1247                .push((type_id, id, Box::new(notification)));
1248            cx.notify();
1249        }
1250    }
1251
1252    fn dismiss_notification(&mut self, type_id: TypeId, id: usize, cx: &mut ViewContext<Self>) {
1253        self.notifications
1254            .retain(|(existing_type_id, existing_id, _)| {
1255                if (*existing_type_id, *existing_id) == (type_id, id) {
1256                    cx.notify();
1257                    false
1258                } else {
1259                    true
1260                }
1261            });
1262    }
1263
1264    pub fn items<'a>(
1265        &'a self,
1266        cx: &'a AppContext,
1267    ) -> impl 'a + Iterator<Item = &Box<dyn ItemHandle>> {
1268        self.panes.iter().flat_map(|pane| pane.read(cx).items())
1269    }
1270
1271    pub fn item_of_type<T: Item>(&self, cx: &AppContext) -> Option<ViewHandle<T>> {
1272        self.items_of_type(cx).max_by_key(|item| item.id())
1273    }
1274
1275    pub fn items_of_type<'a, T: Item>(
1276        &'a self,
1277        cx: &'a AppContext,
1278    ) -> impl 'a + Iterator<Item = ViewHandle<T>> {
1279        self.panes
1280            .iter()
1281            .flat_map(|pane| pane.read(cx).items_of_type())
1282    }
1283
1284    pub fn active_item(&self, cx: &AppContext) -> Option<Box<dyn ItemHandle>> {
1285        self.active_pane().read(cx).active_item()
1286    }
1287
1288    fn active_project_path(&self, cx: &ViewContext<Self>) -> Option<ProjectPath> {
1289        self.active_item(cx).and_then(|item| item.project_path(cx))
1290    }
1291
1292    pub fn save_active_item(
1293        &mut self,
1294        force_name_change: bool,
1295        cx: &mut ViewContext<Self>,
1296    ) -> Task<Result<()>> {
1297        let project = self.project.clone();
1298        if let Some(item) = self.active_item(cx) {
1299            if !force_name_change && item.can_save(cx) {
1300                if item.has_conflict(cx.as_ref()) {
1301                    const CONFLICT_MESSAGE: &'static str = "This file has changed on disk since you started editing it. Do you want to overwrite it?";
1302
1303                    let mut answer = cx.prompt(
1304                        PromptLevel::Warning,
1305                        CONFLICT_MESSAGE,
1306                        &["Overwrite", "Cancel"],
1307                    );
1308                    cx.spawn(|_, mut cx| async move {
1309                        let answer = answer.recv().await;
1310                        if answer == Some(0) {
1311                            cx.update(|cx| item.save(project, cx)).await?;
1312                        }
1313                        Ok(())
1314                    })
1315                } else {
1316                    item.save(project, cx)
1317                }
1318            } else if item.is_singleton(cx) {
1319                let worktree = self.worktrees(cx).next();
1320                let start_abs_path = worktree
1321                    .and_then(|w| w.read(cx).as_local())
1322                    .map_or(Path::new(""), |w| w.abs_path())
1323                    .to_path_buf();
1324                let mut abs_path = cx.prompt_for_new_path(&start_abs_path);
1325                cx.spawn(|_, mut cx| async move {
1326                    if let Some(abs_path) = abs_path.recv().await.flatten() {
1327                        cx.update(|cx| item.save_as(project, abs_path, cx)).await?;
1328                    }
1329                    Ok(())
1330                })
1331            } else {
1332                Task::ready(Ok(()))
1333            }
1334        } else {
1335            Task::ready(Ok(()))
1336        }
1337    }
1338
1339    pub fn toggle_sidebar(&mut self, side: Side, cx: &mut ViewContext<Self>) {
1340        let sidebar = match side {
1341            Side::Left => &mut self.left_sidebar,
1342            Side::Right => &mut self.right_sidebar,
1343        };
1344        sidebar.update(cx, |sidebar, cx| {
1345            sidebar.set_open(!sidebar.is_open(), cx);
1346        });
1347        cx.focus_self();
1348        cx.notify();
1349    }
1350
1351    pub fn toggle_sidebar_item(&mut self, action: &ToggleSidebarItem, cx: &mut ViewContext<Self>) {
1352        let sidebar = match action.side {
1353            Side::Left => &mut self.left_sidebar,
1354            Side::Right => &mut self.right_sidebar,
1355        };
1356        let active_item = sidebar.update(cx, |sidebar, cx| {
1357            if sidebar.is_open() && sidebar.active_item_ix() == action.item_index {
1358                sidebar.set_open(false, cx);
1359                None
1360            } else {
1361                sidebar.set_open(true, cx);
1362                sidebar.activate_item(action.item_index, cx);
1363                sidebar.active_item().cloned()
1364            }
1365        });
1366        if let Some(active_item) = active_item {
1367            if active_item.is_focused(cx) {
1368                cx.focus_self();
1369            } else {
1370                cx.focus(active_item.to_any());
1371            }
1372        } else {
1373            cx.focus_self();
1374        }
1375        cx.notify();
1376    }
1377
1378    pub fn toggle_sidebar_item_focus(
1379        &mut self,
1380        side: Side,
1381        item_index: usize,
1382        cx: &mut ViewContext<Self>,
1383    ) {
1384        let sidebar = match side {
1385            Side::Left => &mut self.left_sidebar,
1386            Side::Right => &mut self.right_sidebar,
1387        };
1388        let active_item = sidebar.update(cx, |sidebar, cx| {
1389            sidebar.set_open(true, cx);
1390            sidebar.activate_item(item_index, cx);
1391            sidebar.active_item().cloned()
1392        });
1393        if let Some(active_item) = active_item {
1394            if active_item.is_focused(cx) {
1395                cx.focus_self();
1396            } else {
1397                cx.focus(active_item.to_any());
1398            }
1399        }
1400        cx.notify();
1401    }
1402
1403    pub fn focus_center(&mut self, _: &menu::Cancel, cx: &mut ViewContext<Self>) {
1404        cx.focus_self();
1405        cx.notify();
1406    }
1407
1408    fn add_pane(&mut self, cx: &mut ViewContext<Self>) -> ViewHandle<Pane> {
1409        let pane = cx.add_view(|cx| Pane::new(cx));
1410        let pane_id = pane.id();
1411        cx.subscribe(&pane, move |this, _, event, cx| {
1412            this.handle_pane_event(pane_id, event, cx)
1413        })
1414        .detach();
1415        self.panes.push(pane.clone());
1416        self.activate_pane(pane.clone(), cx);
1417        cx.emit(Event::PaneAdded(pane.clone()));
1418        pane
1419    }
1420
1421    pub fn add_item(&mut self, item: Box<dyn ItemHandle>, cx: &mut ViewContext<Self>) {
1422        let pane = self.active_pane().clone();
1423        Pane::add_item(self, pane, item, true, true, cx);
1424    }
1425
1426    pub fn open_path(
1427        &mut self,
1428        path: impl Into<ProjectPath>,
1429        focus_item: bool,
1430        cx: &mut ViewContext<Self>,
1431    ) -> Task<Result<Box<dyn ItemHandle>, Arc<anyhow::Error>>> {
1432        let pane = self.active_pane().downgrade();
1433        let task = self.load_path(path.into(), cx);
1434        cx.spawn(|this, mut cx| async move {
1435            let (project_entry_id, build_item) = task.await?;
1436            let pane = pane
1437                .upgrade(&cx)
1438                .ok_or_else(|| anyhow!("pane was closed"))?;
1439            this.update(&mut cx, |this, cx| {
1440                Ok(Pane::open_item(
1441                    this,
1442                    pane,
1443                    project_entry_id,
1444                    focus_item,
1445                    cx,
1446                    build_item,
1447                ))
1448            })
1449        })
1450    }
1451
1452    pub(crate) fn load_path(
1453        &mut self,
1454        path: ProjectPath,
1455        cx: &mut ViewContext<Self>,
1456    ) -> Task<
1457        Result<(
1458            ProjectEntryId,
1459            impl 'static + FnOnce(&mut MutableAppContext) -> Box<dyn ItemHandle>,
1460        )>,
1461    > {
1462        let project = self.project().clone();
1463        let project_item = project.update(cx, |project, cx| project.open_path(path, cx));
1464        let window_id = cx.window_id();
1465        cx.as_mut().spawn(|mut cx| async move {
1466            let (project_entry_id, project_item) = project_item.await?;
1467            let build_item = cx.update(|cx| {
1468                cx.default_global::<ProjectItemBuilders>()
1469                    .get(&project_item.model_type())
1470                    .ok_or_else(|| anyhow!("no item builder for project item"))
1471                    .cloned()
1472            })?;
1473            let build_item =
1474                move |cx: &mut MutableAppContext| build_item(window_id, project, project_item, cx);
1475            Ok((project_entry_id, build_item))
1476        })
1477    }
1478
1479    pub fn open_project_item<T>(
1480        &mut self,
1481        project_item: ModelHandle<T::Item>,
1482        cx: &mut ViewContext<Self>,
1483    ) -> ViewHandle<T>
1484    where
1485        T: ProjectItem,
1486    {
1487        use project::Item as _;
1488
1489        let entry_id = project_item.read(cx).entry_id(cx);
1490        if let Some(item) = entry_id
1491            .and_then(|entry_id| self.active_pane().read(cx).item_for_entry(entry_id, cx))
1492            .and_then(|item| item.downcast())
1493        {
1494            self.activate_item(&item, cx);
1495            return item;
1496        }
1497
1498        let item = cx.add_view(|cx| T::for_project_item(self.project().clone(), project_item, cx));
1499        self.add_item(Box::new(item.clone()), cx);
1500        item
1501    }
1502
1503    pub fn activate_item(&mut self, item: &dyn ItemHandle, cx: &mut ViewContext<Self>) -> bool {
1504        let result = self.panes.iter().find_map(|pane| {
1505            if let Some(ix) = pane.read(cx).index_for_item(item) {
1506                Some((pane.clone(), ix))
1507            } else {
1508                None
1509            }
1510        });
1511        if let Some((pane, ix)) = result {
1512            self.activate_pane(pane.clone(), cx);
1513            pane.update(cx, |pane, cx| pane.activate_item(ix, true, true, cx));
1514            true
1515        } else {
1516            false
1517        }
1518    }
1519
1520    fn activate_pane_at_index(&mut self, action: &ActivatePane, cx: &mut ViewContext<Self>) {
1521        let panes = self.center.panes();
1522        if let Some(pane) = panes.get(action.0).map(|p| (*p).clone()) {
1523            self.activate_pane(pane, cx);
1524        } else {
1525            self.split_pane(self.active_pane.clone(), SplitDirection::Right, cx);
1526        }
1527    }
1528
1529    pub fn activate_next_pane(&mut self, cx: &mut ViewContext<Self>) {
1530        let next_pane = {
1531            let panes = self.center.panes();
1532            let ix = panes
1533                .iter()
1534                .position(|pane| **pane == self.active_pane)
1535                .unwrap();
1536            let next_ix = (ix + 1) % panes.len();
1537            panes[next_ix].clone()
1538        };
1539        self.activate_pane(next_pane, cx);
1540    }
1541
1542    pub fn activate_previous_pane(&mut self, cx: &mut ViewContext<Self>) {
1543        let prev_pane = {
1544            let panes = self.center.panes();
1545            let ix = panes
1546                .iter()
1547                .position(|pane| **pane == self.active_pane)
1548                .unwrap();
1549            let prev_ix = if ix == 0 { panes.len() - 1 } else { ix - 1 };
1550            panes[prev_ix].clone()
1551        };
1552        self.activate_pane(prev_pane, cx);
1553    }
1554
1555    fn activate_pane(&mut self, pane: ViewHandle<Pane>, cx: &mut ViewContext<Self>) {
1556        if self.active_pane != pane {
1557            self.active_pane = pane.clone();
1558            self.status_bar.update(cx, |status_bar, cx| {
1559                status_bar.set_active_pane(&self.active_pane, cx);
1560            });
1561            self.active_item_path_changed(cx);
1562            cx.focus(&self.active_pane);
1563            cx.notify();
1564        }
1565
1566        self.update_followers(
1567            proto::update_followers::Variant::UpdateActiveView(proto::UpdateActiveView {
1568                id: self.active_item(cx).map(|item| item.id() as u64),
1569                leader_id: self.leader_for_pane(&pane).map(|id| id.0),
1570            }),
1571            cx,
1572        );
1573    }
1574
1575    fn handle_pane_event(
1576        &mut self,
1577        pane_id: usize,
1578        event: &pane::Event,
1579        cx: &mut ViewContext<Self>,
1580    ) {
1581        if let Some(pane) = self.pane(pane_id) {
1582            match event {
1583                pane::Event::Split(direction) => {
1584                    self.split_pane(pane, *direction, cx);
1585                }
1586                pane::Event::Remove => {
1587                    self.remove_pane(pane, cx);
1588                }
1589                pane::Event::Activate => {
1590                    self.activate_pane(pane, cx);
1591                }
1592                pane::Event::ActivateItem { local } => {
1593                    if *local {
1594                        self.unfollow(&pane, cx);
1595                    }
1596                    if pane == self.active_pane {
1597                        self.active_item_path_changed(cx);
1598                    }
1599                }
1600                pane::Event::ChangeItemTitle => {
1601                    if pane == self.active_pane {
1602                        self.active_item_path_changed(cx);
1603                    }
1604                    self.update_window_edited(cx);
1605                }
1606                pane::Event::RemoveItem => {
1607                    self.update_window_edited(cx);
1608                }
1609            }
1610        } else {
1611            error!("pane {} not found", pane_id);
1612        }
1613    }
1614
1615    pub fn split_pane(
1616        &mut self,
1617        pane: ViewHandle<Pane>,
1618        direction: SplitDirection,
1619        cx: &mut ViewContext<Self>,
1620    ) -> ViewHandle<Pane> {
1621        let new_pane = self.add_pane(cx);
1622        self.activate_pane(new_pane.clone(), cx);
1623        if let Some(item) = pane.read(cx).active_item() {
1624            if let Some(clone) = item.clone_on_split(cx.as_mut()) {
1625                Pane::add_item(self, new_pane.clone(), clone, true, true, cx);
1626            }
1627        }
1628        self.center.split(&pane, &new_pane, direction).unwrap();
1629        cx.notify();
1630        new_pane
1631    }
1632
1633    fn remove_pane(&mut self, pane: ViewHandle<Pane>, cx: &mut ViewContext<Self>) {
1634        if self.center.remove(&pane).unwrap() {
1635            self.panes.retain(|p| p != &pane);
1636            self.activate_pane(self.panes.last().unwrap().clone(), cx);
1637            self.unfollow(&pane, cx);
1638            self.last_leaders_by_pane.remove(&pane.downgrade());
1639            cx.notify();
1640        } else {
1641            self.active_item_path_changed(cx);
1642        }
1643    }
1644
1645    pub fn panes(&self) -> &[ViewHandle<Pane>] {
1646        &self.panes
1647    }
1648
1649    fn pane(&self, pane_id: usize) -> Option<ViewHandle<Pane>> {
1650        self.panes.iter().find(|pane| pane.id() == pane_id).cloned()
1651    }
1652
1653    pub fn active_pane(&self) -> &ViewHandle<Pane> {
1654        &self.active_pane
1655    }
1656
1657    fn project_remote_id_changed(&mut self, remote_id: Option<u64>, cx: &mut ViewContext<Self>) {
1658        if let Some(remote_id) = remote_id {
1659            self.remote_entity_subscription =
1660                Some(self.client.add_view_for_remote_entity(remote_id, cx));
1661        } else {
1662            self.remote_entity_subscription.take();
1663        }
1664    }
1665
1666    fn collaborator_left(&mut self, peer_id: PeerId, cx: &mut ViewContext<Self>) {
1667        self.leader_state.followers.remove(&peer_id);
1668        if let Some(states_by_pane) = self.follower_states_by_leader.remove(&peer_id) {
1669            for state in states_by_pane.into_values() {
1670                for item in state.items_by_leader_view_id.into_values() {
1671                    if let FollowerItem::Loaded(item) = item {
1672                        item.set_leader_replica_id(None, cx);
1673                    }
1674                }
1675            }
1676        }
1677        cx.notify();
1678    }
1679
1680    pub fn toggle_follow(
1681        &mut self,
1682        ToggleFollow(leader_id): &ToggleFollow,
1683        cx: &mut ViewContext<Self>,
1684    ) -> Option<Task<Result<()>>> {
1685        let leader_id = *leader_id;
1686        let pane = self.active_pane().clone();
1687
1688        if let Some(prev_leader_id) = self.unfollow(&pane, cx) {
1689            if leader_id == prev_leader_id {
1690                return None;
1691            }
1692        }
1693
1694        self.last_leaders_by_pane
1695            .insert(pane.downgrade(), leader_id);
1696        self.follower_states_by_leader
1697            .entry(leader_id)
1698            .or_default()
1699            .insert(pane.clone(), Default::default());
1700        cx.notify();
1701
1702        let project_id = self.project.read(cx).remote_id()?;
1703        let request = self.client.request(proto::Follow {
1704            project_id,
1705            leader_id: leader_id.0,
1706        });
1707        Some(cx.spawn_weak(|this, mut cx| async move {
1708            let response = request.await?;
1709            if let Some(this) = this.upgrade(&cx) {
1710                this.update(&mut cx, |this, _| {
1711                    let state = this
1712                        .follower_states_by_leader
1713                        .get_mut(&leader_id)
1714                        .and_then(|states_by_pane| states_by_pane.get_mut(&pane))
1715                        .ok_or_else(|| anyhow!("following interrupted"))?;
1716                    state.active_view_id = response.active_view_id;
1717                    Ok::<_, anyhow::Error>(())
1718                })?;
1719                Self::add_views_from_leader(this, leader_id, vec![pane], response.views, &mut cx)
1720                    .await?;
1721            }
1722            Ok(())
1723        }))
1724    }
1725
1726    pub fn follow_next_collaborator(
1727        &mut self,
1728        _: &FollowNextCollaborator,
1729        cx: &mut ViewContext<Self>,
1730    ) -> Option<Task<Result<()>>> {
1731        let collaborators = self.project.read(cx).collaborators();
1732        let next_leader_id = if let Some(leader_id) = self.leader_for_pane(&self.active_pane) {
1733            let mut collaborators = collaborators.keys().copied();
1734            while let Some(peer_id) = collaborators.next() {
1735                if peer_id == leader_id {
1736                    break;
1737                }
1738            }
1739            collaborators.next()
1740        } else if let Some(last_leader_id) =
1741            self.last_leaders_by_pane.get(&self.active_pane.downgrade())
1742        {
1743            if collaborators.contains_key(last_leader_id) {
1744                Some(*last_leader_id)
1745            } else {
1746                None
1747            }
1748        } else {
1749            None
1750        };
1751
1752        next_leader_id
1753            .or_else(|| collaborators.keys().copied().next())
1754            .and_then(|leader_id| self.toggle_follow(&ToggleFollow(leader_id), cx))
1755    }
1756
1757    pub fn unfollow(
1758        &mut self,
1759        pane: &ViewHandle<Pane>,
1760        cx: &mut ViewContext<Self>,
1761    ) -> Option<PeerId> {
1762        for (leader_id, states_by_pane) in &mut self.follower_states_by_leader {
1763            let leader_id = *leader_id;
1764            if let Some(state) = states_by_pane.remove(&pane) {
1765                for (_, item) in state.items_by_leader_view_id {
1766                    if let FollowerItem::Loaded(item) = item {
1767                        item.set_leader_replica_id(None, cx);
1768                    }
1769                }
1770
1771                if states_by_pane.is_empty() {
1772                    self.follower_states_by_leader.remove(&leader_id);
1773                    if let Some(project_id) = self.project.read(cx).remote_id() {
1774                        self.client
1775                            .send(proto::Unfollow {
1776                                project_id,
1777                                leader_id: leader_id.0,
1778                            })
1779                            .log_err();
1780                    }
1781                }
1782
1783                cx.notify();
1784                return Some(leader_id);
1785            }
1786        }
1787        None
1788    }
1789
1790    fn render_connection_status(&self, cx: &mut RenderContext<Self>) -> Option<ElementBox> {
1791        let theme = &cx.global::<Settings>().theme;
1792        match &*self.client.status().borrow() {
1793            client::Status::ConnectionError
1794            | client::Status::ConnectionLost
1795            | client::Status::Reauthenticating
1796            | client::Status::Reconnecting { .. }
1797            | client::Status::ReconnectionError { .. } => Some(
1798                Container::new(
1799                    Align::new(
1800                        ConstrainedBox::new(
1801                            Svg::new("icons/offline-14.svg")
1802                                .with_color(theme.workspace.titlebar.offline_icon.color)
1803                                .boxed(),
1804                        )
1805                        .with_width(theme.workspace.titlebar.offline_icon.width)
1806                        .boxed(),
1807                    )
1808                    .boxed(),
1809                )
1810                .with_style(theme.workspace.titlebar.offline_icon.container)
1811                .boxed(),
1812            ),
1813            client::Status::UpgradeRequired => Some(
1814                Label::new(
1815                    "Please update Zed to collaborate".to_string(),
1816                    theme.workspace.titlebar.outdated_warning.text.clone(),
1817                )
1818                .contained()
1819                .with_style(theme.workspace.titlebar.outdated_warning.container)
1820                .aligned()
1821                .boxed(),
1822            ),
1823            _ => None,
1824        }
1825    }
1826
1827    fn render_titlebar(&self, theme: &Theme, cx: &mut RenderContext<Self>) -> ElementBox {
1828        let project = &self.project.read(cx);
1829        let replica_id = project.replica_id();
1830        let mut worktree_root_names = String::new();
1831        for (i, name) in project.worktree_root_names(cx).enumerate() {
1832            if i > 0 {
1833                worktree_root_names.push_str(", ");
1834            }
1835            worktree_root_names.push_str(name);
1836        }
1837
1838        ConstrainedBox::new(
1839            Container::new(
1840                Stack::new()
1841                    .with_child(
1842                        Label::new(worktree_root_names, theme.workspace.titlebar.title.clone())
1843                            .aligned()
1844                            .left()
1845                            .boxed(),
1846                    )
1847                    .with_child(
1848                        Align::new(
1849                            Flex::row()
1850                                .with_children(self.render_collaborators(theme, cx))
1851                                .with_children(self.render_current_user(
1852                                    self.user_store.read(cx).current_user().as_ref(),
1853                                    replica_id,
1854                                    theme,
1855                                    cx,
1856                                ))
1857                                .with_children(self.render_connection_status(cx))
1858                                .boxed(),
1859                        )
1860                        .right()
1861                        .boxed(),
1862                    )
1863                    .boxed(),
1864            )
1865            .with_style(theme.workspace.titlebar.container)
1866            .boxed(),
1867        )
1868        .with_height(theme.workspace.titlebar.height)
1869        .named("titlebar")
1870    }
1871
1872    fn active_item_path_changed(&mut self, cx: &mut ViewContext<Self>) {
1873        let active_entry = self.active_project_path(cx);
1874        self.project
1875            .update(cx, |project, cx| project.set_active_path(active_entry, cx));
1876        self.update_window_title(cx);
1877    }
1878
1879    fn update_window_title(&mut self, cx: &mut ViewContext<Self>) {
1880        let mut title = String::new();
1881        let project = self.project().read(cx);
1882        if let Some(path) = self.active_item(cx).and_then(|item| item.project_path(cx)) {
1883            let filename = path
1884                .path
1885                .file_name()
1886                .map(|s| s.to_string_lossy())
1887                .or_else(|| {
1888                    Some(Cow::Borrowed(
1889                        project
1890                            .worktree_for_id(path.worktree_id, cx)?
1891                            .read(cx)
1892                            .root_name(),
1893                    ))
1894                });
1895            if let Some(filename) = filename {
1896                title.push_str(filename.as_ref());
1897                title.push_str("");
1898            }
1899        }
1900        for (i, name) in project.worktree_root_names(cx).enumerate() {
1901            if i > 0 {
1902                title.push_str(", ");
1903            }
1904            title.push_str(name);
1905        }
1906        if title.is_empty() {
1907            title = "empty project".to_string();
1908        }
1909        cx.set_window_title(&title);
1910    }
1911
1912    fn update_window_edited(&mut self, cx: &mut ViewContext<Self>) {
1913        let is_edited = self
1914            .items(cx)
1915            .any(|item| item.has_conflict(cx) || item.is_dirty(cx));
1916        if is_edited != self.window_edited {
1917            self.window_edited = is_edited;
1918            cx.set_window_edited(self.window_edited)
1919        }
1920    }
1921
1922    fn render_collaborators(&self, theme: &Theme, cx: &mut RenderContext<Self>) -> Vec<ElementBox> {
1923        let mut collaborators = self
1924            .project
1925            .read(cx)
1926            .collaborators()
1927            .values()
1928            .cloned()
1929            .collect::<Vec<_>>();
1930        collaborators.sort_unstable_by_key(|collaborator| collaborator.replica_id);
1931        collaborators
1932            .into_iter()
1933            .filter_map(|collaborator| {
1934                Some(self.render_avatar(
1935                    collaborator.user.avatar.clone()?,
1936                    collaborator.replica_id,
1937                    Some((collaborator.peer_id, &collaborator.user.github_login)),
1938                    theme,
1939                    cx,
1940                ))
1941            })
1942            .collect()
1943    }
1944
1945    fn render_current_user(
1946        &self,
1947        user: Option<&Arc<User>>,
1948        replica_id: ReplicaId,
1949        theme: &Theme,
1950        cx: &mut RenderContext<Self>,
1951    ) -> Option<ElementBox> {
1952        let status = *self.client.status().borrow();
1953        if let Some(avatar) = user.and_then(|user| user.avatar.clone()) {
1954            Some(self.render_avatar(avatar, replica_id, None, theme, cx))
1955        } else if matches!(status, client::Status::UpgradeRequired) {
1956            None
1957        } else {
1958            Some(
1959                MouseEventHandler::new::<Authenticate, _, _>(0, cx, |state, _| {
1960                    let style = theme
1961                        .workspace
1962                        .titlebar
1963                        .sign_in_prompt
1964                        .style_for(state, false);
1965                    Label::new("Sign in".to_string(), style.text.clone())
1966                        .contained()
1967                        .with_style(style.container)
1968                        .boxed()
1969                })
1970                .on_click(|_, _, cx| cx.dispatch_action(Authenticate))
1971                .with_cursor_style(CursorStyle::PointingHand)
1972                .aligned()
1973                .boxed(),
1974            )
1975        }
1976    }
1977
1978    fn render_avatar(
1979        &self,
1980        avatar: Arc<ImageData>,
1981        replica_id: ReplicaId,
1982        peer: Option<(PeerId, &str)>,
1983        theme: &Theme,
1984        cx: &mut RenderContext<Self>,
1985    ) -> ElementBox {
1986        let replica_color = theme.editor.replica_selection_style(replica_id).cursor;
1987        let is_followed = peer.map_or(false, |(peer_id, _)| {
1988            self.follower_states_by_leader.contains_key(&peer_id)
1989        });
1990        let mut avatar_style = theme.workspace.titlebar.avatar;
1991        if is_followed {
1992            avatar_style.border = Border::all(1.0, replica_color);
1993        }
1994        let content = Stack::new()
1995            .with_child(
1996                Image::new(avatar)
1997                    .with_style(avatar_style)
1998                    .constrained()
1999                    .with_width(theme.workspace.titlebar.avatar_width)
2000                    .aligned()
2001                    .boxed(),
2002            )
2003            .with_child(
2004                AvatarRibbon::new(replica_color)
2005                    .constrained()
2006                    .with_width(theme.workspace.titlebar.avatar_ribbon.width)
2007                    .with_height(theme.workspace.titlebar.avatar_ribbon.height)
2008                    .aligned()
2009                    .bottom()
2010                    .boxed(),
2011            )
2012            .constrained()
2013            .with_width(theme.workspace.titlebar.avatar_width)
2014            .contained()
2015            .with_margin_left(theme.workspace.titlebar.avatar_margin)
2016            .boxed();
2017
2018        if let Some((peer_id, peer_github_login)) = peer {
2019            MouseEventHandler::new::<ToggleFollow, _, _>(replica_id.into(), cx, move |_, _| content)
2020                .with_cursor_style(CursorStyle::PointingHand)
2021                .on_click(move |_, _, cx| cx.dispatch_action(ToggleFollow(peer_id)))
2022                .with_tooltip::<ToggleFollow, _>(
2023                    peer_id.0 as usize,
2024                    if is_followed {
2025                        format!("Unfollow {}", peer_github_login)
2026                    } else {
2027                        format!("Follow {}", peer_github_login)
2028                    },
2029                    Some(Box::new(FollowNextCollaborator)),
2030                    theme.tooltip.clone(),
2031                    cx,
2032                )
2033                .boxed()
2034        } else {
2035            content
2036        }
2037    }
2038
2039    fn render_disconnected_overlay(&self, cx: &AppContext) -> Option<ElementBox> {
2040        if self.project.read(cx).is_read_only() {
2041            let theme = &cx.global::<Settings>().theme;
2042            Some(
2043                EventHandler::new(
2044                    Label::new(
2045                        "Your connection to the remote project has been lost.".to_string(),
2046                        theme.workspace.disconnected_overlay.text.clone(),
2047                    )
2048                    .aligned()
2049                    .contained()
2050                    .with_style(theme.workspace.disconnected_overlay.container)
2051                    .boxed(),
2052                )
2053                .capture_all::<Self>(0)
2054                .boxed(),
2055            )
2056        } else {
2057            None
2058        }
2059    }
2060
2061    fn render_notifications(&self, theme: &theme::Workspace) -> Option<ElementBox> {
2062        if self.notifications.is_empty() {
2063            None
2064        } else {
2065            Some(
2066                Flex::column()
2067                    .with_children(self.notifications.iter().map(|(_, _, notification)| {
2068                        ChildView::new(notification.as_ref())
2069                            .contained()
2070                            .with_style(theme.notification)
2071                            .boxed()
2072                    }))
2073                    .constrained()
2074                    .with_width(theme.notifications.width)
2075                    .contained()
2076                    .with_style(theme.notifications.container)
2077                    .aligned()
2078                    .bottom()
2079                    .right()
2080                    .boxed(),
2081            )
2082        }
2083    }
2084
2085    // RPC handlers
2086
2087    async fn handle_follow(
2088        this: ViewHandle<Self>,
2089        envelope: TypedEnvelope<proto::Follow>,
2090        _: Arc<Client>,
2091        mut cx: AsyncAppContext,
2092    ) -> Result<proto::FollowResponse> {
2093        this.update(&mut cx, |this, cx| {
2094            this.leader_state
2095                .followers
2096                .insert(envelope.original_sender_id()?);
2097
2098            let active_view_id = this
2099                .active_item(cx)
2100                .and_then(|i| i.to_followable_item_handle(cx))
2101                .map(|i| i.id() as u64);
2102            Ok(proto::FollowResponse {
2103                active_view_id,
2104                views: this
2105                    .panes()
2106                    .iter()
2107                    .flat_map(|pane| {
2108                        let leader_id = this.leader_for_pane(pane).map(|id| id.0);
2109                        pane.read(cx).items().filter_map({
2110                            let cx = &cx;
2111                            move |item| {
2112                                let id = item.id() as u64;
2113                                let item = item.to_followable_item_handle(cx)?;
2114                                let variant = item.to_state_proto(cx)?;
2115                                Some(proto::View {
2116                                    id,
2117                                    leader_id,
2118                                    variant: Some(variant),
2119                                })
2120                            }
2121                        })
2122                    })
2123                    .collect(),
2124            })
2125        })
2126    }
2127
2128    async fn handle_unfollow(
2129        this: ViewHandle<Self>,
2130        envelope: TypedEnvelope<proto::Unfollow>,
2131        _: Arc<Client>,
2132        mut cx: AsyncAppContext,
2133    ) -> Result<()> {
2134        this.update(&mut cx, |this, _| {
2135            this.leader_state
2136                .followers
2137                .remove(&envelope.original_sender_id()?);
2138            Ok(())
2139        })
2140    }
2141
2142    async fn handle_update_followers(
2143        this: ViewHandle<Self>,
2144        envelope: TypedEnvelope<proto::UpdateFollowers>,
2145        _: Arc<Client>,
2146        mut cx: AsyncAppContext,
2147    ) -> Result<()> {
2148        let leader_id = envelope.original_sender_id()?;
2149        match envelope
2150            .payload
2151            .variant
2152            .ok_or_else(|| anyhow!("invalid update"))?
2153        {
2154            proto::update_followers::Variant::UpdateActiveView(update_active_view) => {
2155                this.update(&mut cx, |this, cx| {
2156                    this.update_leader_state(leader_id, cx, |state, _| {
2157                        state.active_view_id = update_active_view.id;
2158                    });
2159                    Ok::<_, anyhow::Error>(())
2160                })
2161            }
2162            proto::update_followers::Variant::UpdateView(update_view) => {
2163                this.update(&mut cx, |this, cx| {
2164                    let variant = update_view
2165                        .variant
2166                        .ok_or_else(|| anyhow!("missing update view variant"))?;
2167                    this.update_leader_state(leader_id, cx, |state, cx| {
2168                        let variant = variant.clone();
2169                        match state
2170                            .items_by_leader_view_id
2171                            .entry(update_view.id)
2172                            .or_insert(FollowerItem::Loading(Vec::new()))
2173                        {
2174                            FollowerItem::Loaded(item) => {
2175                                item.apply_update_proto(variant, cx).log_err();
2176                            }
2177                            FollowerItem::Loading(updates) => updates.push(variant),
2178                        }
2179                    });
2180                    Ok(())
2181                })
2182            }
2183            proto::update_followers::Variant::CreateView(view) => {
2184                let panes = this.read_with(&cx, |this, _| {
2185                    this.follower_states_by_leader
2186                        .get(&leader_id)
2187                        .into_iter()
2188                        .flat_map(|states_by_pane| states_by_pane.keys())
2189                        .cloned()
2190                        .collect()
2191                });
2192                Self::add_views_from_leader(this.clone(), leader_id, panes, vec![view], &mut cx)
2193                    .await?;
2194                Ok(())
2195            }
2196        }
2197        .log_err();
2198
2199        Ok(())
2200    }
2201
2202    async fn add_views_from_leader(
2203        this: ViewHandle<Self>,
2204        leader_id: PeerId,
2205        panes: Vec<ViewHandle<Pane>>,
2206        views: Vec<proto::View>,
2207        cx: &mut AsyncAppContext,
2208    ) -> Result<()> {
2209        let project = this.read_with(cx, |this, _| this.project.clone());
2210        let replica_id = project
2211            .read_with(cx, |project, _| {
2212                project
2213                    .collaborators()
2214                    .get(&leader_id)
2215                    .map(|c| c.replica_id)
2216            })
2217            .ok_or_else(|| anyhow!("no such collaborator {}", leader_id))?;
2218
2219        let item_builders = cx.update(|cx| {
2220            cx.default_global::<FollowableItemBuilders>()
2221                .values()
2222                .map(|b| b.0)
2223                .collect::<Vec<_>>()
2224                .clone()
2225        });
2226
2227        let mut item_tasks_by_pane = HashMap::default();
2228        for pane in panes {
2229            let mut item_tasks = Vec::new();
2230            let mut leader_view_ids = Vec::new();
2231            for view in &views {
2232                let mut variant = view.variant.clone();
2233                if variant.is_none() {
2234                    Err(anyhow!("missing variant"))?;
2235                }
2236                for build_item in &item_builders {
2237                    let task =
2238                        cx.update(|cx| build_item(pane.clone(), project.clone(), &mut variant, cx));
2239                    if let Some(task) = task {
2240                        item_tasks.push(task);
2241                        leader_view_ids.push(view.id);
2242                        break;
2243                    } else {
2244                        assert!(variant.is_some());
2245                    }
2246                }
2247            }
2248
2249            item_tasks_by_pane.insert(pane, (item_tasks, leader_view_ids));
2250        }
2251
2252        for (pane, (item_tasks, leader_view_ids)) in item_tasks_by_pane {
2253            let items = futures::future::try_join_all(item_tasks).await?;
2254            this.update(cx, |this, cx| {
2255                let state = this
2256                    .follower_states_by_leader
2257                    .get_mut(&leader_id)?
2258                    .get_mut(&pane)?;
2259
2260                for (id, item) in leader_view_ids.into_iter().zip(items) {
2261                    item.set_leader_replica_id(Some(replica_id), cx);
2262                    match state.items_by_leader_view_id.entry(id) {
2263                        hash_map::Entry::Occupied(e) => {
2264                            let e = e.into_mut();
2265                            if let FollowerItem::Loading(updates) = e {
2266                                for update in updates.drain(..) {
2267                                    item.apply_update_proto(update, cx)
2268                                        .context("failed to apply view update")
2269                                        .log_err();
2270                                }
2271                            }
2272                            *e = FollowerItem::Loaded(item);
2273                        }
2274                        hash_map::Entry::Vacant(e) => {
2275                            e.insert(FollowerItem::Loaded(item));
2276                        }
2277                    }
2278                }
2279
2280                Some(())
2281            });
2282        }
2283        this.update(cx, |this, cx| this.leader_updated(leader_id, cx));
2284
2285        Ok(())
2286    }
2287
2288    fn update_followers(
2289        &self,
2290        update: proto::update_followers::Variant,
2291        cx: &AppContext,
2292    ) -> Option<()> {
2293        let project_id = self.project.read(cx).remote_id()?;
2294        if !self.leader_state.followers.is_empty() {
2295            self.client
2296                .send(proto::UpdateFollowers {
2297                    project_id,
2298                    follower_ids: self.leader_state.followers.iter().map(|f| f.0).collect(),
2299                    variant: Some(update),
2300                })
2301                .log_err();
2302        }
2303        None
2304    }
2305
2306    pub fn leader_for_pane(&self, pane: &ViewHandle<Pane>) -> Option<PeerId> {
2307        self.follower_states_by_leader
2308            .iter()
2309            .find_map(|(leader_id, state)| {
2310                if state.contains_key(pane) {
2311                    Some(*leader_id)
2312                } else {
2313                    None
2314                }
2315            })
2316    }
2317
2318    fn update_leader_state(
2319        &mut self,
2320        leader_id: PeerId,
2321        cx: &mut ViewContext<Self>,
2322        mut update_fn: impl FnMut(&mut FollowerState, &mut ViewContext<Self>),
2323    ) {
2324        for (_, state) in self
2325            .follower_states_by_leader
2326            .get_mut(&leader_id)
2327            .into_iter()
2328            .flatten()
2329        {
2330            update_fn(state, cx);
2331        }
2332        self.leader_updated(leader_id, cx);
2333    }
2334
2335    fn leader_updated(&mut self, leader_id: PeerId, cx: &mut ViewContext<Self>) -> Option<()> {
2336        let mut items_to_add = Vec::new();
2337        for (pane, state) in self.follower_states_by_leader.get(&leader_id)? {
2338            if let Some(active_item) = state
2339                .active_view_id
2340                .and_then(|id| state.items_by_leader_view_id.get(&id))
2341            {
2342                if let FollowerItem::Loaded(item) = active_item {
2343                    items_to_add.push((pane.clone(), item.boxed_clone()));
2344                }
2345            }
2346        }
2347
2348        for (pane, item) in items_to_add {
2349            Pane::add_item(self, pane.clone(), item.boxed_clone(), false, false, cx);
2350            if pane == self.active_pane {
2351                pane.update(cx, |pane, cx| pane.focus_active_item(cx));
2352            }
2353            cx.notify();
2354        }
2355        None
2356    }
2357
2358    fn on_window_activation_changed(&mut self, active: bool, cx: &mut ViewContext<Self>) {
2359        if !active
2360            && matches!(
2361                cx.global::<Settings>().autosave,
2362                Autosave::OnWindowChange | Autosave::OnFocusChange
2363            )
2364        {
2365            for pane in &self.panes {
2366                pane.update(cx, |pane, cx| {
2367                    for item in pane.items() {
2368                        Pane::autosave_item(item.as_ref(), self.project.clone(), cx)
2369                            .detach_and_log_err(cx);
2370                    }
2371                });
2372            }
2373        }
2374    }
2375}
2376
2377impl Entity for Workspace {
2378    type Event = Event;
2379}
2380
2381impl View for Workspace {
2382    fn ui_name() -> &'static str {
2383        "Workspace"
2384    }
2385
2386    fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
2387        let theme = cx.global::<Settings>().theme.clone();
2388        Stack::new()
2389            .with_child(
2390                Flex::column()
2391                    .with_child(self.render_titlebar(&theme, cx))
2392                    .with_child(
2393                        Stack::new()
2394                            .with_child({
2395                                Flex::row()
2396                                    .with_children(
2397                                        if self.left_sidebar.read(cx).active_item().is_some() {
2398                                            Some(
2399                                                ChildView::new(&self.left_sidebar)
2400                                                    .flex(0.8, false)
2401                                                    .boxed(),
2402                                            )
2403                                        } else {
2404                                            None
2405                                        },
2406                                    )
2407                                    .with_child(
2408                                        FlexItem::new(self.center.render(
2409                                            &theme,
2410                                            &self.follower_states_by_leader,
2411                                            self.project.read(cx).collaborators(),
2412                                        ))
2413                                        .flex(1., true)
2414                                        .boxed(),
2415                                    )
2416                                    .with_children(
2417                                        if self.right_sidebar.read(cx).active_item().is_some() {
2418                                            Some(
2419                                                ChildView::new(&self.right_sidebar)
2420                                                    .flex(0.8, false)
2421                                                    .boxed(),
2422                                            )
2423                                        } else {
2424                                            None
2425                                        },
2426                                    )
2427                                    .boxed()
2428                            })
2429                            .with_children(self.modal.as_ref().map(|m| {
2430                                ChildView::new(m)
2431                                    .contained()
2432                                    .with_style(theme.workspace.modal)
2433                                    .aligned()
2434                                    .top()
2435                                    .boxed()
2436                            }))
2437                            .with_children(self.render_notifications(&theme.workspace))
2438                            .flex(1.0, true)
2439                            .boxed(),
2440                    )
2441                    .with_child(ChildView::new(&self.status_bar).boxed())
2442                    .contained()
2443                    .with_background_color(theme.workspace.background)
2444                    .boxed(),
2445            )
2446            .with_children(self.render_disconnected_overlay(cx))
2447            .named("workspace")
2448    }
2449
2450    fn on_focus(&mut self, cx: &mut ViewContext<Self>) {
2451        cx.focus(&self.active_pane);
2452    }
2453}
2454
2455pub trait WorkspaceHandle {
2456    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath>;
2457}
2458
2459impl WorkspaceHandle for ViewHandle<Workspace> {
2460    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath> {
2461        self.read(cx)
2462            .worktrees(cx)
2463            .flat_map(|worktree| {
2464                let worktree_id = worktree.read(cx).id();
2465                worktree.read(cx).files(true, 0).map(move |f| ProjectPath {
2466                    worktree_id,
2467                    path: f.path.clone(),
2468                })
2469            })
2470            .collect::<Vec<_>>()
2471    }
2472}
2473
2474pub struct AvatarRibbon {
2475    color: Color,
2476}
2477
2478impl AvatarRibbon {
2479    pub fn new(color: Color) -> AvatarRibbon {
2480        AvatarRibbon { color }
2481    }
2482}
2483
2484impl Element for AvatarRibbon {
2485    type LayoutState = ();
2486
2487    type PaintState = ();
2488
2489    fn layout(
2490        &mut self,
2491        constraint: gpui::SizeConstraint,
2492        _: &mut gpui::LayoutContext,
2493    ) -> (gpui::geometry::vector::Vector2F, Self::LayoutState) {
2494        (constraint.max, ())
2495    }
2496
2497    fn paint(
2498        &mut self,
2499        bounds: gpui::geometry::rect::RectF,
2500        _: gpui::geometry::rect::RectF,
2501        _: &mut Self::LayoutState,
2502        cx: &mut gpui::PaintContext,
2503    ) -> Self::PaintState {
2504        let mut path = PathBuilder::new();
2505        path.reset(bounds.lower_left());
2506        path.curve_to(
2507            bounds.origin() + vec2f(bounds.height(), 0.),
2508            bounds.origin(),
2509        );
2510        path.line_to(bounds.upper_right() - vec2f(bounds.height(), 0.));
2511        path.curve_to(bounds.lower_right(), bounds.upper_right());
2512        path.line_to(bounds.lower_left());
2513        cx.scene.push_path(path.build(self.color, None));
2514    }
2515
2516    fn dispatch_event(
2517        &mut self,
2518        _: &gpui::Event,
2519        _: RectF,
2520        _: RectF,
2521        _: &mut Self::LayoutState,
2522        _: &mut Self::PaintState,
2523        _: &mut gpui::EventContext,
2524    ) -> bool {
2525        false
2526    }
2527
2528    fn debug(
2529        &self,
2530        bounds: gpui::geometry::rect::RectF,
2531        _: &Self::LayoutState,
2532        _: &Self::PaintState,
2533        _: &gpui::DebugContext,
2534    ) -> gpui::json::Value {
2535        json::json!({
2536            "type": "AvatarRibbon",
2537            "bounds": bounds.to_json(),
2538            "color": self.color.to_json(),
2539        })
2540    }
2541}
2542
2543impl std::fmt::Debug for OpenPaths {
2544    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2545        f.debug_struct("OpenPaths")
2546            .field("paths", &self.paths)
2547            .finish()
2548    }
2549}
2550
2551fn open(_: &Open, cx: &mut MutableAppContext) {
2552    let mut paths = cx.prompt_for_paths(PathPromptOptions {
2553        files: true,
2554        directories: true,
2555        multiple: true,
2556    });
2557    cx.spawn(|mut cx| async move {
2558        if let Some(paths) = paths.recv().await.flatten() {
2559            cx.update(|cx| cx.dispatch_global_action(OpenPaths { paths }));
2560        }
2561    })
2562    .detach();
2563}
2564
2565pub struct WorkspaceCreated(WeakViewHandle<Workspace>);
2566
2567pub fn activate_workspace_for_project(
2568    cx: &mut MutableAppContext,
2569    predicate: impl Fn(&mut Project, &mut ModelContext<Project>) -> bool,
2570) -> Option<ViewHandle<Workspace>> {
2571    for window_id in cx.window_ids().collect::<Vec<_>>() {
2572        if let Some(workspace_handle) = cx.root_view::<Workspace>(window_id) {
2573            let project = workspace_handle.read(cx).project.clone();
2574            if project.update(cx, &predicate) {
2575                cx.activate_window(window_id);
2576                return Some(workspace_handle);
2577            }
2578        }
2579    }
2580    None
2581}
2582
2583pub fn open_paths(
2584    abs_paths: &[PathBuf],
2585    app_state: &Arc<AppState>,
2586    cx: &mut MutableAppContext,
2587) -> Task<(
2588    ViewHandle<Workspace>,
2589    Vec<Option<Result<Box<dyn ItemHandle>, Arc<anyhow::Error>>>>,
2590)> {
2591    log::info!("open paths {:?}", abs_paths);
2592
2593    // Open paths in existing workspace if possible
2594    let existing =
2595        activate_workspace_for_project(cx, |project, cx| project.contains_paths(abs_paths, cx));
2596
2597    let app_state = app_state.clone();
2598    let abs_paths = abs_paths.to_vec();
2599    cx.spawn(|mut cx| async move {
2600        let mut new_project = None;
2601        let workspace = if let Some(existing) = existing {
2602            existing
2603        } else {
2604            let contains_directory =
2605                futures::future::join_all(abs_paths.iter().map(|path| app_state.fs.is_file(path)))
2606                    .await
2607                    .contains(&false);
2608
2609            cx.add_window((app_state.build_window_options)(), |cx| {
2610                let project = Project::local(
2611                    false,
2612                    app_state.client.clone(),
2613                    app_state.user_store.clone(),
2614                    app_state.project_store.clone(),
2615                    app_state.languages.clone(),
2616                    app_state.fs.clone(),
2617                    cx,
2618                );
2619                new_project = Some(project.clone());
2620                let mut workspace = Workspace::new(project, cx);
2621                (app_state.initialize_workspace)(&mut workspace, &app_state, cx);
2622                if contains_directory {
2623                    workspace.toggle_sidebar(Side::Left, cx);
2624                }
2625                workspace
2626            })
2627            .1
2628        };
2629
2630        let items = workspace
2631            .update(&mut cx, |workspace, cx| {
2632                workspace.open_paths(abs_paths, true, cx)
2633            })
2634            .await;
2635
2636        if let Some(project) = new_project {
2637            project
2638                .update(&mut cx, |project, cx| project.restore_state(cx))
2639                .await
2640                .log_err();
2641        }
2642
2643        (workspace, items)
2644    })
2645}
2646
2647pub fn join_project(
2648    contact: Arc<Contact>,
2649    project_index: usize,
2650    app_state: &Arc<AppState>,
2651    cx: &mut MutableAppContext,
2652) {
2653    let project_id = contact.projects[project_index].id;
2654
2655    for window_id in cx.window_ids().collect::<Vec<_>>() {
2656        if let Some(workspace) = cx.root_view::<Workspace>(window_id) {
2657            if workspace.read(cx).project().read(cx).remote_id() == Some(project_id) {
2658                cx.activate_window(window_id);
2659                return;
2660            }
2661        }
2662    }
2663
2664    cx.add_window((app_state.build_window_options)(), |cx| {
2665        WaitingRoom::new(contact, project_index, app_state.clone(), cx)
2666    });
2667}
2668
2669fn open_new(app_state: &Arc<AppState>, cx: &mut MutableAppContext) {
2670    let (window_id, workspace) = cx.add_window((app_state.build_window_options)(), |cx| {
2671        let mut workspace = Workspace::new(
2672            Project::local(
2673                false,
2674                app_state.client.clone(),
2675                app_state.user_store.clone(),
2676                app_state.project_store.clone(),
2677                app_state.languages.clone(),
2678                app_state.fs.clone(),
2679                cx,
2680            ),
2681            cx,
2682        );
2683        (app_state.initialize_workspace)(&mut workspace, app_state, cx);
2684        workspace
2685    });
2686    cx.dispatch_action(window_id, vec![workspace.id()], &NewFile);
2687}
2688
2689#[cfg(test)]
2690mod tests {
2691    use super::*;
2692    use gpui::{executor::Deterministic, ModelHandle, TestAppContext, ViewContext};
2693    use project::{FakeFs, Project, ProjectEntryId};
2694    use serde_json::json;
2695
2696    #[gpui::test]
2697    async fn test_tracking_active_path(cx: &mut TestAppContext) {
2698        cx.foreground().forbid_parking();
2699        Settings::test_async(cx);
2700        let fs = FakeFs::new(cx.background());
2701        fs.insert_tree(
2702            "/root1",
2703            json!({
2704                "one.txt": "",
2705                "two.txt": "",
2706            }),
2707        )
2708        .await;
2709        fs.insert_tree(
2710            "/root2",
2711            json!({
2712                "three.txt": "",
2713            }),
2714        )
2715        .await;
2716
2717        let project = Project::test(fs, ["root1".as_ref()], cx).await;
2718        let (window_id, workspace) = cx.add_window(|cx| Workspace::new(project.clone(), cx));
2719        let worktree_id = project.read_with(cx, |project, cx| {
2720            project.worktrees(cx).next().unwrap().read(cx).id()
2721        });
2722
2723        let item1 = cx.add_view(window_id, |_| {
2724            let mut item = TestItem::new();
2725            item.project_path = Some((worktree_id, "one.txt").into());
2726            item
2727        });
2728        let item2 = cx.add_view(window_id, |_| {
2729            let mut item = TestItem::new();
2730            item.project_path = Some((worktree_id, "two.txt").into());
2731            item
2732        });
2733
2734        // Add an item to an empty pane
2735        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item1), cx));
2736        project.read_with(cx, |project, cx| {
2737            assert_eq!(
2738                project.active_entry(),
2739                project.entry_for_path(&(worktree_id, "one.txt").into(), cx)
2740            );
2741        });
2742        assert_eq!(
2743            cx.current_window_title(window_id).as_deref(),
2744            Some("one.txt — root1")
2745        );
2746
2747        // Add a second item to a non-empty pane
2748        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item2), cx));
2749        assert_eq!(
2750            cx.current_window_title(window_id).as_deref(),
2751            Some("two.txt — root1")
2752        );
2753        project.read_with(cx, |project, cx| {
2754            assert_eq!(
2755                project.active_entry(),
2756                project.entry_for_path(&(worktree_id, "two.txt").into(), cx)
2757            );
2758        });
2759
2760        // Close the active item
2761        workspace
2762            .update(cx, |workspace, cx| {
2763                Pane::close_active_item(workspace, &Default::default(), cx).unwrap()
2764            })
2765            .await
2766            .unwrap();
2767        assert_eq!(
2768            cx.current_window_title(window_id).as_deref(),
2769            Some("one.txt — root1")
2770        );
2771        project.read_with(cx, |project, cx| {
2772            assert_eq!(
2773                project.active_entry(),
2774                project.entry_for_path(&(worktree_id, "one.txt").into(), cx)
2775            );
2776        });
2777
2778        // Add a project folder
2779        project
2780            .update(cx, |project, cx| {
2781                project.find_or_create_local_worktree("/root2", true, cx)
2782            })
2783            .await
2784            .unwrap();
2785        assert_eq!(
2786            cx.current_window_title(window_id).as_deref(),
2787            Some("one.txt — root1, root2")
2788        );
2789
2790        // Remove a project folder
2791        project.update(cx, |project, cx| {
2792            project.remove_worktree(worktree_id, cx);
2793        });
2794        assert_eq!(
2795            cx.current_window_title(window_id).as_deref(),
2796            Some("one.txt — root2")
2797        );
2798    }
2799
2800    #[gpui::test]
2801    async fn test_close_window(cx: &mut TestAppContext) {
2802        cx.foreground().forbid_parking();
2803        Settings::test_async(cx);
2804        let fs = FakeFs::new(cx.background());
2805        fs.insert_tree("/root", json!({ "one": "" })).await;
2806
2807        let project = Project::test(fs, ["root".as_ref()], cx).await;
2808        let (window_id, workspace) = cx.add_window(|cx| Workspace::new(project.clone(), cx));
2809
2810        // When there are no dirty items, there's nothing to do.
2811        let item1 = cx.add_view(window_id, |_| TestItem::new());
2812        workspace.update(cx, |w, cx| w.add_item(Box::new(item1.clone()), cx));
2813        let task = workspace.update(cx, |w, cx| w.prepare_to_close(cx));
2814        assert_eq!(task.await.unwrap(), true);
2815
2816        // When there are dirty untitled items, prompt to save each one. If the user
2817        // cancels any prompt, then abort.
2818        let item2 = cx.add_view(window_id, |_| {
2819            let mut item = TestItem::new();
2820            item.is_dirty = true;
2821            item
2822        });
2823        let item3 = cx.add_view(window_id, |_| {
2824            let mut item = TestItem::new();
2825            item.is_dirty = true;
2826            item.project_entry_ids = vec![ProjectEntryId::from_proto(1)];
2827            item
2828        });
2829        workspace.update(cx, |w, cx| {
2830            w.add_item(Box::new(item2.clone()), cx);
2831            w.add_item(Box::new(item3.clone()), cx);
2832        });
2833        let task = workspace.update(cx, |w, cx| w.prepare_to_close(cx));
2834        cx.foreground().run_until_parked();
2835        cx.simulate_prompt_answer(window_id, 2 /* cancel */);
2836        cx.foreground().run_until_parked();
2837        assert!(!cx.has_pending_prompt(window_id));
2838        assert_eq!(task.await.unwrap(), false);
2839    }
2840
2841    #[gpui::test]
2842    async fn test_close_pane_items(cx: &mut TestAppContext) {
2843        cx.foreground().forbid_parking();
2844        Settings::test_async(cx);
2845        let fs = FakeFs::new(cx.background());
2846
2847        let project = Project::test(fs, None, cx).await;
2848        let (window_id, workspace) = cx.add_window(|cx| Workspace::new(project, cx));
2849
2850        let item1 = cx.add_view(window_id, |_| {
2851            let mut item = TestItem::new();
2852            item.is_dirty = true;
2853            item.project_entry_ids = vec![ProjectEntryId::from_proto(1)];
2854            item
2855        });
2856        let item2 = cx.add_view(window_id, |_| {
2857            let mut item = TestItem::new();
2858            item.is_dirty = true;
2859            item.has_conflict = true;
2860            item.project_entry_ids = vec![ProjectEntryId::from_proto(2)];
2861            item
2862        });
2863        let item3 = cx.add_view(window_id, |_| {
2864            let mut item = TestItem::new();
2865            item.is_dirty = true;
2866            item.has_conflict = true;
2867            item.project_entry_ids = vec![ProjectEntryId::from_proto(3)];
2868            item
2869        });
2870        let item4 = cx.add_view(window_id, |_| {
2871            let mut item = TestItem::new();
2872            item.is_dirty = true;
2873            item
2874        });
2875        let pane = workspace.update(cx, |workspace, cx| {
2876            workspace.add_item(Box::new(item1.clone()), cx);
2877            workspace.add_item(Box::new(item2.clone()), cx);
2878            workspace.add_item(Box::new(item3.clone()), cx);
2879            workspace.add_item(Box::new(item4.clone()), cx);
2880            workspace.active_pane().clone()
2881        });
2882
2883        let close_items = workspace.update(cx, |workspace, cx| {
2884            pane.update(cx, |pane, cx| {
2885                pane.activate_item(1, true, true, cx);
2886                assert_eq!(pane.active_item().unwrap().id(), item2.id());
2887            });
2888
2889            let item1_id = item1.id();
2890            let item3_id = item3.id();
2891            let item4_id = item4.id();
2892            Pane::close_items(workspace, pane.clone(), cx, move |id| {
2893                [item1_id, item3_id, item4_id].contains(&id)
2894            })
2895        });
2896
2897        cx.foreground().run_until_parked();
2898        pane.read_with(cx, |pane, _| {
2899            assert_eq!(pane.items().count(), 4);
2900            assert_eq!(pane.active_item().unwrap().id(), item1.id());
2901        });
2902
2903        cx.simulate_prompt_answer(window_id, 0);
2904        cx.foreground().run_until_parked();
2905        pane.read_with(cx, |pane, cx| {
2906            assert_eq!(item1.read(cx).save_count, 1);
2907            assert_eq!(item1.read(cx).save_as_count, 0);
2908            assert_eq!(item1.read(cx).reload_count, 0);
2909            assert_eq!(pane.items().count(), 3);
2910            assert_eq!(pane.active_item().unwrap().id(), item3.id());
2911        });
2912
2913        cx.simulate_prompt_answer(window_id, 1);
2914        cx.foreground().run_until_parked();
2915        pane.read_with(cx, |pane, cx| {
2916            assert_eq!(item3.read(cx).save_count, 0);
2917            assert_eq!(item3.read(cx).save_as_count, 0);
2918            assert_eq!(item3.read(cx).reload_count, 1);
2919            assert_eq!(pane.items().count(), 2);
2920            assert_eq!(pane.active_item().unwrap().id(), item4.id());
2921        });
2922
2923        cx.simulate_prompt_answer(window_id, 0);
2924        cx.foreground().run_until_parked();
2925        cx.simulate_new_path_selection(|_| Some(Default::default()));
2926        close_items.await.unwrap();
2927        pane.read_with(cx, |pane, cx| {
2928            assert_eq!(item4.read(cx).save_count, 0);
2929            assert_eq!(item4.read(cx).save_as_count, 1);
2930            assert_eq!(item4.read(cx).reload_count, 0);
2931            assert_eq!(pane.items().count(), 1);
2932            assert_eq!(pane.active_item().unwrap().id(), item2.id());
2933        });
2934    }
2935
2936    #[gpui::test]
2937    async fn test_prompting_to_save_only_on_last_item_for_entry(cx: &mut TestAppContext) {
2938        cx.foreground().forbid_parking();
2939        Settings::test_async(cx);
2940        let fs = FakeFs::new(cx.background());
2941
2942        let project = Project::test(fs, [], cx).await;
2943        let (window_id, workspace) = cx.add_window(|cx| Workspace::new(project, cx));
2944
2945        // Create several workspace items with single project entries, and two
2946        // workspace items with multiple project entries.
2947        let single_entry_items = (0..=4)
2948            .map(|project_entry_id| {
2949                let mut item = TestItem::new();
2950                item.is_dirty = true;
2951                item.project_entry_ids = vec![ProjectEntryId::from_proto(project_entry_id)];
2952                item.is_singleton = true;
2953                item
2954            })
2955            .collect::<Vec<_>>();
2956        let item_2_3 = {
2957            let mut item = TestItem::new();
2958            item.is_dirty = true;
2959            item.is_singleton = false;
2960            item.project_entry_ids =
2961                vec![ProjectEntryId::from_proto(2), ProjectEntryId::from_proto(3)];
2962            item
2963        };
2964        let item_3_4 = {
2965            let mut item = TestItem::new();
2966            item.is_dirty = true;
2967            item.is_singleton = false;
2968            item.project_entry_ids =
2969                vec![ProjectEntryId::from_proto(3), ProjectEntryId::from_proto(4)];
2970            item
2971        };
2972
2973        // Create two panes that contain the following project entries:
2974        //   left pane:
2975        //     multi-entry items:   (2, 3)
2976        //     single-entry items:  0, 1, 2, 3, 4
2977        //   right pane:
2978        //     single-entry items:  1
2979        //     multi-entry items:   (3, 4)
2980        let left_pane = workspace.update(cx, |workspace, cx| {
2981            let left_pane = workspace.active_pane().clone();
2982            let right_pane = workspace.split_pane(left_pane.clone(), SplitDirection::Right, cx);
2983
2984            workspace.activate_pane(left_pane.clone(), cx);
2985            workspace.add_item(Box::new(cx.add_view(|_| item_2_3.clone())), cx);
2986            for item in &single_entry_items {
2987                workspace.add_item(Box::new(cx.add_view(|_| item.clone())), cx);
2988            }
2989
2990            workspace.activate_pane(right_pane.clone(), cx);
2991            workspace.add_item(Box::new(cx.add_view(|_| single_entry_items[1].clone())), cx);
2992            workspace.add_item(Box::new(cx.add_view(|_| item_3_4.clone())), cx);
2993
2994            left_pane
2995        });
2996
2997        // When closing all of the items in the left pane, we should be prompted twice:
2998        // once for project entry 0, and once for project entry 2. After those two
2999        // prompts, the task should complete.
3000        let close = workspace.update(cx, |workspace, cx| {
3001            workspace.activate_pane(left_pane.clone(), cx);
3002            Pane::close_items(workspace, left_pane.clone(), cx, |_| true)
3003        });
3004
3005        cx.foreground().run_until_parked();
3006        left_pane.read_with(cx, |pane, cx| {
3007            assert_eq!(
3008                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
3009                &[ProjectEntryId::from_proto(0)]
3010            );
3011        });
3012        cx.simulate_prompt_answer(window_id, 0);
3013
3014        cx.foreground().run_until_parked();
3015        left_pane.read_with(cx, |pane, cx| {
3016            assert_eq!(
3017                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
3018                &[ProjectEntryId::from_proto(2)]
3019            );
3020        });
3021        cx.simulate_prompt_answer(window_id, 0);
3022
3023        cx.foreground().run_until_parked();
3024        close.await.unwrap();
3025        left_pane.read_with(cx, |pane, _| {
3026            assert_eq!(pane.items().count(), 0);
3027        });
3028    }
3029
3030    #[gpui::test]
3031    async fn test_autosave(deterministic: Arc<Deterministic>, cx: &mut gpui::TestAppContext) {
3032        deterministic.forbid_parking();
3033
3034        Settings::test_async(cx);
3035        let fs = FakeFs::new(cx.background());
3036
3037        let project = Project::test(fs, [], cx).await;
3038        let (window_id, workspace) = cx.add_window(|cx| Workspace::new(project, cx));
3039
3040        let item = cx.add_view(window_id, |_| {
3041            let mut item = TestItem::new();
3042            item.project_entry_ids = vec![ProjectEntryId::from_proto(1)];
3043            item
3044        });
3045        let item_id = item.id();
3046        workspace.update(cx, |workspace, cx| {
3047            workspace.add_item(Box::new(item.clone()), cx);
3048        });
3049
3050        // Autosave on window change.
3051        item.update(cx, |item, cx| {
3052            cx.update_global(|settings: &mut Settings, _| {
3053                settings.autosave = Autosave::OnWindowChange;
3054            });
3055            item.is_dirty = true;
3056        });
3057
3058        // Deactivating the window saves the file.
3059        cx.simulate_window_activation(None);
3060        deterministic.run_until_parked();
3061        item.read_with(cx, |item, _| assert_eq!(item.save_count, 1));
3062
3063        // Autosave on focus change.
3064        item.update(cx, |item, cx| {
3065            cx.focus_self();
3066            cx.update_global(|settings: &mut Settings, _| {
3067                settings.autosave = Autosave::OnFocusChange;
3068            });
3069            item.is_dirty = true;
3070        });
3071
3072        // Blurring the item saves the file.
3073        item.update(cx, |_, cx| cx.blur());
3074        deterministic.run_until_parked();
3075        item.read_with(cx, |item, _| assert_eq!(item.save_count, 2));
3076
3077        // Deactivating the window still saves the file.
3078        cx.simulate_window_activation(Some(window_id));
3079        item.update(cx, |item, cx| {
3080            cx.focus_self();
3081            item.is_dirty = true;
3082        });
3083        cx.simulate_window_activation(None);
3084
3085        deterministic.run_until_parked();
3086        item.read_with(cx, |item, _| assert_eq!(item.save_count, 3));
3087
3088        // Autosave after delay.
3089        item.update(cx, |item, cx| {
3090            cx.update_global(|settings: &mut Settings, _| {
3091                settings.autosave = Autosave::AfterDelay { milliseconds: 500 };
3092            });
3093            item.is_dirty = true;
3094            cx.emit(TestItemEvent::Edit);
3095        });
3096
3097        // Delay hasn't fully expired, so the file is still dirty and unsaved.
3098        deterministic.advance_clock(Duration::from_millis(250));
3099        item.read_with(cx, |item, _| assert_eq!(item.save_count, 3));
3100
3101        // After delay expires, the file is saved.
3102        deterministic.advance_clock(Duration::from_millis(250));
3103        item.read_with(cx, |item, _| assert_eq!(item.save_count, 4));
3104
3105        // Autosave on focus change, ensuring closing the tab counts as such.
3106        item.update(cx, |item, cx| {
3107            cx.update_global(|settings: &mut Settings, _| {
3108                settings.autosave = Autosave::OnFocusChange;
3109            });
3110            item.is_dirty = true;
3111        });
3112
3113        workspace
3114            .update(cx, |workspace, cx| {
3115                let pane = workspace.active_pane().clone();
3116                Pane::close_items(workspace, pane, cx, move |id| id == item_id)
3117            })
3118            .await
3119            .unwrap();
3120        assert!(!cx.has_pending_prompt(window_id));
3121        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
3122
3123        // Add the item again, ensuring autosave is prevented if the underlying file has been deleted.
3124        workspace.update(cx, |workspace, cx| {
3125            workspace.add_item(Box::new(item.clone()), cx);
3126        });
3127        item.update(cx, |item, cx| {
3128            item.project_entry_ids = Default::default();
3129            item.is_dirty = true;
3130            cx.blur();
3131        });
3132        deterministic.run_until_parked();
3133        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
3134
3135        // Ensure autosave is prevented for deleted files also when closing the buffer.
3136        let _close_items = workspace.update(cx, |workspace, cx| {
3137            let pane = workspace.active_pane().clone();
3138            Pane::close_items(workspace, pane, cx, move |id| id == item_id)
3139        });
3140        deterministic.run_until_parked();
3141        assert!(cx.has_pending_prompt(window_id));
3142        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
3143    }
3144
3145    #[gpui::test]
3146    async fn test_pane_navigation(
3147        deterministic: Arc<Deterministic>,
3148        cx: &mut gpui::TestAppContext,
3149    ) {
3150        deterministic.forbid_parking();
3151        Settings::test_async(cx);
3152        let fs = FakeFs::new(cx.background());
3153
3154        let project = Project::test(fs, [], cx).await;
3155        let (window_id, workspace) = cx.add_window(|cx| Workspace::new(project, cx));
3156
3157        let item = cx.add_view(window_id, |_| {
3158            let mut item = TestItem::new();
3159            item.project_entry_ids = vec![ProjectEntryId::from_proto(1)];
3160            item
3161        });
3162        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
3163        let toolbar = pane.read_with(cx, |pane, _| pane.toolbar().clone());
3164        let toolbar_notify_count = Rc::new(RefCell::new(0));
3165
3166        workspace.update(cx, |workspace, cx| {
3167            workspace.add_item(Box::new(item.clone()), cx);
3168            let toolbar_notification_count = toolbar_notify_count.clone();
3169            cx.observe(&toolbar, move |_, _, _| {
3170                *toolbar_notification_count.borrow_mut() += 1
3171            })
3172            .detach();
3173        });
3174
3175        pane.read_with(cx, |pane, _| {
3176            assert!(!pane.can_navigate_backward());
3177            assert!(!pane.can_navigate_forward());
3178        });
3179
3180        item.update(cx, |item, cx| {
3181            item.set_state("one".to_string(), cx);
3182        });
3183
3184        // Toolbar must be notified to re-render the navigation buttons
3185        assert_eq!(*toolbar_notify_count.borrow(), 1);
3186
3187        pane.read_with(cx, |pane, _| {
3188            assert!(pane.can_navigate_backward());
3189            assert!(!pane.can_navigate_forward());
3190        });
3191
3192        workspace
3193            .update(cx, |workspace, cx| {
3194                Pane::go_back(workspace, Some(pane.clone()), cx)
3195            })
3196            .await;
3197
3198        assert_eq!(*toolbar_notify_count.borrow(), 3);
3199        pane.read_with(cx, |pane, _| {
3200            assert!(!pane.can_navigate_backward());
3201            assert!(pane.can_navigate_forward());
3202        });
3203    }
3204
3205    struct TestItem {
3206        state: String,
3207        save_count: usize,
3208        save_as_count: usize,
3209        reload_count: usize,
3210        is_dirty: bool,
3211        is_singleton: bool,
3212        has_conflict: bool,
3213        project_entry_ids: Vec<ProjectEntryId>,
3214        project_path: Option<ProjectPath>,
3215        nav_history: Option<ItemNavHistory>,
3216    }
3217
3218    enum TestItemEvent {
3219        Edit,
3220    }
3221
3222    impl Clone for TestItem {
3223        fn clone(&self) -> Self {
3224            Self {
3225                state: self.state.clone(),
3226                save_count: self.save_count,
3227                save_as_count: self.save_as_count,
3228                reload_count: self.reload_count,
3229                is_dirty: self.is_dirty,
3230                is_singleton: self.is_singleton,
3231                has_conflict: self.has_conflict,
3232                project_entry_ids: self.project_entry_ids.clone(),
3233                project_path: self.project_path.clone(),
3234                nav_history: None,
3235            }
3236        }
3237    }
3238
3239    impl TestItem {
3240        fn new() -> Self {
3241            Self {
3242                state: String::new(),
3243                save_count: 0,
3244                save_as_count: 0,
3245                reload_count: 0,
3246                is_dirty: false,
3247                has_conflict: false,
3248                project_entry_ids: Vec::new(),
3249                project_path: None,
3250                is_singleton: true,
3251                nav_history: None,
3252            }
3253        }
3254
3255        fn set_state(&mut self, state: String, cx: &mut ViewContext<Self>) {
3256            self.push_to_nav_history(cx);
3257            self.state = state;
3258        }
3259
3260        fn push_to_nav_history(&mut self, cx: &mut ViewContext<Self>) {
3261            if let Some(history) = &mut self.nav_history {
3262                history.push(Some(Box::new(self.state.clone())), cx);
3263            }
3264        }
3265    }
3266
3267    impl Entity for TestItem {
3268        type Event = TestItemEvent;
3269    }
3270
3271    impl View for TestItem {
3272        fn ui_name() -> &'static str {
3273            "TestItem"
3274        }
3275
3276        fn render(&mut self, _: &mut RenderContext<Self>) -> ElementBox {
3277            Empty::new().boxed()
3278        }
3279    }
3280
3281    impl Item for TestItem {
3282        fn tab_content(&self, _: &theme::Tab, _: &AppContext) -> ElementBox {
3283            Empty::new().boxed()
3284        }
3285
3286        fn project_path(&self, _: &AppContext) -> Option<ProjectPath> {
3287            self.project_path.clone()
3288        }
3289
3290        fn project_entry_ids(&self, _: &AppContext) -> SmallVec<[ProjectEntryId; 3]> {
3291            self.project_entry_ids.iter().copied().collect()
3292        }
3293
3294        fn is_singleton(&self, _: &AppContext) -> bool {
3295            self.is_singleton
3296        }
3297
3298        fn set_nav_history(&mut self, history: ItemNavHistory, _: &mut ViewContext<Self>) {
3299            self.nav_history = Some(history);
3300        }
3301
3302        fn navigate(&mut self, state: Box<dyn Any>, _: &mut ViewContext<Self>) -> bool {
3303            let state = *state.downcast::<String>().unwrap_or_default();
3304            if state != self.state {
3305                self.state = state;
3306                true
3307            } else {
3308                false
3309            }
3310        }
3311
3312        fn deactivated(&mut self, cx: &mut ViewContext<Self>) {
3313            self.push_to_nav_history(cx);
3314        }
3315
3316        fn clone_on_split(&self, _: &mut ViewContext<Self>) -> Option<Self>
3317        where
3318            Self: Sized,
3319        {
3320            Some(self.clone())
3321        }
3322
3323        fn is_dirty(&self, _: &AppContext) -> bool {
3324            self.is_dirty
3325        }
3326
3327        fn has_conflict(&self, _: &AppContext) -> bool {
3328            self.has_conflict
3329        }
3330
3331        fn can_save(&self, _: &AppContext) -> bool {
3332            self.project_entry_ids.len() > 0
3333        }
3334
3335        fn save(
3336            &mut self,
3337            _: ModelHandle<Project>,
3338            _: &mut ViewContext<Self>,
3339        ) -> Task<anyhow::Result<()>> {
3340            self.save_count += 1;
3341            self.is_dirty = false;
3342            Task::ready(Ok(()))
3343        }
3344
3345        fn save_as(
3346            &mut self,
3347            _: ModelHandle<Project>,
3348            _: std::path::PathBuf,
3349            _: &mut ViewContext<Self>,
3350        ) -> Task<anyhow::Result<()>> {
3351            self.save_as_count += 1;
3352            self.is_dirty = false;
3353            Task::ready(Ok(()))
3354        }
3355
3356        fn reload(
3357            &mut self,
3358            _: ModelHandle<Project>,
3359            _: &mut ViewContext<Self>,
3360        ) -> Task<anyhow::Result<()>> {
3361            self.reload_count += 1;
3362            self.is_dirty = false;
3363            Task::ready(Ok(()))
3364        }
3365
3366        fn should_update_tab_on_event(_: &Self::Event) -> bool {
3367            true
3368        }
3369
3370        fn is_edit_event(event: &Self::Event) -> bool {
3371            matches!(event, TestItemEvent::Edit)
3372        }
3373    }
3374}