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