workspace.rs

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