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(|| self.active_pane().downgrade());
1334        let task = self.load_path(path.into(), cx);
1335        cx.spawn(|this, mut cx| async move {
1336            let (project_entry_id, build_item) = task.await?;
1337            let pane = pane
1338                .upgrade(&cx)
1339                .ok_or_else(|| anyhow!("pane was closed"))?;
1340            this.update(&mut cx, |this, cx| {
1341                Ok(Pane::open_item(
1342                    this,
1343                    pane,
1344                    project_entry_id,
1345                    focus_item,
1346                    cx,
1347                    build_item,
1348                ))
1349            })
1350        })
1351    }
1352
1353    pub(crate) fn load_path(
1354        &mut self,
1355        path: ProjectPath,
1356        cx: &mut ViewContext<Self>,
1357    ) -> Task<
1358        Result<(
1359            ProjectEntryId,
1360            impl 'static + FnOnce(&mut ViewContext<Pane>) -> Box<dyn ItemHandle>,
1361        )>,
1362    > {
1363        let project = self.project().clone();
1364        let project_item = project.update(cx, |project, cx| project.open_path(path, cx));
1365        cx.as_mut().spawn(|mut cx| async move {
1366            let (project_entry_id, project_item) = project_item.await?;
1367            let build_item = cx.update(|cx| {
1368                cx.default_global::<ProjectItemBuilders>()
1369                    .get(&project_item.model_type())
1370                    .ok_or_else(|| anyhow!("no item builder for project item"))
1371                    .cloned()
1372            })?;
1373            let build_item =
1374                move |cx: &mut ViewContext<Pane>| build_item(project, project_item, cx);
1375            Ok((project_entry_id, build_item))
1376        })
1377    }
1378
1379    pub fn open_project_item<T>(
1380        &mut self,
1381        project_item: ModelHandle<T::Item>,
1382        cx: &mut ViewContext<Self>,
1383    ) -> ViewHandle<T>
1384    where
1385        T: ProjectItem,
1386    {
1387        use project::Item as _;
1388
1389        let entry_id = project_item.read(cx).entry_id(cx);
1390        if let Some(item) = entry_id
1391            .and_then(|entry_id| self.active_pane().read(cx).item_for_entry(entry_id, cx))
1392            .and_then(|item| item.downcast())
1393        {
1394            self.activate_item(&item, cx);
1395            return item;
1396        }
1397
1398        let item = cx.add_view(|cx| T::for_project_item(self.project().clone(), project_item, cx));
1399        self.add_item(Box::new(item.clone()), cx);
1400        item
1401    }
1402
1403    pub fn open_shared_screen(&mut self, action: &OpenSharedScreen, cx: &mut ViewContext<Self>) {
1404        if let Some(shared_screen) =
1405            self.shared_screen_for_peer(action.peer_id, &self.active_pane, cx)
1406        {
1407            let pane = self.active_pane.clone();
1408            Pane::add_item(self, &pane, Box::new(shared_screen), false, true, None, cx);
1409        }
1410    }
1411
1412    pub fn activate_item(&mut self, item: &dyn ItemHandle, cx: &mut ViewContext<Self>) -> bool {
1413        let result = self.panes.iter().find_map(|pane| {
1414            pane.read(cx)
1415                .index_for_item(item)
1416                .map(|ix| (pane.clone(), ix))
1417        });
1418        if let Some((pane, ix)) = result {
1419            pane.update(cx, |pane, cx| pane.activate_item(ix, true, true, cx));
1420            true
1421        } else {
1422            false
1423        }
1424    }
1425
1426    fn activate_pane_at_index(&mut self, action: &ActivatePane, cx: &mut ViewContext<Self>) {
1427        let panes = self.center.panes();
1428        if let Some(pane) = panes.get(action.0).map(|p| (*p).clone()) {
1429            cx.focus(pane);
1430        } else {
1431            self.split_pane(self.active_pane.clone(), SplitDirection::Right, cx);
1432        }
1433    }
1434
1435    pub fn activate_next_pane(&mut self, cx: &mut ViewContext<Self>) {
1436        let panes = self.center.panes();
1437        if let Some(ix) = panes.iter().position(|pane| **pane == self.active_pane) {
1438            let next_ix = (ix + 1) % panes.len();
1439            let next_pane = panes[next_ix].clone();
1440            cx.focus(next_pane);
1441        }
1442    }
1443
1444    pub fn activate_previous_pane(&mut self, cx: &mut ViewContext<Self>) {
1445        let panes = self.center.panes();
1446        if let Some(ix) = panes.iter().position(|pane| **pane == self.active_pane) {
1447            let prev_ix = cmp::min(ix.wrapping_sub(1), panes.len() - 1);
1448            let prev_pane = panes[prev_ix].clone();
1449            cx.focus(prev_pane);
1450        }
1451    }
1452
1453    fn handle_pane_focused(&mut self, pane: ViewHandle<Pane>, cx: &mut ViewContext<Self>) {
1454        if self.active_pane != pane {
1455            self.active_pane
1456                .update(cx, |pane, cx| pane.set_active(false, cx));
1457            self.active_pane = pane.clone();
1458            self.active_pane
1459                .update(cx, |pane, cx| pane.set_active(true, cx));
1460            self.status_bar.update(cx, |status_bar, cx| {
1461                status_bar.set_active_pane(&self.active_pane, cx);
1462            });
1463            self.active_item_path_changed(cx);
1464
1465            if &pane == self.dock_pane() {
1466                Dock::show(self, cx);
1467            } else {
1468                self.last_active_center_pane = Some(pane.downgrade());
1469                if self.dock.is_anchored_at(DockAnchor::Expanded) {
1470                    Dock::hide(self, cx);
1471                }
1472            }
1473            cx.notify();
1474        }
1475
1476        self.update_followers(
1477            proto::update_followers::Variant::UpdateActiveView(proto::UpdateActiveView {
1478                id: self.active_item(cx).and_then(|item| {
1479                    item.to_followable_item_handle(cx)?
1480                        .remote_id(&self.client, cx)
1481                        .map(|id| id.to_proto())
1482                }),
1483                leader_id: self.leader_for_pane(&pane),
1484            }),
1485            cx,
1486        );
1487    }
1488
1489    fn handle_pane_event(
1490        &mut self,
1491        pane_id: usize,
1492        event: &pane::Event,
1493        cx: &mut ViewContext<Self>,
1494    ) {
1495        if let Some(pane) = self.pane(pane_id) {
1496            let is_dock = &pane == self.dock.pane();
1497            match event {
1498                pane::Event::Split(direction) if !is_dock => {
1499                    self.split_pane(pane, *direction, cx);
1500                }
1501                pane::Event::Remove if !is_dock => self.remove_pane(pane, cx),
1502                pane::Event::Remove if is_dock => Dock::hide(self, cx),
1503                pane::Event::ActivateItem { local } => {
1504                    if *local {
1505                        self.unfollow(&pane, cx);
1506                    }
1507                    if &pane == self.active_pane() {
1508                        self.active_item_path_changed(cx);
1509                    }
1510                }
1511                pane::Event::ChangeItemTitle => {
1512                    if pane == self.active_pane {
1513                        self.active_item_path_changed(cx);
1514                    }
1515                    self.update_window_edited(cx);
1516                }
1517                pane::Event::RemoveItem { item_id } => {
1518                    self.update_window_edited(cx);
1519                    if let hash_map::Entry::Occupied(entry) = self.panes_by_item.entry(*item_id) {
1520                        if entry.get().id() == pane.id() {
1521                            entry.remove();
1522                        }
1523                    }
1524                }
1525                _ => {}
1526            }
1527
1528            self.serialize_workspace(cx);
1529        } else if self.dock.visible_pane().is_none() {
1530            error!("pane {} not found", pane_id);
1531        }
1532    }
1533
1534    pub fn split_pane(
1535        &mut self,
1536        pane: ViewHandle<Pane>,
1537        direction: SplitDirection,
1538        cx: &mut ViewContext<Self>,
1539    ) -> Option<ViewHandle<Pane>> {
1540        if &pane == self.dock_pane() {
1541            warn!("Can't split dock pane.");
1542            return None;
1543        }
1544
1545        let item = pane.read(cx).active_item()?;
1546        let new_pane = self.add_pane(cx);
1547        if let Some(clone) = item.clone_on_split(self.database_id(), cx.as_mut()) {
1548            Pane::add_item(self, &new_pane, clone, true, true, None, cx);
1549        }
1550        self.center.split(&pane, &new_pane, direction).unwrap();
1551        cx.notify();
1552        Some(new_pane)
1553    }
1554
1555    pub fn split_pane_with_item(&mut self, action: &SplitWithItem, cx: &mut ViewContext<Self>) {
1556        let Some(pane_to_split) = action.pane_to_split.upgrade(cx) else { return; };
1557        let Some(from) = action.from.upgrade(cx) else { return; };
1558        if &pane_to_split == self.dock_pane() {
1559            warn!("Can't split dock pane.");
1560            return;
1561        }
1562
1563        let new_pane = self.add_pane(cx);
1564        Pane::move_item(
1565            self,
1566            from.clone(),
1567            new_pane.clone(),
1568            action.item_id_to_move,
1569            0,
1570            cx,
1571        );
1572        self.center
1573            .split(&pane_to_split, &new_pane, action.split_direction)
1574            .unwrap();
1575        cx.notify();
1576    }
1577
1578    pub fn split_pane_with_project_entry(
1579        &mut self,
1580        action: &SplitWithProjectEntry,
1581        cx: &mut ViewContext<Self>,
1582    ) -> Option<Task<Result<()>>> {
1583        let pane_to_split = action.pane_to_split.upgrade(cx)?;
1584        if &pane_to_split == self.dock_pane() {
1585            warn!("Can't split dock pane.");
1586            return None;
1587        }
1588
1589        let new_pane = self.add_pane(cx);
1590        self.center
1591            .split(&pane_to_split, &new_pane, action.split_direction)
1592            .unwrap();
1593
1594        let path = self
1595            .project
1596            .read(cx)
1597            .path_for_entry(action.project_entry, cx)?;
1598        let task = self.open_path(path, Some(new_pane.downgrade()), true, cx);
1599        Some(cx.foreground().spawn(async move {
1600            task.await?;
1601            Ok(())
1602        }))
1603    }
1604
1605    fn remove_pane(&mut self, pane: ViewHandle<Pane>, cx: &mut ViewContext<Self>) {
1606        if self.center.remove(&pane).unwrap() {
1607            self.panes.retain(|p| p != &pane);
1608            cx.focus(self.panes.last().unwrap().clone());
1609            self.unfollow(&pane, cx);
1610            self.last_leaders_by_pane.remove(&pane.downgrade());
1611            for removed_item in pane.read(cx).items() {
1612                self.panes_by_item.remove(&removed_item.id());
1613            }
1614            if self.last_active_center_pane == Some(pane.downgrade()) {
1615                self.last_active_center_pane = None;
1616            }
1617
1618            cx.notify();
1619        } else {
1620            self.active_item_path_changed(cx);
1621        }
1622    }
1623
1624    pub fn panes(&self) -> &[ViewHandle<Pane>] {
1625        &self.panes
1626    }
1627
1628    fn pane(&self, pane_id: usize) -> Option<ViewHandle<Pane>> {
1629        self.panes.iter().find(|pane| pane.id() == pane_id).cloned()
1630    }
1631
1632    pub fn active_pane(&self) -> &ViewHandle<Pane> {
1633        &self.active_pane
1634    }
1635
1636    pub fn dock_pane(&self) -> &ViewHandle<Pane> {
1637        self.dock.pane()
1638    }
1639
1640    fn project_remote_id_changed(&mut self, remote_id: Option<u64>, cx: &mut ViewContext<Self>) {
1641        if let Some(remote_id) = remote_id {
1642            self.remote_entity_subscription =
1643                Some(self.client.add_view_for_remote_entity(remote_id, cx));
1644        } else {
1645            self.remote_entity_subscription.take();
1646        }
1647    }
1648
1649    fn collaborator_left(&mut self, peer_id: PeerId, cx: &mut ViewContext<Self>) {
1650        self.leader_state.followers.remove(&peer_id);
1651        if let Some(states_by_pane) = self.follower_states_by_leader.remove(&peer_id) {
1652            for state in states_by_pane.into_values() {
1653                for item in state.items_by_leader_view_id.into_values() {
1654                    item.set_leader_replica_id(None, cx);
1655                }
1656            }
1657        }
1658        cx.notify();
1659    }
1660
1661    pub fn toggle_follow(
1662        &mut self,
1663        ToggleFollow(leader_id): &ToggleFollow,
1664        cx: &mut ViewContext<Self>,
1665    ) -> Option<Task<Result<()>>> {
1666        let leader_id = *leader_id;
1667        let pane = self.active_pane().clone();
1668
1669        if let Some(prev_leader_id) = self.unfollow(&pane, cx) {
1670            if leader_id == prev_leader_id {
1671                return None;
1672            }
1673        }
1674
1675        self.last_leaders_by_pane
1676            .insert(pane.downgrade(), leader_id);
1677        self.follower_states_by_leader
1678            .entry(leader_id)
1679            .or_default()
1680            .insert(pane.clone(), Default::default());
1681        cx.notify();
1682
1683        let project_id = self.project.read(cx).remote_id()?;
1684        let request = self.client.request(proto::Follow {
1685            project_id,
1686            leader_id: Some(leader_id),
1687        });
1688
1689        Some(cx.spawn_weak(|this, mut cx| async move {
1690            let response = request.await?;
1691            if let Some(this) = this.upgrade(&cx) {
1692                this.update(&mut cx, |this, _| {
1693                    let state = this
1694                        .follower_states_by_leader
1695                        .get_mut(&leader_id)
1696                        .and_then(|states_by_pane| states_by_pane.get_mut(&pane))
1697                        .ok_or_else(|| anyhow!("following interrupted"))?;
1698                    state.active_view_id = if let Some(active_view_id) = response.active_view_id {
1699                        Some(ViewId::from_proto(active_view_id)?)
1700                    } else {
1701                        None
1702                    };
1703                    Ok::<_, anyhow::Error>(())
1704                })?;
1705                Self::add_views_from_leader(
1706                    this.clone(),
1707                    leader_id,
1708                    vec![pane],
1709                    response.views,
1710                    &mut cx,
1711                )
1712                .await?;
1713                this.update(&mut cx, |this, cx| this.leader_updated(leader_id, cx));
1714            }
1715            Ok(())
1716        }))
1717    }
1718
1719    pub fn follow_next_collaborator(
1720        &mut self,
1721        _: &FollowNextCollaborator,
1722        cx: &mut ViewContext<Self>,
1723    ) -> Option<Task<Result<()>>> {
1724        let collaborators = self.project.read(cx).collaborators();
1725        let next_leader_id = if let Some(leader_id) = self.leader_for_pane(&self.active_pane) {
1726            let mut collaborators = collaborators.keys().copied();
1727            for peer_id in collaborators.by_ref() {
1728                if peer_id == leader_id {
1729                    break;
1730                }
1731            }
1732            collaborators.next()
1733        } else if let Some(last_leader_id) =
1734            self.last_leaders_by_pane.get(&self.active_pane.downgrade())
1735        {
1736            if collaborators.contains_key(last_leader_id) {
1737                Some(*last_leader_id)
1738            } else {
1739                None
1740            }
1741        } else {
1742            None
1743        };
1744
1745        next_leader_id
1746            .or_else(|| collaborators.keys().copied().next())
1747            .and_then(|leader_id| self.toggle_follow(&ToggleFollow(leader_id), cx))
1748    }
1749
1750    pub fn unfollow(
1751        &mut self,
1752        pane: &ViewHandle<Pane>,
1753        cx: &mut ViewContext<Self>,
1754    ) -> Option<PeerId> {
1755        for (leader_id, states_by_pane) in &mut self.follower_states_by_leader {
1756            let leader_id = *leader_id;
1757            if let Some(state) = states_by_pane.remove(pane) {
1758                for (_, item) in state.items_by_leader_view_id {
1759                    item.set_leader_replica_id(None, cx);
1760                }
1761
1762                if states_by_pane.is_empty() {
1763                    self.follower_states_by_leader.remove(&leader_id);
1764                    if let Some(project_id) = self.project.read(cx).remote_id() {
1765                        self.client
1766                            .send(proto::Unfollow {
1767                                project_id,
1768                                leader_id: Some(leader_id),
1769                            })
1770                            .log_err();
1771                    }
1772                }
1773
1774                cx.notify();
1775                return Some(leader_id);
1776            }
1777        }
1778        None
1779    }
1780
1781    pub fn is_following(&self, peer_id: PeerId) -> bool {
1782        self.follower_states_by_leader.contains_key(&peer_id)
1783    }
1784
1785    pub fn is_followed(&self, peer_id: PeerId) -> bool {
1786        self.leader_state.followers.contains(&peer_id)
1787    }
1788
1789    fn render_titlebar(&self, theme: &Theme, cx: &mut RenderContext<Self>) -> ElementBox {
1790        let project = &self.project.read(cx);
1791        let mut worktree_root_names = String::new();
1792        for (i, name) in project.worktree_root_names(cx).enumerate() {
1793            if i > 0 {
1794                worktree_root_names.push_str(", ");
1795            }
1796            worktree_root_names.push_str(name);
1797        }
1798
1799        // TODO: There should be a better system in place for this
1800        // (https://github.com/zed-industries/zed/issues/1290)
1801        let is_fullscreen = cx.window_is_fullscreen(cx.window_id());
1802        let container_theme = if is_fullscreen {
1803            let mut container_theme = theme.workspace.titlebar.container;
1804            container_theme.padding.left = container_theme.padding.right;
1805            container_theme
1806        } else {
1807            theme.workspace.titlebar.container
1808        };
1809
1810        enum TitleBar {}
1811        ConstrainedBox::new(
1812            MouseEventHandler::<TitleBar>::new(0, cx, |_, cx| {
1813                Container::new(
1814                    Stack::new()
1815                        .with_child(
1816                            Label::new(worktree_root_names, theme.workspace.titlebar.title.clone())
1817                                .aligned()
1818                                .left()
1819                                .boxed(),
1820                        )
1821                        .with_children(
1822                            self.titlebar_item
1823                                .as_ref()
1824                                .map(|item| ChildView::new(item, cx).aligned().right().boxed()),
1825                        )
1826                        .boxed(),
1827                )
1828                .with_style(container_theme)
1829                .boxed()
1830            })
1831            .on_click(MouseButton::Left, |event, cx| {
1832                if event.click_count == 2 {
1833                    cx.zoom_window(cx.window_id());
1834                }
1835            })
1836            .boxed(),
1837        )
1838        .with_height(theme.workspace.titlebar.height)
1839        .named("titlebar")
1840    }
1841
1842    fn active_item_path_changed(&mut self, cx: &mut ViewContext<Self>) {
1843        let active_entry = self.active_project_path(cx);
1844        self.project
1845            .update(cx, |project, cx| project.set_active_path(active_entry, cx));
1846        self.update_window_title(cx);
1847    }
1848
1849    fn update_window_title(&mut self, cx: &mut ViewContext<Self>) {
1850        let project = self.project().read(cx);
1851        let mut title = String::new();
1852
1853        if let Some(path) = self.active_item(cx).and_then(|item| item.project_path(cx)) {
1854            let filename = path
1855                .path
1856                .file_name()
1857                .map(|s| s.to_string_lossy())
1858                .or_else(|| {
1859                    Some(Cow::Borrowed(
1860                        project
1861                            .worktree_for_id(path.worktree_id, cx)?
1862                            .read(cx)
1863                            .root_name(),
1864                    ))
1865                });
1866
1867            if let Some(filename) = filename {
1868                title.push_str(filename.as_ref());
1869                title.push_str("");
1870            }
1871        }
1872
1873        for (i, name) in project.worktree_root_names(cx).enumerate() {
1874            if i > 0 {
1875                title.push_str(", ");
1876            }
1877            title.push_str(name);
1878        }
1879
1880        if title.is_empty() {
1881            title = "empty project".to_string();
1882        }
1883
1884        if project.is_remote() {
1885            title.push_str("");
1886        } else if project.is_shared() {
1887            title.push_str("");
1888        }
1889
1890        cx.set_window_title(&title);
1891    }
1892
1893    fn update_window_edited(&mut self, cx: &mut ViewContext<Self>) {
1894        let is_edited = !self.project.read(cx).is_read_only()
1895            && self
1896                .items(cx)
1897                .any(|item| item.has_conflict(cx) || item.is_dirty(cx));
1898        if is_edited != self.window_edited {
1899            self.window_edited = is_edited;
1900            cx.set_window_edited(self.window_edited)
1901        }
1902    }
1903
1904    fn render_disconnected_overlay(&self, cx: &mut RenderContext<Workspace>) -> Option<ElementBox> {
1905        if self.project.read(cx).is_read_only() {
1906            enum DisconnectedOverlay {}
1907            Some(
1908                MouseEventHandler::<DisconnectedOverlay>::new(0, cx, |_, cx| {
1909                    let theme = &cx.global::<Settings>().theme;
1910                    Label::new(
1911                        "Your connection to the remote project has been lost.".to_string(),
1912                        theme.workspace.disconnected_overlay.text.clone(),
1913                    )
1914                    .aligned()
1915                    .contained()
1916                    .with_style(theme.workspace.disconnected_overlay.container)
1917                    .boxed()
1918                })
1919                .with_cursor_style(CursorStyle::Arrow)
1920                .capture_all()
1921                .boxed(),
1922            )
1923        } else {
1924            None
1925        }
1926    }
1927
1928    fn render_notifications(
1929        &self,
1930        theme: &theme::Workspace,
1931        cx: &AppContext,
1932    ) -> Option<ElementBox> {
1933        if self.notifications.is_empty() {
1934            None
1935        } else {
1936            Some(
1937                Flex::column()
1938                    .with_children(self.notifications.iter().map(|(_, _, notification)| {
1939                        ChildView::new(notification.as_ref(), cx)
1940                            .contained()
1941                            .with_style(theme.notification)
1942                            .boxed()
1943                    }))
1944                    .constrained()
1945                    .with_width(theme.notifications.width)
1946                    .contained()
1947                    .with_style(theme.notifications.container)
1948                    .aligned()
1949                    .bottom()
1950                    .right()
1951                    .boxed(),
1952            )
1953        }
1954    }
1955
1956    // RPC handlers
1957
1958    async fn handle_follow(
1959        this: ViewHandle<Self>,
1960        envelope: TypedEnvelope<proto::Follow>,
1961        _: Arc<Client>,
1962        mut cx: AsyncAppContext,
1963    ) -> Result<proto::FollowResponse> {
1964        this.update(&mut cx, |this, cx| {
1965            let client = &this.client;
1966            this.leader_state
1967                .followers
1968                .insert(envelope.original_sender_id()?);
1969
1970            let active_view_id = this.active_item(cx).and_then(|i| {
1971                Some(
1972                    i.to_followable_item_handle(cx)?
1973                        .remote_id(client, cx)?
1974                        .to_proto(),
1975                )
1976            });
1977
1978            cx.notify();
1979
1980            Ok(proto::FollowResponse {
1981                active_view_id,
1982                views: this
1983                    .panes()
1984                    .iter()
1985                    .flat_map(|pane| {
1986                        let leader_id = this.leader_for_pane(pane);
1987                        pane.read(cx).items().filter_map({
1988                            let cx = &cx;
1989                            move |item| {
1990                                let item = item.to_followable_item_handle(cx)?;
1991                                let id = item.remote_id(client, cx)?.to_proto();
1992                                let variant = item.to_state_proto(cx)?;
1993                                Some(proto::View {
1994                                    id: Some(id),
1995                                    leader_id,
1996                                    variant: Some(variant),
1997                                })
1998                            }
1999                        })
2000                    })
2001                    .collect(),
2002            })
2003        })
2004    }
2005
2006    async fn handle_unfollow(
2007        this: ViewHandle<Self>,
2008        envelope: TypedEnvelope<proto::Unfollow>,
2009        _: Arc<Client>,
2010        mut cx: AsyncAppContext,
2011    ) -> Result<()> {
2012        this.update(&mut cx, |this, cx| {
2013            this.leader_state
2014                .followers
2015                .remove(&envelope.original_sender_id()?);
2016            cx.notify();
2017            Ok(())
2018        })
2019    }
2020
2021    async fn handle_update_followers(
2022        this: ViewHandle<Self>,
2023        envelope: TypedEnvelope<proto::UpdateFollowers>,
2024        _: Arc<Client>,
2025        cx: AsyncAppContext,
2026    ) -> Result<()> {
2027        let leader_id = envelope.original_sender_id()?;
2028        this.read_with(&cx, |this, _| {
2029            this.leader_updates_tx
2030                .unbounded_send((leader_id, envelope.payload))
2031        })?;
2032        Ok(())
2033    }
2034
2035    async fn process_leader_update(
2036        this: ViewHandle<Self>,
2037        leader_id: PeerId,
2038        update: proto::UpdateFollowers,
2039        cx: &mut AsyncAppContext,
2040    ) -> Result<()> {
2041        match update.variant.ok_or_else(|| anyhow!("invalid update"))? {
2042            proto::update_followers::Variant::UpdateActiveView(update_active_view) => {
2043                this.update(cx, |this, _| {
2044                    if let Some(state) = this.follower_states_by_leader.get_mut(&leader_id) {
2045                        for state in state.values_mut() {
2046                            state.active_view_id =
2047                                if let Some(active_view_id) = update_active_view.id.clone() {
2048                                    Some(ViewId::from_proto(active_view_id)?)
2049                                } else {
2050                                    None
2051                                };
2052                        }
2053                    }
2054                    anyhow::Ok(())
2055                })?;
2056            }
2057            proto::update_followers::Variant::UpdateView(update_view) => {
2058                let variant = update_view
2059                    .variant
2060                    .ok_or_else(|| anyhow!("missing update view variant"))?;
2061                let id = update_view
2062                    .id
2063                    .ok_or_else(|| anyhow!("missing update view id"))?;
2064                let mut tasks = Vec::new();
2065                this.update(cx, |this, cx| {
2066                    let project = this.project.clone();
2067                    if let Some(state) = this.follower_states_by_leader.get_mut(&leader_id) {
2068                        for state in state.values_mut() {
2069                            let view_id = ViewId::from_proto(id.clone())?;
2070                            if let Some(item) = state.items_by_leader_view_id.get(&view_id) {
2071                                tasks.push(item.apply_update_proto(&project, variant.clone(), cx));
2072                            }
2073                        }
2074                    }
2075                    anyhow::Ok(())
2076                })?;
2077                try_join_all(tasks).await.log_err();
2078            }
2079            proto::update_followers::Variant::CreateView(view) => {
2080                let panes = this.read_with(cx, |this, _| {
2081                    this.follower_states_by_leader
2082                        .get(&leader_id)
2083                        .into_iter()
2084                        .flat_map(|states_by_pane| states_by_pane.keys())
2085                        .cloned()
2086                        .collect()
2087                });
2088                Self::add_views_from_leader(this.clone(), leader_id, panes, vec![view], cx).await?;
2089            }
2090        }
2091        this.update(cx, |this, cx| this.leader_updated(leader_id, cx));
2092        Ok(())
2093    }
2094
2095    async fn add_views_from_leader(
2096        this: ViewHandle<Self>,
2097        leader_id: PeerId,
2098        panes: Vec<ViewHandle<Pane>>,
2099        views: Vec<proto::View>,
2100        cx: &mut AsyncAppContext,
2101    ) -> Result<()> {
2102        let project = this.read_with(cx, |this, _| this.project.clone());
2103        let replica_id = project
2104            .read_with(cx, |project, _| {
2105                project
2106                    .collaborators()
2107                    .get(&leader_id)
2108                    .map(|c| c.replica_id)
2109            })
2110            .ok_or_else(|| anyhow!("no such collaborator {}", leader_id))?;
2111
2112        let item_builders = cx.update(|cx| {
2113            cx.default_global::<FollowableItemBuilders>()
2114                .values()
2115                .map(|b| b.0)
2116                .collect::<Vec<_>>()
2117        });
2118
2119        let mut item_tasks_by_pane = HashMap::default();
2120        for pane in panes {
2121            let mut item_tasks = Vec::new();
2122            let mut leader_view_ids = Vec::new();
2123            for view in &views {
2124                let Some(id) = &view.id else { continue };
2125                let id = ViewId::from_proto(id.clone())?;
2126                let mut variant = view.variant.clone();
2127                if variant.is_none() {
2128                    Err(anyhow!("missing variant"))?;
2129                }
2130                for build_item in &item_builders {
2131                    let task = cx.update(|cx| {
2132                        build_item(pane.clone(), project.clone(), id, &mut variant, cx)
2133                    });
2134                    if let Some(task) = task {
2135                        item_tasks.push(task);
2136                        leader_view_ids.push(id);
2137                        break;
2138                    } else {
2139                        assert!(variant.is_some());
2140                    }
2141                }
2142            }
2143
2144            item_tasks_by_pane.insert(pane, (item_tasks, leader_view_ids));
2145        }
2146
2147        for (pane, (item_tasks, leader_view_ids)) in item_tasks_by_pane {
2148            let items = futures::future::try_join_all(item_tasks).await?;
2149            this.update(cx, |this, cx| {
2150                let state = this
2151                    .follower_states_by_leader
2152                    .get_mut(&leader_id)?
2153                    .get_mut(&pane)?;
2154
2155                for (id, item) in leader_view_ids.into_iter().zip(items) {
2156                    item.set_leader_replica_id(Some(replica_id), cx);
2157                    state.items_by_leader_view_id.insert(id, item);
2158                }
2159
2160                Some(())
2161            });
2162        }
2163        Ok(())
2164    }
2165
2166    fn update_followers(
2167        &self,
2168        update: proto::update_followers::Variant,
2169        cx: &AppContext,
2170    ) -> Option<()> {
2171        let project_id = self.project.read(cx).remote_id()?;
2172        if !self.leader_state.followers.is_empty() {
2173            self.client
2174                .send(proto::UpdateFollowers {
2175                    project_id,
2176                    follower_ids: self.leader_state.followers.iter().copied().collect(),
2177                    variant: Some(update),
2178                })
2179                .log_err();
2180        }
2181        None
2182    }
2183
2184    pub fn leader_for_pane(&self, pane: &ViewHandle<Pane>) -> Option<PeerId> {
2185        self.follower_states_by_leader
2186            .iter()
2187            .find_map(|(leader_id, state)| {
2188                if state.contains_key(pane) {
2189                    Some(*leader_id)
2190                } else {
2191                    None
2192                }
2193            })
2194    }
2195
2196    fn leader_updated(&mut self, leader_id: PeerId, cx: &mut ViewContext<Self>) -> Option<()> {
2197        cx.notify();
2198
2199        let call = self.active_call()?;
2200        let room = call.read(cx).room()?.read(cx);
2201        let participant = room.remote_participant_for_peer_id(leader_id)?;
2202        let mut items_to_activate = Vec::new();
2203        match participant.location {
2204            call::ParticipantLocation::SharedProject { project_id } => {
2205                if Some(project_id) == self.project.read(cx).remote_id() {
2206                    for (pane, state) in self.follower_states_by_leader.get(&leader_id)? {
2207                        if let Some(item) = state
2208                            .active_view_id
2209                            .and_then(|id| state.items_by_leader_view_id.get(&id))
2210                        {
2211                            items_to_activate.push((pane.clone(), item.boxed_clone()));
2212                        } else {
2213                            if let Some(shared_screen) =
2214                                self.shared_screen_for_peer(leader_id, pane, cx)
2215                            {
2216                                items_to_activate.push((pane.clone(), Box::new(shared_screen)));
2217                            }
2218                        }
2219                    }
2220                }
2221            }
2222            call::ParticipantLocation::UnsharedProject => {}
2223            call::ParticipantLocation::External => {
2224                for (pane, _) in self.follower_states_by_leader.get(&leader_id)? {
2225                    if let Some(shared_screen) = self.shared_screen_for_peer(leader_id, pane, cx) {
2226                        items_to_activate.push((pane.clone(), Box::new(shared_screen)));
2227                    }
2228                }
2229            }
2230        }
2231
2232        for (pane, item) in items_to_activate {
2233            let active_item_was_focused = pane
2234                .read(cx)
2235                .active_item()
2236                .map(|active_item| cx.is_child_focused(active_item.to_any()))
2237                .unwrap_or_default();
2238
2239            if let Some(index) = pane.update(cx, |pane, _| pane.index_for_item(item.as_ref())) {
2240                pane.update(cx, |pane, cx| pane.activate_item(index, false, false, cx));
2241            } else {
2242                Pane::add_item(self, &pane, item.boxed_clone(), false, false, None, cx);
2243            }
2244
2245            if active_item_was_focused {
2246                pane.update(cx, |pane, cx| pane.focus_active_item(cx));
2247            }
2248        }
2249
2250        None
2251    }
2252
2253    fn shared_screen_for_peer(
2254        &self,
2255        peer_id: PeerId,
2256        pane: &ViewHandle<Pane>,
2257        cx: &mut ViewContext<Self>,
2258    ) -> Option<ViewHandle<SharedScreen>> {
2259        let call = self.active_call()?;
2260        let room = call.read(cx).room()?.read(cx);
2261        let participant = room.remote_participant_for_peer_id(peer_id)?;
2262        let track = participant.tracks.values().next()?.clone();
2263        let user = participant.user.clone();
2264
2265        for item in pane.read(cx).items_of_type::<SharedScreen>() {
2266            if item.read(cx).peer_id == peer_id {
2267                return Some(item);
2268            }
2269        }
2270
2271        Some(cx.add_view(|cx| SharedScreen::new(&track, peer_id, user.clone(), cx)))
2272    }
2273
2274    pub fn on_window_activation_changed(&mut self, active: bool, cx: &mut ViewContext<Self>) {
2275        if active {
2276            cx.background()
2277                .spawn(persistence::DB.update_timestamp(self.database_id()))
2278                .detach();
2279        } else {
2280            for pane in &self.panes {
2281                pane.update(cx, |pane, cx| {
2282                    if let Some(item) = pane.active_item() {
2283                        item.workspace_deactivated(cx);
2284                    }
2285                    if matches!(
2286                        cx.global::<Settings>().autosave,
2287                        Autosave::OnWindowChange | Autosave::OnFocusChange
2288                    ) {
2289                        for item in pane.items() {
2290                            Pane::autosave_item(item.as_ref(), self.project.clone(), cx)
2291                                .detach_and_log_err(cx);
2292                        }
2293                    }
2294                });
2295            }
2296        }
2297    }
2298
2299    fn active_call(&self) -> Option<&ModelHandle<ActiveCall>> {
2300        self.active_call.as_ref().map(|(call, _)| call)
2301    }
2302
2303    fn on_active_call_event(
2304        &mut self,
2305        _: ModelHandle<ActiveCall>,
2306        event: &call::room::Event,
2307        cx: &mut ViewContext<Self>,
2308    ) {
2309        match event {
2310            call::room::Event::ParticipantLocationChanged { participant_id }
2311            | call::room::Event::RemoteVideoTracksChanged { participant_id } => {
2312                self.leader_updated(*participant_id, cx);
2313            }
2314            _ => {}
2315        }
2316    }
2317
2318    pub fn database_id(&self) -> WorkspaceId {
2319        self.database_id
2320    }
2321
2322    fn location(&self, cx: &AppContext) -> Option<WorkspaceLocation> {
2323        let project = self.project().read(cx);
2324
2325        if project.is_local() {
2326            Some(
2327                project
2328                    .visible_worktrees(cx)
2329                    .map(|worktree| worktree.read(cx).abs_path())
2330                    .collect::<Vec<_>>()
2331                    .into(),
2332            )
2333        } else {
2334            None
2335        }
2336    }
2337
2338    fn remove_panes(&mut self, member: Member, cx: &mut ViewContext<Workspace>) {
2339        match member {
2340            Member::Axis(PaneAxis { members, .. }) => {
2341                for child in members.iter() {
2342                    self.remove_panes(child.clone(), cx)
2343                }
2344            }
2345            Member::Pane(pane) => self.remove_pane(pane.clone(), cx),
2346        }
2347    }
2348
2349    fn serialize_workspace(&self, cx: &AppContext) {
2350        fn serialize_pane_handle(
2351            pane_handle: &ViewHandle<Pane>,
2352            cx: &AppContext,
2353        ) -> SerializedPane {
2354            let (items, active) = {
2355                let pane = pane_handle.read(cx);
2356                let active_item_id = pane.active_item().map(|item| item.id());
2357                (
2358                    pane.items()
2359                        .filter_map(|item_handle| {
2360                            Some(SerializedItem {
2361                                kind: Arc::from(item_handle.serialized_item_kind()?),
2362                                item_id: item_handle.id(),
2363                                active: Some(item_handle.id()) == active_item_id,
2364                            })
2365                        })
2366                        .collect::<Vec<_>>(),
2367                    pane.is_active(),
2368                )
2369            };
2370
2371            SerializedPane::new(items, active)
2372        }
2373
2374        fn build_serialized_pane_group(
2375            pane_group: &Member,
2376            cx: &AppContext,
2377        ) -> SerializedPaneGroup {
2378            match pane_group {
2379                Member::Axis(PaneAxis { axis, members }) => SerializedPaneGroup::Group {
2380                    axis: *axis,
2381                    children: members
2382                        .iter()
2383                        .map(|member| build_serialized_pane_group(member, cx))
2384                        .collect::<Vec<_>>(),
2385                },
2386                Member::Pane(pane_handle) => {
2387                    SerializedPaneGroup::Pane(serialize_pane_handle(&pane_handle, cx))
2388                }
2389            }
2390        }
2391
2392        if let Some(location) = self.location(cx) {
2393            // Load bearing special case:
2394            //  - with_local_workspace() relies on this to not have other stuff open
2395            //    when you open your log
2396            if !location.paths().is_empty() {
2397                let dock_pane = serialize_pane_handle(self.dock.pane(), cx);
2398                let center_group = build_serialized_pane_group(&self.center.root, cx);
2399
2400                let serialized_workspace = SerializedWorkspace {
2401                    id: self.database_id,
2402                    location,
2403                    dock_position: self.dock.position(),
2404                    dock_pane,
2405                    center_group,
2406                    left_sidebar_open: self.left_sidebar.read(cx).is_open(),
2407                    bounds: Default::default(),
2408                    display: Default::default(),
2409                };
2410
2411                cx.background()
2412                    .spawn(persistence::DB.save_workspace(serialized_workspace))
2413                    .detach();
2414            }
2415        }
2416    }
2417
2418    fn load_from_serialized_workspace(
2419        workspace: WeakViewHandle<Workspace>,
2420        serialized_workspace: SerializedWorkspace,
2421        cx: &mut MutableAppContext,
2422    ) {
2423        cx.spawn(|mut cx| async move {
2424            if let Some(workspace) = workspace.upgrade(&cx) {
2425                let (project, dock_pane_handle, old_center_pane) =
2426                    workspace.read_with(&cx, |workspace, _| {
2427                        (
2428                            workspace.project().clone(),
2429                            workspace.dock_pane().clone(),
2430                            workspace.last_active_center_pane.clone(),
2431                        )
2432                    });
2433
2434                serialized_workspace
2435                    .dock_pane
2436                    .deserialize_to(
2437                        &project,
2438                        &dock_pane_handle,
2439                        serialized_workspace.id,
2440                        &workspace,
2441                        &mut cx,
2442                    )
2443                    .await;
2444
2445                // Traverse the splits tree and add to things
2446                let center_group = serialized_workspace
2447                    .center_group
2448                    .deserialize(&project, serialized_workspace.id, &workspace, &mut cx)
2449                    .await;
2450
2451                // Remove old panes from workspace panes list
2452                workspace.update(&mut cx, |workspace, cx| {
2453                    if let Some((center_group, active_pane)) = center_group {
2454                        workspace.remove_panes(workspace.center.root.clone(), cx);
2455
2456                        // Swap workspace center group
2457                        workspace.center = PaneGroup::with_root(center_group);
2458
2459                        // Change the focus to the workspace first so that we retrigger focus in on the pane.
2460                        cx.focus_self();
2461
2462                        if let Some(active_pane) = active_pane {
2463                            cx.focus(active_pane);
2464                        } else {
2465                            cx.focus(workspace.panes.last().unwrap().clone());
2466                        }
2467                    } else {
2468                        let old_center_handle = old_center_pane.and_then(|weak| weak.upgrade(cx));
2469                        if let Some(old_center_handle) = old_center_handle {
2470                            cx.focus(old_center_handle)
2471                        } else {
2472                            cx.focus_self()
2473                        }
2474                    }
2475
2476                    if workspace.left_sidebar().read(cx).is_open()
2477                        != serialized_workspace.left_sidebar_open
2478                    {
2479                        workspace.toggle_sidebar(SidebarSide::Left, cx);
2480                    }
2481
2482                    // Note that without after_window, the focus_self() and
2483                    // the focus the dock generates start generating alternating
2484                    // focus due to the deferred execution each triggering each other
2485                    cx.after_window_update(move |workspace, cx| {
2486                        Dock::set_dock_position(workspace, serialized_workspace.dock_position, cx);
2487                    });
2488
2489                    cx.notify();
2490                });
2491
2492                // Serialize ourself to make sure our timestamps and any pane / item changes are replicated
2493                workspace.read_with(&cx, |workspace, cx| workspace.serialize_workspace(cx))
2494            }
2495        })
2496        .detach();
2497    }
2498}
2499
2500fn notify_if_database_failed(workspace: &ViewHandle<Workspace>, cx: &mut AsyncAppContext) {
2501    if (*db::ALL_FILE_DB_FAILED).load(std::sync::atomic::Ordering::Acquire) {
2502        workspace.update(cx, |workspace, cx| {
2503            workspace.show_notification_once(0, cx, |cx| {
2504                cx.add_view(|_| {
2505                    MessageNotification::new(
2506                        indoc::indoc! {"
2507                            Failed to load any database file :(
2508                        "},
2509                        OsOpen("https://github.com/zed-industries/community/issues/new?assignees=&labels=defect%2Ctriage&template=2_bug_report.yml".to_string()),
2510                        "Click to let us know about this error"
2511                    )
2512                })
2513            });
2514        });
2515    } else {
2516        let backup_path = (*db::BACKUP_DB_PATH).read();
2517        if let Some(backup_path) = &*backup_path {
2518            workspace.update(cx, |workspace, cx| {
2519                workspace.show_notification_once(0, cx, |cx| {
2520                    cx.add_view(|_| {
2521                        let backup_path = backup_path.to_string_lossy();
2522                        MessageNotification::new(
2523                            format!(
2524                                indoc::indoc! {"
2525                                Database file was corrupted :(
2526                                Old database backed up to:
2527                                {}
2528                                "},
2529                                backup_path
2530                            ),
2531                            OsOpen(backup_path.to_string()),
2532                            "Click to show old database in finder",
2533                        )
2534                    })
2535                });
2536            });
2537        }
2538    }
2539}
2540
2541impl Entity for Workspace {
2542    type Event = Event;
2543}
2544
2545impl View for Workspace {
2546    fn ui_name() -> &'static str {
2547        "Workspace"
2548    }
2549
2550    fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
2551        let theme = cx.global::<Settings>().theme.clone();
2552        Stack::new()
2553            .with_child(
2554                Flex::column()
2555                    .with_child(self.render_titlebar(&theme, cx))
2556                    .with_child(
2557                        Stack::new()
2558                            .with_child({
2559                                let project = self.project.clone();
2560                                Flex::row()
2561                                    .with_children(
2562                                        if self.left_sidebar.read(cx).active_item().is_some() {
2563                                            Some(
2564                                                ChildView::new(&self.left_sidebar, cx)
2565                                                    .constrained()
2566                                                    .dynamically(|constraint, cx| {
2567                                                        SizeConstraint::new(
2568                                                            Vector2F::new(20., constraint.min.y()),
2569                                                            Vector2F::new(
2570                                                                cx.window_size.x() * 0.8,
2571                                                                constraint.max.y(),
2572                                                            ),
2573                                                        )
2574                                                    })
2575                                                    .boxed(),
2576                                            )
2577                                        } else {
2578                                            None
2579                                        },
2580                                    )
2581                                    .with_child(
2582                                        FlexItem::new(
2583                                            Flex::column()
2584                                                .with_child(
2585                                                    FlexItem::new(self.center.render(
2586                                                        &project,
2587                                                        &theme,
2588                                                        &self.follower_states_by_leader,
2589                                                        self.active_call(),
2590                                                        self.active_pane(),
2591                                                        cx,
2592                                                    ))
2593                                                    .flex(1., true)
2594                                                    .boxed(),
2595                                                )
2596                                                .with_children(self.dock.render(
2597                                                    &theme,
2598                                                    DockAnchor::Bottom,
2599                                                    cx,
2600                                                ))
2601                                                .boxed(),
2602                                        )
2603                                        .flex(1., true)
2604                                        .boxed(),
2605                                    )
2606                                    .with_children(self.dock.render(&theme, DockAnchor::Right, cx))
2607                                    .with_children(
2608                                        if self.right_sidebar.read(cx).active_item().is_some() {
2609                                            Some(
2610                                                ChildView::new(&self.right_sidebar, cx)
2611                                                    .constrained()
2612                                                    .dynamically(|constraint, cx| {
2613                                                        SizeConstraint::new(
2614                                                            Vector2F::new(20., constraint.min.y()),
2615                                                            Vector2F::new(
2616                                                                cx.window_size.x() * 0.8,
2617                                                                constraint.max.y(),
2618                                                            ),
2619                                                        )
2620                                                    })
2621                                                    .boxed(),
2622                                            )
2623                                        } else {
2624                                            None
2625                                        },
2626                                    )
2627                                    .boxed()
2628                            })
2629                            .with_child(
2630                                Overlay::new(
2631                                    Stack::new()
2632                                        .with_children(self.dock.render(
2633                                            &theme,
2634                                            DockAnchor::Expanded,
2635                                            cx,
2636                                        ))
2637                                        .with_children(self.modal.as_ref().map(|modal| {
2638                                            ChildView::new(modal, cx)
2639                                                .contained()
2640                                                .with_style(theme.workspace.modal)
2641                                                .aligned()
2642                                                .top()
2643                                                .boxed()
2644                                        }))
2645                                        .with_children(
2646                                            self.render_notifications(&theme.workspace, cx),
2647                                        )
2648                                        .boxed(),
2649                                )
2650                                .boxed(),
2651                            )
2652                            .flex(1.0, true)
2653                            .boxed(),
2654                    )
2655                    .with_child(ChildView::new(&self.status_bar, cx).boxed())
2656                    .contained()
2657                    .with_background_color(theme.workspace.background)
2658                    .boxed(),
2659            )
2660            .with_children(DragAndDrop::render(cx))
2661            .with_children(self.render_disconnected_overlay(cx))
2662            .named("workspace")
2663    }
2664
2665    fn focus_in(&mut self, view: AnyViewHandle, cx: &mut ViewContext<Self>) {
2666        if cx.is_self_focused() {
2667            cx.focus(&self.active_pane);
2668        } else {
2669            for pane in self.panes() {
2670                let view = view.clone();
2671                if pane.update(cx, |_, cx| view.id() == cx.view_id() || cx.is_child(view)) {
2672                    self.handle_pane_focused(pane.clone(), cx);
2673                    break;
2674                }
2675            }
2676        }
2677    }
2678
2679    fn keymap_context(&self, _: &AppContext) -> KeymapContext {
2680        let mut keymap = Self::default_keymap_context();
2681        if self.active_pane() == self.dock_pane() {
2682            keymap.set.insert("Dock".into());
2683        }
2684        keymap
2685    }
2686}
2687
2688impl ViewId {
2689    pub(crate) fn from_proto(message: proto::ViewId) -> Result<Self> {
2690        Ok(Self {
2691            creator: message
2692                .creator
2693                .ok_or_else(|| anyhow!("creator is missing"))?,
2694            id: message.id,
2695        })
2696    }
2697
2698    pub(crate) fn to_proto(&self) -> proto::ViewId {
2699        proto::ViewId {
2700            creator: Some(self.creator),
2701            id: self.id,
2702        }
2703    }
2704}
2705
2706pub trait WorkspaceHandle {
2707    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath>;
2708}
2709
2710impl WorkspaceHandle for ViewHandle<Workspace> {
2711    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath> {
2712        self.read(cx)
2713            .worktrees(cx)
2714            .flat_map(|worktree| {
2715                let worktree_id = worktree.read(cx).id();
2716                worktree.read(cx).files(true, 0).map(move |f| ProjectPath {
2717                    worktree_id,
2718                    path: f.path.clone(),
2719                })
2720            })
2721            .collect::<Vec<_>>()
2722    }
2723}
2724
2725impl std::fmt::Debug for OpenPaths {
2726    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2727        f.debug_struct("OpenPaths")
2728            .field("paths", &self.paths)
2729            .finish()
2730    }
2731}
2732
2733fn open(_: &Open, cx: &mut MutableAppContext) {
2734    let mut paths = cx.prompt_for_paths(PathPromptOptions {
2735        files: true,
2736        directories: true,
2737        multiple: true,
2738    });
2739    cx.spawn(|mut cx| async move {
2740        if let Some(paths) = paths.recv().await.flatten() {
2741            cx.update(|cx| cx.dispatch_global_action(OpenPaths { paths }));
2742        }
2743    })
2744    .detach();
2745}
2746
2747pub struct WorkspaceCreated(WeakViewHandle<Workspace>);
2748
2749pub fn activate_workspace_for_project(
2750    cx: &mut MutableAppContext,
2751    predicate: impl Fn(&mut Project, &mut ModelContext<Project>) -> bool,
2752) -> Option<ViewHandle<Workspace>> {
2753    for window_id in cx.window_ids().collect::<Vec<_>>() {
2754        if let Some(workspace_handle) = cx.root_view::<Workspace>(window_id) {
2755            let project = workspace_handle.read(cx).project.clone();
2756            if project.update(cx, &predicate) {
2757                cx.activate_window(window_id);
2758                return Some(workspace_handle);
2759            }
2760        }
2761    }
2762    None
2763}
2764
2765pub async fn last_opened_workspace_paths() -> Option<WorkspaceLocation> {
2766    DB.last_workspace().await.log_err().flatten()
2767}
2768
2769#[allow(clippy::type_complexity)]
2770pub fn open_paths(
2771    abs_paths: &[PathBuf],
2772    app_state: &Arc<AppState>,
2773    cx: &mut MutableAppContext,
2774) -> Task<(
2775    ViewHandle<Workspace>,
2776    Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
2777)> {
2778    log::info!("open paths {:?}", abs_paths);
2779
2780    // Open paths in existing workspace if possible
2781    let existing =
2782        activate_workspace_for_project(cx, |project, cx| project.contains_paths(abs_paths, cx));
2783
2784    let app_state = app_state.clone();
2785    let abs_paths = abs_paths.to_vec();
2786    cx.spawn(|mut cx| async move {
2787        if let Some(existing) = existing {
2788            (
2789                existing.clone(),
2790                existing
2791                    .update(&mut cx, |workspace, cx| {
2792                        workspace.open_paths(abs_paths, true, cx)
2793                    })
2794                    .await,
2795            )
2796        } else {
2797            let contains_directory =
2798                futures::future::join_all(abs_paths.iter().map(|path| app_state.fs.is_file(path)))
2799                    .await
2800                    .contains(&false);
2801
2802            cx.update(|cx| {
2803                let task = Workspace::new_local(abs_paths, app_state.clone(), cx);
2804
2805                cx.spawn(|mut cx| async move {
2806                    let (workspace, items) = task.await;
2807
2808                    workspace.update(&mut cx, |workspace, cx| {
2809                        if contains_directory {
2810                            workspace.toggle_sidebar(SidebarSide::Left, cx);
2811                        }
2812                    });
2813
2814                    (workspace, items)
2815                })
2816            })
2817            .await
2818        }
2819    })
2820}
2821
2822pub fn open_new(app_state: &Arc<AppState>, cx: &mut MutableAppContext) -> Task<()> {
2823    let task = Workspace::new_local(Vec::new(), app_state.clone(), cx);
2824    cx.spawn(|mut cx| async move {
2825        let (workspace, opened_paths) = task.await;
2826
2827        workspace.update(&mut cx, |_, cx| {
2828            if opened_paths.is_empty() {
2829                cx.dispatch_action(NewFile);
2830            }
2831        })
2832    })
2833}
2834
2835#[cfg(test)]
2836mod tests {
2837    use std::{cell::RefCell, rc::Rc};
2838
2839    use crate::item::test::{TestItem, TestItemEvent, TestProjectItem};
2840
2841    use super::*;
2842    use fs::FakeFs;
2843    use gpui::{executor::Deterministic, TestAppContext, ViewContext};
2844    use project::{Project, ProjectEntryId};
2845    use serde_json::json;
2846
2847    pub fn default_item_factory(
2848        _workspace: &mut Workspace,
2849        _cx: &mut ViewContext<Workspace>,
2850    ) -> Option<Box<dyn ItemHandle>> {
2851        unimplemented!()
2852    }
2853
2854    #[gpui::test]
2855    async fn test_tab_disambiguation(cx: &mut TestAppContext) {
2856        cx.foreground().forbid_parking();
2857        Settings::test_async(cx);
2858
2859        let fs = FakeFs::new(cx.background());
2860        let project = Project::test(fs, [], cx).await;
2861        let (_, workspace) = cx.add_window(|cx| {
2862            Workspace::new(
2863                Default::default(),
2864                0,
2865                project.clone(),
2866                default_item_factory,
2867                cx,
2868            )
2869        });
2870
2871        // Adding an item with no ambiguity renders the tab without detail.
2872        let item1 = cx.add_view(&workspace, |_| {
2873            let mut item = TestItem::new();
2874            item.tab_descriptions = Some(vec!["c", "b1/c", "a/b1/c"]);
2875            item
2876        });
2877        workspace.update(cx, |workspace, cx| {
2878            workspace.add_item(Box::new(item1.clone()), cx);
2879        });
2880        item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), None));
2881
2882        // Adding an item that creates ambiguity increases the level of detail on
2883        // both tabs.
2884        let item2 = cx.add_view(&workspace, |_| {
2885            let mut item = TestItem::new();
2886            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
2887            item
2888        });
2889        workspace.update(cx, |workspace, cx| {
2890            workspace.add_item(Box::new(item2.clone()), cx);
2891        });
2892        item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
2893        item2.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
2894
2895        // Adding an item that creates ambiguity increases the level of detail only
2896        // on the ambiguous tabs. In this case, the ambiguity can't be resolved so
2897        // we stop at the highest detail available.
2898        let item3 = cx.add_view(&workspace, |_| {
2899            let mut item = TestItem::new();
2900            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
2901            item
2902        });
2903        workspace.update(cx, |workspace, cx| {
2904            workspace.add_item(Box::new(item3.clone()), cx);
2905        });
2906        item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
2907        item2.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
2908        item3.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
2909    }
2910
2911    #[gpui::test]
2912    async fn test_tracking_active_path(cx: &mut TestAppContext) {
2913        cx.foreground().forbid_parking();
2914        Settings::test_async(cx);
2915        let fs = FakeFs::new(cx.background());
2916        fs.insert_tree(
2917            "/root1",
2918            json!({
2919                "one.txt": "",
2920                "two.txt": "",
2921            }),
2922        )
2923        .await;
2924        fs.insert_tree(
2925            "/root2",
2926            json!({
2927                "three.txt": "",
2928            }),
2929        )
2930        .await;
2931
2932        let project = Project::test(fs, ["root1".as_ref()], cx).await;
2933        let (window_id, workspace) = cx.add_window(|cx| {
2934            Workspace::new(
2935                Default::default(),
2936                0,
2937                project.clone(),
2938                default_item_factory,
2939                cx,
2940            )
2941        });
2942        let worktree_id = project.read_with(cx, |project, cx| {
2943            project.worktrees(cx).next().unwrap().read(cx).id()
2944        });
2945
2946        let item1 = cx.add_view(&workspace, |cx| {
2947            TestItem::new().with_project_items(&[TestProjectItem::new(1, "one.txt", cx)])
2948        });
2949        let item2 = cx.add_view(&workspace, |cx| {
2950            TestItem::new().with_project_items(&[TestProjectItem::new(2, "two.txt", cx)])
2951        });
2952
2953        // Add an item to an empty pane
2954        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item1), cx));
2955        project.read_with(cx, |project, cx| {
2956            assert_eq!(
2957                project.active_entry(),
2958                project
2959                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
2960                    .map(|e| e.id)
2961            );
2962        });
2963        assert_eq!(
2964            cx.current_window_title(window_id).as_deref(),
2965            Some("one.txt — root1")
2966        );
2967
2968        // Add a second item to a non-empty pane
2969        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item2), cx));
2970        assert_eq!(
2971            cx.current_window_title(window_id).as_deref(),
2972            Some("two.txt — root1")
2973        );
2974        project.read_with(cx, |project, cx| {
2975            assert_eq!(
2976                project.active_entry(),
2977                project
2978                    .entry_for_path(&(worktree_id, "two.txt").into(), cx)
2979                    .map(|e| e.id)
2980            );
2981        });
2982
2983        // Close the active item
2984        workspace
2985            .update(cx, |workspace, cx| {
2986                Pane::close_active_item(workspace, &Default::default(), cx).unwrap()
2987            })
2988            .await
2989            .unwrap();
2990        assert_eq!(
2991            cx.current_window_title(window_id).as_deref(),
2992            Some("one.txt — root1")
2993        );
2994        project.read_with(cx, |project, cx| {
2995            assert_eq!(
2996                project.active_entry(),
2997                project
2998                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
2999                    .map(|e| e.id)
3000            );
3001        });
3002
3003        // Add a project folder
3004        project
3005            .update(cx, |project, cx| {
3006                project.find_or_create_local_worktree("/root2", true, cx)
3007            })
3008            .await
3009            .unwrap();
3010        assert_eq!(
3011            cx.current_window_title(window_id).as_deref(),
3012            Some("one.txt — root1, root2")
3013        );
3014
3015        // Remove a project folder
3016        project
3017            .update(cx, |project, cx| project.remove_worktree(worktree_id, cx))
3018            .await;
3019        assert_eq!(
3020            cx.current_window_title(window_id).as_deref(),
3021            Some("one.txt — root2")
3022        );
3023    }
3024
3025    #[gpui::test]
3026    async fn test_close_window(cx: &mut TestAppContext) {
3027        cx.foreground().forbid_parking();
3028        Settings::test_async(cx);
3029        let fs = FakeFs::new(cx.background());
3030        fs.insert_tree("/root", json!({ "one": "" })).await;
3031
3032        let project = Project::test(fs, ["root".as_ref()], cx).await;
3033        let (window_id, workspace) = cx.add_window(|cx| {
3034            Workspace::new(
3035                Default::default(),
3036                0,
3037                project.clone(),
3038                default_item_factory,
3039                cx,
3040            )
3041        });
3042
3043        // When there are no dirty items, there's nothing to do.
3044        let item1 = cx.add_view(&workspace, |_| TestItem::new());
3045        workspace.update(cx, |w, cx| w.add_item(Box::new(item1.clone()), cx));
3046        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
3047        assert!(task.await.unwrap());
3048
3049        // When there are dirty untitled items, prompt to save each one. If the user
3050        // cancels any prompt, then abort.
3051        let item2 = cx.add_view(&workspace, |_| TestItem::new().with_dirty(true));
3052        let item3 = cx.add_view(&workspace, |cx| {
3053            TestItem::new()
3054                .with_dirty(true)
3055                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
3056        });
3057        workspace.update(cx, |w, cx| {
3058            w.add_item(Box::new(item2.clone()), cx);
3059            w.add_item(Box::new(item3.clone()), cx);
3060        });
3061        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
3062        cx.foreground().run_until_parked();
3063        cx.simulate_prompt_answer(window_id, 2 /* cancel */);
3064        cx.foreground().run_until_parked();
3065        assert!(!cx.has_pending_prompt(window_id));
3066        assert!(!task.await.unwrap());
3067    }
3068
3069    #[gpui::test]
3070    async fn test_close_pane_items(cx: &mut TestAppContext) {
3071        cx.foreground().forbid_parking();
3072        Settings::test_async(cx);
3073        let fs = FakeFs::new(cx.background());
3074
3075        let project = Project::test(fs, None, cx).await;
3076        let (window_id, workspace) = cx.add_window(|cx| {
3077            Workspace::new(Default::default(), 0, project, default_item_factory, cx)
3078        });
3079
3080        let item1 = cx.add_view(&workspace, |cx| {
3081            TestItem::new()
3082                .with_dirty(true)
3083                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
3084        });
3085        let item2 = cx.add_view(&workspace, |cx| {
3086            TestItem::new()
3087                .with_dirty(true)
3088                .with_conflict(true)
3089                .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)])
3090        });
3091        let item3 = cx.add_view(&workspace, |cx| {
3092            TestItem::new()
3093                .with_dirty(true)
3094                .with_conflict(true)
3095                .with_project_items(&[TestProjectItem::new(3, "3.txt", cx)])
3096        });
3097        let item4 = cx.add_view(&workspace, |cx| {
3098            TestItem::new()
3099                .with_dirty(true)
3100                .with_project_items(&[TestProjectItem::new_untitled(cx)])
3101        });
3102        let pane = workspace.update(cx, |workspace, cx| {
3103            workspace.add_item(Box::new(item1.clone()), cx);
3104            workspace.add_item(Box::new(item2.clone()), cx);
3105            workspace.add_item(Box::new(item3.clone()), cx);
3106            workspace.add_item(Box::new(item4.clone()), cx);
3107            workspace.active_pane().clone()
3108        });
3109
3110        let close_items = workspace.update(cx, |workspace, cx| {
3111            pane.update(cx, |pane, cx| {
3112                pane.activate_item(1, true, true, cx);
3113                assert_eq!(pane.active_item().unwrap().id(), item2.id());
3114            });
3115
3116            let item1_id = item1.id();
3117            let item3_id = item3.id();
3118            let item4_id = item4.id();
3119            Pane::close_items(workspace, pane.clone(), cx, move |id| {
3120                [item1_id, item3_id, item4_id].contains(&id)
3121            })
3122        });
3123        cx.foreground().run_until_parked();
3124
3125        // There's a prompt to save item 1.
3126        pane.read_with(cx, |pane, _| {
3127            assert_eq!(pane.items_len(), 4);
3128            assert_eq!(pane.active_item().unwrap().id(), item1.id());
3129        });
3130        assert!(cx.has_pending_prompt(window_id));
3131
3132        // Confirm saving item 1.
3133        cx.simulate_prompt_answer(window_id, 0);
3134        cx.foreground().run_until_parked();
3135
3136        // Item 1 is saved. There's a prompt to save item 3.
3137        pane.read_with(cx, |pane, cx| {
3138            assert_eq!(item1.read(cx).save_count, 1);
3139            assert_eq!(item1.read(cx).save_as_count, 0);
3140            assert_eq!(item1.read(cx).reload_count, 0);
3141            assert_eq!(pane.items_len(), 3);
3142            assert_eq!(pane.active_item().unwrap().id(), item3.id());
3143        });
3144        assert!(cx.has_pending_prompt(window_id));
3145
3146        // Cancel saving item 3.
3147        cx.simulate_prompt_answer(window_id, 1);
3148        cx.foreground().run_until_parked();
3149
3150        // Item 3 is reloaded. There's a prompt to save item 4.
3151        pane.read_with(cx, |pane, cx| {
3152            assert_eq!(item3.read(cx).save_count, 0);
3153            assert_eq!(item3.read(cx).save_as_count, 0);
3154            assert_eq!(item3.read(cx).reload_count, 1);
3155            assert_eq!(pane.items_len(), 2);
3156            assert_eq!(pane.active_item().unwrap().id(), item4.id());
3157        });
3158        assert!(cx.has_pending_prompt(window_id));
3159
3160        // Confirm saving item 4.
3161        cx.simulate_prompt_answer(window_id, 0);
3162        cx.foreground().run_until_parked();
3163
3164        // There's a prompt for a path for item 4.
3165        cx.simulate_new_path_selection(|_| Some(Default::default()));
3166        close_items.await.unwrap();
3167
3168        // The requested items are closed.
3169        pane.read_with(cx, |pane, cx| {
3170            assert_eq!(item4.read(cx).save_count, 0);
3171            assert_eq!(item4.read(cx).save_as_count, 1);
3172            assert_eq!(item4.read(cx).reload_count, 0);
3173            assert_eq!(pane.items_len(), 1);
3174            assert_eq!(pane.active_item().unwrap().id(), item2.id());
3175        });
3176    }
3177
3178    #[gpui::test]
3179    async fn test_prompting_to_save_only_on_last_item_for_entry(cx: &mut TestAppContext) {
3180        cx.foreground().forbid_parking();
3181        Settings::test_async(cx);
3182        let fs = FakeFs::new(cx.background());
3183
3184        let project = Project::test(fs, [], cx).await;
3185        let (window_id, workspace) = cx.add_window(|cx| {
3186            Workspace::new(Default::default(), 0, project, default_item_factory, cx)
3187        });
3188
3189        // Create several workspace items with single project entries, and two
3190        // workspace items with multiple project entries.
3191        let single_entry_items = (0..=4)
3192            .map(|project_entry_id| {
3193                cx.add_view(&workspace, |cx| {
3194                    TestItem::new()
3195                        .with_dirty(true)
3196                        .with_project_items(&[TestProjectItem::new(
3197                            project_entry_id,
3198                            &format!("{project_entry_id}.txt"),
3199                            cx,
3200                        )])
3201                })
3202            })
3203            .collect::<Vec<_>>();
3204        let item_2_3 = cx.add_view(&workspace, |cx| {
3205            TestItem::new()
3206                .with_dirty(true)
3207                .with_singleton(false)
3208                .with_project_items(&[
3209                    single_entry_items[2].read(cx).project_items[0].clone(),
3210                    single_entry_items[3].read(cx).project_items[0].clone(),
3211                ])
3212        });
3213        let item_3_4 = cx.add_view(&workspace, |cx| {
3214            TestItem::new()
3215                .with_dirty(true)
3216                .with_singleton(false)
3217                .with_project_items(&[
3218                    single_entry_items[3].read(cx).project_items[0].clone(),
3219                    single_entry_items[4].read(cx).project_items[0].clone(),
3220                ])
3221        });
3222
3223        // Create two panes that contain the following project entries:
3224        //   left pane:
3225        //     multi-entry items:   (2, 3)
3226        //     single-entry items:  0, 1, 2, 3, 4
3227        //   right pane:
3228        //     single-entry items:  1
3229        //     multi-entry items:   (3, 4)
3230        let left_pane = workspace.update(cx, |workspace, cx| {
3231            let left_pane = workspace.active_pane().clone();
3232            workspace.add_item(Box::new(item_2_3.clone()), cx);
3233            for item in single_entry_items {
3234                workspace.add_item(Box::new(item), cx);
3235            }
3236            left_pane.update(cx, |pane, cx| {
3237                pane.activate_item(2, true, true, cx);
3238            });
3239
3240            workspace
3241                .split_pane(left_pane.clone(), SplitDirection::Right, cx)
3242                .unwrap();
3243
3244            left_pane
3245        });
3246
3247        //Need to cause an effect flush in order to respect new focus
3248        workspace.update(cx, |workspace, cx| {
3249            workspace.add_item(Box::new(item_3_4.clone()), cx);
3250            cx.focus(left_pane.clone());
3251        });
3252
3253        // When closing all of the items in the left pane, we should be prompted twice:
3254        // once for project entry 0, and once for project entry 2. After those two
3255        // prompts, the task should complete.
3256
3257        let close = workspace.update(cx, |workspace, cx| {
3258            Pane::close_items(workspace, left_pane.clone(), cx, |_| true)
3259        });
3260
3261        cx.foreground().run_until_parked();
3262        left_pane.read_with(cx, |pane, cx| {
3263            assert_eq!(
3264                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
3265                &[ProjectEntryId::from_proto(0)]
3266            );
3267        });
3268        cx.simulate_prompt_answer(window_id, 0);
3269
3270        cx.foreground().run_until_parked();
3271        left_pane.read_with(cx, |pane, cx| {
3272            assert_eq!(
3273                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
3274                &[ProjectEntryId::from_proto(2)]
3275            );
3276        });
3277        cx.simulate_prompt_answer(window_id, 0);
3278
3279        cx.foreground().run_until_parked();
3280        close.await.unwrap();
3281        left_pane.read_with(cx, |pane, _| {
3282            assert_eq!(pane.items_len(), 0);
3283        });
3284    }
3285
3286    #[gpui::test]
3287    async fn test_autosave(deterministic: Arc<Deterministic>, cx: &mut gpui::TestAppContext) {
3288        deterministic.forbid_parking();
3289
3290        Settings::test_async(cx);
3291        let fs = FakeFs::new(cx.background());
3292
3293        let project = Project::test(fs, [], cx).await;
3294        let (window_id, workspace) = cx.add_window(|cx| {
3295            Workspace::new(Default::default(), 0, project, default_item_factory, cx)
3296        });
3297
3298        let item = cx.add_view(&workspace, |cx| {
3299            TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
3300        });
3301        let item_id = item.id();
3302        workspace.update(cx, |workspace, cx| {
3303            workspace.add_item(Box::new(item.clone()), cx);
3304        });
3305
3306        // Autosave on window change.
3307        item.update(cx, |item, cx| {
3308            cx.update_global(|settings: &mut Settings, _| {
3309                settings.autosave = Autosave::OnWindowChange;
3310            });
3311            item.is_dirty = true;
3312        });
3313
3314        // Deactivating the window saves the file.
3315        cx.simulate_window_activation(None);
3316        deterministic.run_until_parked();
3317        item.read_with(cx, |item, _| assert_eq!(item.save_count, 1));
3318
3319        // Autosave on focus change.
3320        item.update(cx, |item, cx| {
3321            cx.focus_self();
3322            cx.update_global(|settings: &mut Settings, _| {
3323                settings.autosave = Autosave::OnFocusChange;
3324            });
3325            item.is_dirty = true;
3326        });
3327
3328        // Blurring the item saves the file.
3329        item.update(cx, |_, cx| cx.blur());
3330        deterministic.run_until_parked();
3331        item.read_with(cx, |item, _| assert_eq!(item.save_count, 2));
3332
3333        // Deactivating the window still saves the file.
3334        cx.simulate_window_activation(Some(window_id));
3335        item.update(cx, |item, cx| {
3336            cx.focus_self();
3337            item.is_dirty = true;
3338        });
3339        cx.simulate_window_activation(None);
3340
3341        deterministic.run_until_parked();
3342        item.read_with(cx, |item, _| assert_eq!(item.save_count, 3));
3343
3344        // Autosave after delay.
3345        item.update(cx, |item, cx| {
3346            cx.update_global(|settings: &mut Settings, _| {
3347                settings.autosave = Autosave::AfterDelay { milliseconds: 500 };
3348            });
3349            item.is_dirty = true;
3350            cx.emit(TestItemEvent::Edit);
3351        });
3352
3353        // Delay hasn't fully expired, so the file is still dirty and unsaved.
3354        deterministic.advance_clock(Duration::from_millis(250));
3355        item.read_with(cx, |item, _| assert_eq!(item.save_count, 3));
3356
3357        // After delay expires, the file is saved.
3358        deterministic.advance_clock(Duration::from_millis(250));
3359        item.read_with(cx, |item, _| assert_eq!(item.save_count, 4));
3360
3361        // Autosave on focus change, ensuring closing the tab counts as such.
3362        item.update(cx, |item, cx| {
3363            cx.update_global(|settings: &mut Settings, _| {
3364                settings.autosave = Autosave::OnFocusChange;
3365            });
3366            item.is_dirty = true;
3367        });
3368
3369        workspace
3370            .update(cx, |workspace, cx| {
3371                let pane = workspace.active_pane().clone();
3372                Pane::close_items(workspace, pane, cx, move |id| id == item_id)
3373            })
3374            .await
3375            .unwrap();
3376        assert!(!cx.has_pending_prompt(window_id));
3377        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
3378
3379        // Add the item again, ensuring autosave is prevented if the underlying file has been deleted.
3380        workspace.update(cx, |workspace, cx| {
3381            workspace.add_item(Box::new(item.clone()), cx);
3382        });
3383        item.update(cx, |item, cx| {
3384            item.project_items[0].update(cx, |item, _| {
3385                item.entry_id = None;
3386            });
3387            item.is_dirty = true;
3388            cx.blur();
3389        });
3390        deterministic.run_until_parked();
3391        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
3392
3393        // Ensure autosave is prevented for deleted files also when closing the buffer.
3394        let _close_items = workspace.update(cx, |workspace, cx| {
3395            let pane = workspace.active_pane().clone();
3396            Pane::close_items(workspace, pane, cx, move |id| id == item_id)
3397        });
3398        deterministic.run_until_parked();
3399        assert!(cx.has_pending_prompt(window_id));
3400        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
3401    }
3402
3403    #[gpui::test]
3404    async fn test_pane_navigation(
3405        deterministic: Arc<Deterministic>,
3406        cx: &mut gpui::TestAppContext,
3407    ) {
3408        deterministic.forbid_parking();
3409        Settings::test_async(cx);
3410        let fs = FakeFs::new(cx.background());
3411
3412        let project = Project::test(fs, [], cx).await;
3413        let (_, workspace) = cx.add_window(|cx| {
3414            Workspace::new(Default::default(), 0, project, default_item_factory, cx)
3415        });
3416
3417        let item = cx.add_view(&workspace, |cx| {
3418            TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
3419        });
3420        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
3421        let toolbar = pane.read_with(cx, |pane, _| pane.toolbar().clone());
3422        let toolbar_notify_count = Rc::new(RefCell::new(0));
3423
3424        workspace.update(cx, |workspace, cx| {
3425            workspace.add_item(Box::new(item.clone()), cx);
3426            let toolbar_notification_count = toolbar_notify_count.clone();
3427            cx.observe(&toolbar, move |_, _, _| {
3428                *toolbar_notification_count.borrow_mut() += 1
3429            })
3430            .detach();
3431        });
3432
3433        pane.read_with(cx, |pane, _| {
3434            assert!(!pane.can_navigate_backward());
3435            assert!(!pane.can_navigate_forward());
3436        });
3437
3438        item.update(cx, |item, cx| {
3439            item.set_state("one".to_string(), cx);
3440        });
3441
3442        // Toolbar must be notified to re-render the navigation buttons
3443        assert_eq!(*toolbar_notify_count.borrow(), 1);
3444
3445        pane.read_with(cx, |pane, _| {
3446            assert!(pane.can_navigate_backward());
3447            assert!(!pane.can_navigate_forward());
3448        });
3449
3450        workspace
3451            .update(cx, |workspace, cx| {
3452                Pane::go_back(workspace, Some(pane.clone()), cx)
3453            })
3454            .await;
3455
3456        assert_eq!(*toolbar_notify_count.borrow(), 3);
3457        pane.read_with(cx, |pane, _| {
3458            assert!(!pane.can_navigate_backward());
3459            assert!(pane.can_navigate_forward());
3460        });
3461    }
3462}