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