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