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