workspace.rs

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