workspace.rs

   1pub mod dock;
   2/// NOTE: Focus only 'takes' after an update has flushed_effects.
   3///
   4/// This may cause issues when you're trying to write tests that use workspace focus to add items at
   5/// specific locations.
   6pub mod item;
   7pub mod notifications;
   8pub mod pane;
   9pub mod pane_group;
  10mod persistence;
  11pub mod searchable;
  12pub mod shared_screen;
  13mod status_bar;
  14mod toolbar;
  15mod workspace_settings;
  16
  17use anyhow::{anyhow, Context, Result};
  18use assets::Assets;
  19use call::ActiveCall;
  20use client::{
  21    proto::{self, PeerId},
  22    Client, TypedEnvelope, UserStore,
  23};
  24use collections::{hash_map, HashMap, HashSet};
  25use drag_and_drop::DragAndDrop;
  26use futures::{
  27    channel::{mpsc, oneshot},
  28    future::try_join_all,
  29    FutureExt, StreamExt,
  30};
  31use gpui::{
  32    actions,
  33    elements::*,
  34    geometry::{
  35        rect::RectF,
  36        vector::{vec2f, Vector2F},
  37    },
  38    impl_actions,
  39    platform::{
  40        CursorStyle, MouseButton, PathPromptOptions, Platform, PromptLevel, WindowBounds,
  41        WindowOptions,
  42    },
  43    AnyModelHandle, AnyViewHandle, AnyWeakViewHandle, AppContext, AsyncAppContext, Entity,
  44    ModelContext, ModelHandle, SizeConstraint, Subscription, Task, View, ViewContext, ViewHandle,
  45    WeakViewHandle, WindowContext,
  46};
  47use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ProjectItem};
  48use itertools::Itertools;
  49use language::{LanguageRegistry, Rope};
  50use std::{
  51    any::TypeId,
  52    borrow::Cow,
  53    cmp, env,
  54    future::Future,
  55    path::{Path, PathBuf},
  56    rc::Rc,
  57    str,
  58    sync::{atomic::AtomicUsize, Arc},
  59    time::Duration,
  60};
  61
  62use crate::{
  63    notifications::{simple_message_notification::MessageNotification, NotificationTracker},
  64    persistence::model::{
  65        DockData, DockStructure, SerializedPane, SerializedPaneGroup, SerializedWorkspace,
  66    },
  67};
  68use dock::{Dock, DockPosition, Panel, PanelButtons, PanelHandle};
  69use lazy_static::lazy_static;
  70use notifications::{NotificationHandle, NotifyResultExt};
  71pub use pane::*;
  72pub use pane_group::*;
  73use persistence::{model::SerializedItem, DB};
  74pub use persistence::{
  75    model::{ItemId, WorkspaceLocation},
  76    WorkspaceDb, DB as WORKSPACE_DB,
  77};
  78use postage::prelude::Stream;
  79use project::{Project, ProjectEntryId, ProjectPath, Worktree, WorktreeId};
  80use serde::Deserialize;
  81use shared_screen::SharedScreen;
  82use status_bar::StatusBar;
  83pub use status_bar::StatusItemView;
  84use theme::{Theme, ThemeSettings};
  85pub use toolbar::{ToolbarItemLocation, ToolbarItemView};
  86use util::{async_iife, paths, ResultExt};
  87pub use workspace_settings::{AutosaveSetting, GitGutterSetting, WorkspaceSettings};
  88
  89lazy_static! {
  90    static ref ZED_WINDOW_SIZE: Option<Vector2F> = env::var("ZED_WINDOW_SIZE")
  91        .ok()
  92        .as_deref()
  93        .and_then(parse_pixel_position_env_var);
  94    static ref ZED_WINDOW_POSITION: Option<Vector2F> = env::var("ZED_WINDOW_POSITION")
  95        .ok()
  96        .as_deref()
  97        .and_then(parse_pixel_position_env_var);
  98}
  99
 100pub trait Modal: View {
 101    fn dismiss_on_event(event: &Self::Event) -> bool;
 102}
 103
 104#[derive(Clone, PartialEq)]
 105pub struct RemoveWorktreeFromProject(pub WorktreeId);
 106
 107actions!(
 108    workspace,
 109    [
 110        Open,
 111        NewFile,
 112        NewWindow,
 113        CloseWindow,
 114        AddFolderToProject,
 115        Unfollow,
 116        Save,
 117        SaveAs,
 118        SaveAll,
 119        ActivatePreviousPane,
 120        ActivateNextPane,
 121        FollowNextCollaborator,
 122        NewTerminal,
 123        NewCenterTerminal,
 124        ToggleTerminalFocus,
 125        NewSearch,
 126        Feedback,
 127        Restart,
 128        Welcome,
 129        ToggleZoom,
 130        ToggleLeftDock,
 131        ToggleRightDock,
 132        ToggleBottomDock,
 133    ]
 134);
 135
 136actions!(zed, [OpenSettings]);
 137
 138#[derive(Clone, PartialEq)]
 139pub struct OpenPaths {
 140    pub paths: Vec<PathBuf>,
 141}
 142
 143#[derive(Clone, Deserialize, PartialEq)]
 144pub struct ActivatePane(pub usize);
 145
 146pub struct Toast {
 147    id: usize,
 148    msg: Cow<'static, str>,
 149    on_click: Option<(Cow<'static, str>, Arc<dyn Fn(&mut WindowContext)>)>,
 150}
 151
 152impl Toast {
 153    pub fn new<I: Into<Cow<'static, str>>>(id: usize, msg: I) -> Self {
 154        Toast {
 155            id,
 156            msg: msg.into(),
 157            on_click: None,
 158        }
 159    }
 160
 161    pub fn on_click<F, M>(mut self, message: M, on_click: F) -> Self
 162    where
 163        M: Into<Cow<'static, str>>,
 164        F: Fn(&mut WindowContext) + 'static,
 165    {
 166        self.on_click = Some((message.into(), Arc::new(on_click)));
 167        self
 168    }
 169}
 170
 171impl PartialEq for Toast {
 172    fn eq(&self, other: &Self) -> bool {
 173        self.id == other.id
 174            && self.msg == other.msg
 175            && self.on_click.is_some() == other.on_click.is_some()
 176    }
 177}
 178
 179impl Clone for Toast {
 180    fn clone(&self) -> Self {
 181        Toast {
 182            id: self.id,
 183            msg: self.msg.to_owned(),
 184            on_click: self.on_click.clone(),
 185        }
 186    }
 187}
 188
 189pub type WorkspaceId = i64;
 190
 191impl_actions!(workspace, [ActivatePane]);
 192
 193pub fn init_settings(cx: &mut AppContext) {
 194    settings::register::<WorkspaceSettings>(cx);
 195}
 196
 197pub fn init(app_state: Arc<AppState>, cx: &mut AppContext) {
 198    init_settings(cx);
 199    pane::init(cx);
 200    notifications::init(cx);
 201
 202    cx.add_global_action({
 203        let app_state = Arc::downgrade(&app_state);
 204        move |_: &Open, cx: &mut AppContext| {
 205            let mut paths = cx.prompt_for_paths(PathPromptOptions {
 206                files: true,
 207                directories: true,
 208                multiple: true,
 209            });
 210
 211            if let Some(app_state) = app_state.upgrade() {
 212                cx.spawn(move |mut cx| async move {
 213                    if let Some(paths) = paths.recv().await.flatten() {
 214                        cx.update(|cx| {
 215                            open_paths(&paths, &app_state, None, cx).detach_and_log_err(cx)
 216                        });
 217                    }
 218                })
 219                .detach();
 220            }
 221        }
 222    });
 223    cx.add_async_action(Workspace::open);
 224
 225    cx.add_async_action(Workspace::follow_next_collaborator);
 226    cx.add_async_action(Workspace::close);
 227    cx.add_global_action(Workspace::close_global);
 228    cx.add_global_action(restart);
 229    cx.add_async_action(Workspace::save_all);
 230    cx.add_action(Workspace::add_folder_to_project);
 231    cx.add_action(
 232        |workspace: &mut Workspace, _: &Unfollow, cx: &mut ViewContext<Workspace>| {
 233            let pane = workspace.active_pane().clone();
 234            workspace.unfollow(&pane, cx);
 235        },
 236    );
 237    cx.add_action(
 238        |workspace: &mut Workspace, _: &Save, cx: &mut ViewContext<Workspace>| {
 239            workspace.save_active_item(false, cx).detach_and_log_err(cx);
 240        },
 241    );
 242    cx.add_action(
 243        |workspace: &mut Workspace, _: &SaveAs, cx: &mut ViewContext<Workspace>| {
 244            workspace.save_active_item(true, cx).detach_and_log_err(cx);
 245        },
 246    );
 247    cx.add_action(|workspace: &mut Workspace, _: &ActivatePreviousPane, cx| {
 248        workspace.activate_previous_pane(cx)
 249    });
 250    cx.add_action(|workspace: &mut Workspace, _: &ActivateNextPane, cx| {
 251        workspace.activate_next_pane(cx)
 252    });
 253    cx.add_action(|workspace: &mut Workspace, _: &ToggleLeftDock, cx| {
 254        workspace.toggle_dock(DockPosition::Left, cx);
 255    });
 256    cx.add_action(|workspace: &mut Workspace, _: &ToggleRightDock, cx| {
 257        workspace.toggle_dock(DockPosition::Right, cx);
 258    });
 259    cx.add_action(|workspace: &mut Workspace, _: &ToggleBottomDock, cx| {
 260        workspace.toggle_dock(DockPosition::Bottom, cx);
 261    });
 262    cx.add_action(Workspace::activate_pane_at_index);
 263    cx.add_action(|workspace: &mut Workspace, _: &ReopenClosedItem, cx| {
 264        workspace.reopen_closed_item(cx).detach();
 265    });
 266    cx.add_action(|workspace: &mut Workspace, _: &GoBack, cx| {
 267        workspace
 268            .go_back(workspace.active_pane().downgrade(), cx)
 269            .detach();
 270    });
 271    cx.add_action(|workspace: &mut Workspace, _: &GoForward, cx| {
 272        workspace
 273            .go_forward(workspace.active_pane().downgrade(), cx)
 274            .detach();
 275    });
 276
 277    cx.add_action(|_: &mut Workspace, _: &install_cli::Install, cx| {
 278        cx.spawn(|workspace, mut cx| async move {
 279            let err = install_cli::install_cli(&cx)
 280                .await
 281                .context("Failed to create CLI symlink");
 282
 283            workspace.update(&mut cx, |workspace, cx| {
 284                if matches!(err, Err(_)) {
 285                    err.notify_err(workspace, cx);
 286                } else {
 287                    workspace.show_notification(1, cx, |cx| {
 288                        cx.add_view(|_| {
 289                            MessageNotification::new("Successfully installed the `zed` binary")
 290                        })
 291                    });
 292                }
 293            })
 294        })
 295        .detach();
 296    });
 297
 298    cx.add_action(
 299        move |_: &mut Workspace, _: &OpenSettings, cx: &mut ViewContext<Workspace>| {
 300            create_and_open_local_file(&paths::SETTINGS, cx, || {
 301                settings::initial_user_settings_content(&Assets)
 302                    .as_ref()
 303                    .into()
 304            })
 305            .detach_and_log_err(cx);
 306        },
 307    );
 308
 309    let client = &app_state.client;
 310    client.add_view_request_handler(Workspace::handle_follow);
 311    client.add_view_message_handler(Workspace::handle_unfollow);
 312    client.add_view_message_handler(Workspace::handle_update_followers);
 313}
 314
 315type ProjectItemBuilders = HashMap<
 316    TypeId,
 317    fn(ModelHandle<Project>, AnyModelHandle, &mut ViewContext<Pane>) -> Box<dyn ItemHandle>,
 318>;
 319pub fn register_project_item<I: ProjectItem>(cx: &mut AppContext) {
 320    cx.update_default_global(|builders: &mut ProjectItemBuilders, _| {
 321        builders.insert(TypeId::of::<I::Item>(), |project, model, cx| {
 322            let item = model.downcast::<I::Item>().unwrap();
 323            Box::new(cx.add_view(|cx| I::for_project_item(project, item, cx)))
 324        });
 325    });
 326}
 327
 328type FollowableItemBuilder = fn(
 329    ViewHandle<Pane>,
 330    ModelHandle<Project>,
 331    ViewId,
 332    &mut Option<proto::view::Variant>,
 333    &mut AppContext,
 334) -> Option<Task<Result<Box<dyn FollowableItemHandle>>>>;
 335type FollowableItemBuilders = HashMap<
 336    TypeId,
 337    (
 338        FollowableItemBuilder,
 339        fn(&AnyViewHandle) -> Box<dyn FollowableItemHandle>,
 340    ),
 341>;
 342pub fn register_followable_item<I: FollowableItem>(cx: &mut AppContext) {
 343    cx.update_default_global(|builders: &mut FollowableItemBuilders, _| {
 344        builders.insert(
 345            TypeId::of::<I>(),
 346            (
 347                |pane, project, id, state, cx| {
 348                    I::from_state_proto(pane, project, id, state, cx).map(|task| {
 349                        cx.foreground()
 350                            .spawn(async move { Ok(Box::new(task.await?) as Box<_>) })
 351                    })
 352                },
 353                |this| Box::new(this.clone().downcast::<I>().unwrap()),
 354            ),
 355        );
 356    });
 357}
 358
 359type ItemDeserializers = HashMap<
 360    Arc<str>,
 361    fn(
 362        ModelHandle<Project>,
 363        WeakViewHandle<Workspace>,
 364        WorkspaceId,
 365        ItemId,
 366        &mut ViewContext<Pane>,
 367    ) -> Task<Result<Box<dyn ItemHandle>>>,
 368>;
 369pub fn register_deserializable_item<I: Item>(cx: &mut AppContext) {
 370    cx.update_default_global(|deserializers: &mut ItemDeserializers, _cx| {
 371        if let Some(serialized_item_kind) = I::serialized_item_kind() {
 372            deserializers.insert(
 373                Arc::from(serialized_item_kind),
 374                |project, workspace, workspace_id, item_id, cx| {
 375                    let task = I::deserialize(project, workspace, workspace_id, item_id, cx);
 376                    cx.foreground()
 377                        .spawn(async { Ok(Box::new(task.await?) as Box<_>) })
 378                },
 379            );
 380        }
 381    });
 382}
 383
 384pub struct AppState {
 385    pub languages: Arc<LanguageRegistry>,
 386    pub client: Arc<client::Client>,
 387    pub user_store: ModelHandle<client::UserStore>,
 388    pub fs: Arc<dyn fs::Fs>,
 389    pub build_window_options:
 390        fn(Option<WindowBounds>, Option<uuid::Uuid>, &dyn Platform) -> WindowOptions<'static>,
 391    pub initialize_workspace:
 392        fn(WeakViewHandle<Workspace>, bool, Arc<AppState>, AsyncAppContext) -> Task<Result<()>>,
 393    pub background_actions: BackgroundActions,
 394}
 395
 396impl AppState {
 397    #[cfg(any(test, feature = "test-support"))]
 398    pub fn test(cx: &mut AppContext) -> Arc<Self> {
 399        use settings::SettingsStore;
 400
 401        if !cx.has_global::<SettingsStore>() {
 402            cx.set_global(SettingsStore::test(cx));
 403        }
 404
 405        let fs = fs::FakeFs::new(cx.background().clone());
 406        let languages = Arc::new(LanguageRegistry::test());
 407        let http_client = util::http::FakeHttpClient::with_404_response();
 408        let client = Client::new(http_client.clone(), cx);
 409        let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx));
 410
 411        theme::init((), cx);
 412        client::init(&client, cx);
 413        crate::init_settings(cx);
 414
 415        Arc::new(Self {
 416            client,
 417            fs,
 418            languages,
 419            user_store,
 420            initialize_workspace: |_, _, _, _| Task::ready(Ok(())),
 421            build_window_options: |_, _, _| Default::default(),
 422            background_actions: || &[],
 423        })
 424    }
 425}
 426
 427struct DelayedDebouncedEditAction {
 428    task: Option<Task<()>>,
 429    cancel_channel: Option<oneshot::Sender<()>>,
 430}
 431
 432impl DelayedDebouncedEditAction {
 433    fn new() -> DelayedDebouncedEditAction {
 434        DelayedDebouncedEditAction {
 435            task: None,
 436            cancel_channel: None,
 437        }
 438    }
 439
 440    fn fire_new<F>(&mut self, delay: Duration, cx: &mut ViewContext<Workspace>, func: F)
 441    where
 442        F: 'static + FnOnce(&mut Workspace, &mut ViewContext<Workspace>) -> Task<Result<()>>,
 443    {
 444        if let Some(channel) = self.cancel_channel.take() {
 445            _ = channel.send(());
 446        }
 447
 448        let (sender, mut receiver) = oneshot::channel::<()>();
 449        self.cancel_channel = Some(sender);
 450
 451        let previous_task = self.task.take();
 452        self.task = Some(cx.spawn(|workspace, mut cx| async move {
 453            let mut timer = cx.background().timer(delay).fuse();
 454            if let Some(previous_task) = previous_task {
 455                previous_task.await;
 456            }
 457
 458            futures::select_biased! {
 459                _ = receiver => return,
 460                    _ = timer => {}
 461            }
 462
 463            if let Some(result) = workspace
 464                .update(&mut cx, |workspace, cx| (func)(workspace, cx))
 465                .log_err()
 466            {
 467                result.await.log_err();
 468            }
 469        }));
 470    }
 471}
 472
 473pub enum Event {
 474    PaneAdded(ViewHandle<Pane>),
 475    ContactRequestedJoin(u64),
 476}
 477
 478pub struct Workspace {
 479    weak_self: WeakViewHandle<Self>,
 480    remote_entity_subscription: Option<client::Subscription>,
 481    modal: Option<AnyViewHandle>,
 482    zoomed: Option<AnyWeakViewHandle>,
 483    zoomed_position: Option<DockPosition>,
 484    center: PaneGroup,
 485    left_dock: ViewHandle<Dock>,
 486    bottom_dock: ViewHandle<Dock>,
 487    right_dock: ViewHandle<Dock>,
 488    panes: Vec<ViewHandle<Pane>>,
 489    panes_by_item: HashMap<usize, WeakViewHandle<Pane>>,
 490    active_pane: ViewHandle<Pane>,
 491    last_active_center_pane: Option<WeakViewHandle<Pane>>,
 492    status_bar: ViewHandle<StatusBar>,
 493    titlebar_item: Option<AnyViewHandle>,
 494    notifications: Vec<(TypeId, usize, Box<dyn NotificationHandle>)>,
 495    project: ModelHandle<Project>,
 496    leader_state: LeaderState,
 497    follower_states_by_leader: FollowerStatesByLeader,
 498    last_leaders_by_pane: HashMap<WeakViewHandle<Pane>, PeerId>,
 499    window_edited: bool,
 500    active_call: Option<(ModelHandle<ActiveCall>, Vec<gpui::Subscription>)>,
 501    leader_updates_tx: mpsc::UnboundedSender<(PeerId, proto::UpdateFollowers)>,
 502    database_id: WorkspaceId,
 503    app_state: Arc<AppState>,
 504    subscriptions: Vec<Subscription>,
 505    _apply_leader_updates: Task<Result<()>>,
 506    _observe_current_user: Task<Result<()>>,
 507    pane_history_timestamp: Arc<AtomicUsize>,
 508}
 509
 510#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
 511pub struct ViewId {
 512    pub creator: PeerId,
 513    pub id: u64,
 514}
 515
 516#[derive(Default)]
 517struct LeaderState {
 518    followers: HashSet<PeerId>,
 519}
 520
 521type FollowerStatesByLeader = HashMap<PeerId, HashMap<ViewHandle<Pane>, FollowerState>>;
 522
 523#[derive(Default)]
 524struct FollowerState {
 525    active_view_id: Option<ViewId>,
 526    items_by_leader_view_id: HashMap<ViewId, Box<dyn FollowableItemHandle>>,
 527}
 528
 529impl Workspace {
 530    pub fn new(
 531        workspace_id: WorkspaceId,
 532        project: ModelHandle<Project>,
 533        app_state: Arc<AppState>,
 534        cx: &mut ViewContext<Self>,
 535    ) -> Self {
 536        cx.observe(&project, |_, _, cx| cx.notify()).detach();
 537        cx.subscribe(&project, move |this, _, event, cx| {
 538            match event {
 539                project::Event::RemoteIdChanged(remote_id) => {
 540                    this.update_window_title(cx);
 541                    this.project_remote_id_changed(*remote_id, cx);
 542                }
 543
 544                project::Event::CollaboratorLeft(peer_id) => {
 545                    this.collaborator_left(*peer_id, cx);
 546                }
 547
 548                project::Event::WorktreeRemoved(_) | project::Event::WorktreeAdded => {
 549                    this.update_window_title(cx);
 550                    this.serialize_workspace(cx);
 551                }
 552
 553                project::Event::DisconnectedFromHost => {
 554                    this.update_window_edited(cx);
 555                    cx.blur();
 556                }
 557
 558                project::Event::Closed => {
 559                    cx.remove_window();
 560                }
 561
 562                project::Event::DeletedEntry(entry_id) => {
 563                    for pane in this.panes.iter() {
 564                        pane.update(cx, |pane, cx| {
 565                            pane.handle_deleted_project_item(*entry_id, cx)
 566                        });
 567                    }
 568                }
 569
 570                _ => {}
 571            }
 572            cx.notify()
 573        })
 574        .detach();
 575
 576        let weak_handle = cx.weak_handle();
 577        let pane_history_timestamp = Arc::new(AtomicUsize::new(0));
 578
 579        let center_pane = cx.add_view(|cx| {
 580            Pane::new(
 581                weak_handle.clone(),
 582                project.clone(),
 583                app_state.background_actions,
 584                pane_history_timestamp.clone(),
 585                cx,
 586            )
 587        });
 588        cx.subscribe(&center_pane, Self::handle_pane_event).detach();
 589        cx.focus(&center_pane);
 590        cx.emit(Event::PaneAdded(center_pane.clone()));
 591
 592        let mut current_user = app_state.user_store.read(cx).watch_current_user();
 593        let mut connection_status = app_state.client.status();
 594        let _observe_current_user = cx.spawn(|this, mut cx| async move {
 595            current_user.recv().await;
 596            connection_status.recv().await;
 597            let mut stream =
 598                Stream::map(current_user, drop).merge(Stream::map(connection_status, drop));
 599
 600            while stream.recv().await.is_some() {
 601                this.update(&mut cx, |_, cx| cx.notify())?;
 602            }
 603            anyhow::Ok(())
 604        });
 605
 606        // All leader updates are enqueued and then processed in a single task, so
 607        // that each asynchronous operation can be run in order.
 608        let (leader_updates_tx, mut leader_updates_rx) =
 609            mpsc::unbounded::<(PeerId, proto::UpdateFollowers)>();
 610        let _apply_leader_updates = cx.spawn(|this, mut cx| async move {
 611            while let Some((leader_id, update)) = leader_updates_rx.next().await {
 612                Self::process_leader_update(&this, leader_id, update, &mut cx)
 613                    .await
 614                    .log_err();
 615            }
 616
 617            Ok(())
 618        });
 619
 620        cx.emit_global(WorkspaceCreated(weak_handle.clone()));
 621
 622        let left_dock = cx.add_view(|_| Dock::new(DockPosition::Left));
 623        let bottom_dock = cx.add_view(|_| Dock::new(DockPosition::Bottom));
 624        let right_dock = cx.add_view(|_| Dock::new(DockPosition::Right));
 625        let left_dock_buttons =
 626            cx.add_view(|cx| PanelButtons::new(left_dock.clone(), weak_handle.clone(), cx));
 627        let bottom_dock_buttons =
 628            cx.add_view(|cx| PanelButtons::new(bottom_dock.clone(), weak_handle.clone(), cx));
 629        let right_dock_buttons =
 630            cx.add_view(|cx| PanelButtons::new(right_dock.clone(), weak_handle.clone(), cx));
 631        let status_bar = cx.add_view(|cx| {
 632            let mut status_bar = StatusBar::new(&center_pane.clone(), cx);
 633            status_bar.add_left_item(left_dock_buttons, cx);
 634            status_bar.add_right_item(right_dock_buttons, cx);
 635            status_bar.add_right_item(bottom_dock_buttons, cx);
 636            status_bar
 637        });
 638
 639        cx.update_default_global::<DragAndDrop<Workspace>, _, _>(|drag_and_drop, _| {
 640            drag_and_drop.register_container(weak_handle.clone());
 641        });
 642
 643        let mut active_call = None;
 644        if cx.has_global::<ModelHandle<ActiveCall>>() {
 645            let call = cx.global::<ModelHandle<ActiveCall>>().clone();
 646            let mut subscriptions = Vec::new();
 647            subscriptions.push(cx.subscribe(&call, Self::on_active_call_event));
 648            active_call = Some((call, subscriptions));
 649        }
 650
 651        let subscriptions = vec![
 652            cx.observe_fullscreen(|_, _, cx| cx.notify()),
 653            cx.observe_window_activation(Self::on_window_activation_changed),
 654            cx.observe_window_bounds(move |_, mut bounds, display, cx| {
 655                // Transform fixed bounds to be stored in terms of the containing display
 656                if let WindowBounds::Fixed(mut window_bounds) = bounds {
 657                    if let Some(screen) = cx.platform().screen_by_id(display) {
 658                        let screen_bounds = screen.bounds();
 659                        window_bounds
 660                            .set_origin_x(window_bounds.origin_x() - screen_bounds.origin_x());
 661                        window_bounds
 662                            .set_origin_y(window_bounds.origin_y() - screen_bounds.origin_y());
 663                        bounds = WindowBounds::Fixed(window_bounds);
 664                    }
 665                }
 666
 667                cx.background()
 668                    .spawn(DB.set_window_bounds(workspace_id, bounds, display))
 669                    .detach_and_log_err(cx);
 670            }),
 671            cx.observe(&left_dock, |this, _, cx| {
 672                this.serialize_workspace(cx);
 673                cx.notify();
 674            }),
 675            cx.observe(&bottom_dock, |this, _, cx| {
 676                this.serialize_workspace(cx);
 677                cx.notify();
 678            }),
 679            cx.observe(&right_dock, |this, _, cx| {
 680                this.serialize_workspace(cx);
 681                cx.notify();
 682            }),
 683        ];
 684
 685        let mut this = Workspace {
 686            weak_self: weak_handle.clone(),
 687            modal: None,
 688            zoomed: None,
 689            zoomed_position: None,
 690            center: PaneGroup::new(center_pane.clone()),
 691            panes: vec![center_pane.clone()],
 692            panes_by_item: Default::default(),
 693            active_pane: center_pane.clone(),
 694            last_active_center_pane: Some(center_pane.downgrade()),
 695            status_bar,
 696            titlebar_item: None,
 697            notifications: Default::default(),
 698            remote_entity_subscription: None,
 699            left_dock,
 700            bottom_dock,
 701            right_dock,
 702            project: project.clone(),
 703            leader_state: Default::default(),
 704            follower_states_by_leader: Default::default(),
 705            last_leaders_by_pane: Default::default(),
 706            window_edited: false,
 707            active_call,
 708            database_id: workspace_id,
 709            app_state,
 710            _observe_current_user,
 711            _apply_leader_updates,
 712            leader_updates_tx,
 713            subscriptions,
 714            pane_history_timestamp,
 715        };
 716        this.project_remote_id_changed(project.read(cx).remote_id(), cx);
 717        cx.defer(|this, cx| this.update_window_title(cx));
 718        this
 719    }
 720
 721    fn new_local(
 722        abs_paths: Vec<PathBuf>,
 723        app_state: Arc<AppState>,
 724        requesting_window_id: Option<usize>,
 725        cx: &mut AppContext,
 726    ) -> Task<(
 727        WeakViewHandle<Workspace>,
 728        Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
 729    )> {
 730        let project_handle = Project::local(
 731            app_state.client.clone(),
 732            app_state.user_store.clone(),
 733            app_state.languages.clone(),
 734            app_state.fs.clone(),
 735            cx,
 736        );
 737
 738        cx.spawn(|mut cx| async move {
 739            let serialized_workspace = persistence::DB.workspace_for_roots(&abs_paths.as_slice());
 740
 741            let paths_to_open = Arc::new(abs_paths);
 742
 743            // Get project paths for all of the abs_paths
 744            let mut worktree_roots: HashSet<Arc<Path>> = Default::default();
 745            let mut project_paths: Vec<(PathBuf, Option<ProjectPath>)> =
 746                Vec::with_capacity(paths_to_open.len());
 747            for path in paths_to_open.iter().cloned() {
 748                if let Some((worktree, project_entry)) = cx
 749                    .update(|cx| {
 750                        Workspace::project_path_for_path(project_handle.clone(), &path, true, cx)
 751                    })
 752                    .await
 753                    .log_err()
 754                {
 755                    worktree_roots.insert(worktree.read_with(&mut cx, |tree, _| tree.abs_path()));
 756                    project_paths.push((path, Some(project_entry)));
 757                } else {
 758                    project_paths.push((path, None));
 759                }
 760            }
 761
 762            let workspace_id = if let Some(serialized_workspace) = serialized_workspace.as_ref() {
 763                serialized_workspace.id
 764            } else {
 765                DB.next_id().await.unwrap_or(0)
 766            };
 767
 768            let window_bounds_override =
 769                ZED_WINDOW_POSITION
 770                    .zip(*ZED_WINDOW_SIZE)
 771                    .map(|(position, size)| {
 772                        WindowBounds::Fixed(RectF::new(
 773                            cx.platform().screens()[0].bounds().origin() + position,
 774                            size,
 775                        ))
 776                    });
 777
 778            let build_workspace = |cx: &mut ViewContext<Workspace>| {
 779                Workspace::new(workspace_id, project_handle.clone(), app_state.clone(), cx)
 780            };
 781
 782            let workspace = requesting_window_id
 783                .and_then(|window_id| {
 784                    cx.update(|cx| cx.replace_root_view(window_id, |cx| build_workspace(cx)))
 785                })
 786                .unwrap_or_else(|| {
 787                    let (bounds, display) = if let Some(bounds) = window_bounds_override {
 788                        (Some(bounds), None)
 789                    } else {
 790                        serialized_workspace
 791                            .as_ref()
 792                            .and_then(|serialized_workspace| {
 793                                let display = serialized_workspace.display?;
 794                                let mut bounds = serialized_workspace.bounds?;
 795
 796                                // Stored bounds are relative to the containing display.
 797                                // So convert back to global coordinates if that screen still exists
 798                                if let WindowBounds::Fixed(mut window_bounds) = bounds {
 799                                    if let Some(screen) = cx.platform().screen_by_id(display) {
 800                                        let screen_bounds = screen.bounds();
 801                                        window_bounds.set_origin_x(
 802                                            window_bounds.origin_x() + screen_bounds.origin_x(),
 803                                        );
 804                                        window_bounds.set_origin_y(
 805                                            window_bounds.origin_y() + screen_bounds.origin_y(),
 806                                        );
 807                                        bounds = WindowBounds::Fixed(window_bounds);
 808                                    } else {
 809                                        // Screen no longer exists. Return none here.
 810                                        return None;
 811                                    }
 812                                }
 813
 814                                Some((bounds, display))
 815                            })
 816                            .unzip()
 817                    };
 818
 819                    // Use the serialized workspace to construct the new window
 820                    cx.add_window(
 821                        (app_state.build_window_options)(bounds, display, cx.platform().as_ref()),
 822                        |cx| build_workspace(cx),
 823                    )
 824                    .1
 825                });
 826
 827            (app_state.initialize_workspace)(
 828                workspace.downgrade(),
 829                serialized_workspace.is_some(),
 830                app_state.clone(),
 831                cx.clone(),
 832            )
 833            .await
 834            .log_err();
 835
 836            cx.update_window(workspace.window_id(), |cx| cx.activate_window());
 837
 838            let workspace = workspace.downgrade();
 839            notify_if_database_failed(&workspace, &mut cx);
 840            let opened_items = open_items(
 841                serialized_workspace,
 842                &workspace,
 843                project_paths,
 844                app_state,
 845                cx,
 846            )
 847            .await;
 848
 849            (workspace, opened_items)
 850        })
 851    }
 852
 853    pub fn weak_handle(&self) -> WeakViewHandle<Self> {
 854        self.weak_self.clone()
 855    }
 856
 857    pub fn left_dock(&self) -> &ViewHandle<Dock> {
 858        &self.left_dock
 859    }
 860
 861    pub fn bottom_dock(&self) -> &ViewHandle<Dock> {
 862        &self.bottom_dock
 863    }
 864
 865    pub fn right_dock(&self) -> &ViewHandle<Dock> {
 866        &self.right_dock
 867    }
 868
 869    pub fn add_panel<T: Panel>(&mut self, panel: ViewHandle<T>, cx: &mut ViewContext<Self>) {
 870        let dock = match panel.position(cx) {
 871            DockPosition::Left => &self.left_dock,
 872            DockPosition::Bottom => &self.bottom_dock,
 873            DockPosition::Right => &self.right_dock,
 874        };
 875
 876        self.subscriptions.push(cx.subscribe(&panel, {
 877            let mut dock = dock.clone();
 878            let mut prev_position = panel.position(cx);
 879            move |this, panel, event, cx| {
 880                if T::should_change_position_on_event(event) {
 881                    let new_position = panel.read(cx).position(cx);
 882                    let mut was_visible = false;
 883                    dock.update(cx, |dock, cx| {
 884                        prev_position = new_position;
 885
 886                        was_visible = dock.is_open()
 887                            && dock
 888                                .visible_panel()
 889                                .map_or(false, |active_panel| active_panel.id() == panel.id());
 890                        dock.remove_panel(&panel, cx);
 891                    });
 892
 893                    if panel.is_zoomed(cx) {
 894                        this.zoomed_position = Some(new_position);
 895                    }
 896
 897                    dock = match panel.read(cx).position(cx) {
 898                        DockPosition::Left => &this.left_dock,
 899                        DockPosition::Bottom => &this.bottom_dock,
 900                        DockPosition::Right => &this.right_dock,
 901                    }
 902                    .clone();
 903                    dock.update(cx, |dock, cx| {
 904                        dock.add_panel(panel.clone(), cx);
 905                        if was_visible {
 906                            dock.set_open(true, cx);
 907                            dock.activate_panel(dock.panels_len() - 1, cx);
 908                        }
 909                    });
 910                } else if T::should_zoom_in_on_event(event) {
 911                    dock.update(cx, |dock, cx| dock.set_panel_zoomed(&panel, true, cx));
 912                    if panel.has_focus(cx) {
 913                        this.zoomed = Some(panel.downgrade().into_any());
 914                        this.zoomed_position = Some(panel.read(cx).position(cx));
 915                    }
 916                } else if T::should_zoom_out_on_event(event) {
 917                    dock.update(cx, |dock, cx| dock.set_panel_zoomed(&panel, false, cx));
 918                    if this.zoomed_position == Some(prev_position) {
 919                        this.zoomed = None;
 920                        this.zoomed_position = None;
 921                    }
 922                    cx.notify();
 923                } else if T::is_focus_event(event) {
 924                    let position = panel.read(cx).position(cx);
 925                    this.dismiss_zoomed_items_to_reveal(Some(position), cx);
 926                    if panel.is_zoomed(cx) {
 927                        this.zoomed = Some(panel.downgrade().into_any());
 928                        this.zoomed_position = Some(position);
 929                    } else {
 930                        this.zoomed = None;
 931                        this.zoomed_position = None;
 932                    }
 933                    cx.notify();
 934                }
 935            }
 936        }));
 937
 938        dock.update(cx, |dock, cx| dock.add_panel(panel, cx));
 939    }
 940
 941    pub fn status_bar(&self) -> &ViewHandle<StatusBar> {
 942        &self.status_bar
 943    }
 944
 945    pub fn app_state(&self) -> &Arc<AppState> {
 946        &self.app_state
 947    }
 948
 949    pub fn user_store(&self) -> &ModelHandle<UserStore> {
 950        &self.app_state.user_store
 951    }
 952
 953    pub fn project(&self) -> &ModelHandle<Project> {
 954        &self.project
 955    }
 956
 957    pub fn recent_navigation_history(
 958        &self,
 959        limit: Option<usize>,
 960        cx: &AppContext,
 961    ) -> Vec<(ProjectPath, Option<PathBuf>)> {
 962        let mut abs_paths_opened: HashMap<PathBuf, HashSet<ProjectPath>> = HashMap::default();
 963        let mut history: HashMap<ProjectPath, (Option<PathBuf>, usize)> = HashMap::default();
 964        for pane in &self.panes {
 965            let pane = pane.read(cx);
 966            pane.nav_history()
 967                .for_each_entry(cx, |entry, (project_path, fs_path)| {
 968                    if let Some(fs_path) = &fs_path {
 969                        abs_paths_opened
 970                            .entry(fs_path.clone())
 971                            .or_default()
 972                            .insert(project_path.clone());
 973                    }
 974                    let timestamp = entry.timestamp;
 975                    match history.entry(project_path) {
 976                        hash_map::Entry::Occupied(mut entry) => {
 977                            let (_, old_timestamp) = entry.get();
 978                            if &timestamp > old_timestamp {
 979                                entry.insert((fs_path, timestamp));
 980                            }
 981                        }
 982                        hash_map::Entry::Vacant(entry) => {
 983                            entry.insert((fs_path, timestamp));
 984                        }
 985                    }
 986                });
 987        }
 988
 989        history
 990            .into_iter()
 991            .sorted_by_key(|(_, (_, timestamp))| *timestamp)
 992            .map(|(project_path, (fs_path, _))| (project_path, fs_path))
 993            .rev()
 994            .filter(|(history_path, abs_path)| {
 995                let latest_project_path_opened = abs_path
 996                    .as_ref()
 997                    .and_then(|abs_path| abs_paths_opened.get(abs_path))
 998                    .and_then(|project_paths| {
 999                        project_paths
1000                            .iter()
1001                            .max_by(|b1, b2| b1.worktree_id.cmp(&b2.worktree_id))
1002                    });
1003
1004                match latest_project_path_opened {
1005                    Some(latest_project_path_opened) => latest_project_path_opened == history_path,
1006                    None => true,
1007                }
1008            })
1009            .take(limit.unwrap_or(usize::MAX))
1010            .collect()
1011    }
1012
1013    fn navigate_history(
1014        &mut self,
1015        pane: WeakViewHandle<Pane>,
1016        mode: NavigationMode,
1017        cx: &mut ViewContext<Workspace>,
1018    ) -> Task<Result<()>> {
1019        let to_load = if let Some(pane) = pane.upgrade(cx) {
1020            cx.focus(&pane);
1021
1022            pane.update(cx, |pane, cx| {
1023                loop {
1024                    // Retrieve the weak item handle from the history.
1025                    let entry = pane.nav_history_mut().pop(mode, cx)?;
1026
1027                    // If the item is still present in this pane, then activate it.
1028                    if let Some(index) = entry
1029                        .item
1030                        .upgrade(cx)
1031                        .and_then(|v| pane.index_for_item(v.as_ref()))
1032                    {
1033                        let prev_active_item_index = pane.active_item_index();
1034                        pane.nav_history_mut().set_mode(mode);
1035                        pane.activate_item(index, true, true, cx);
1036                        pane.nav_history_mut().set_mode(NavigationMode::Normal);
1037
1038                        let mut navigated = prev_active_item_index != pane.active_item_index();
1039                        if let Some(data) = entry.data {
1040                            navigated |= pane.active_item()?.navigate(data, cx);
1041                        }
1042
1043                        if navigated {
1044                            break None;
1045                        }
1046                    }
1047                    // If the item is no longer present in this pane, then retrieve its
1048                    // project path in order to reopen it.
1049                    else {
1050                        break pane
1051                            .nav_history()
1052                            .path_for_item(entry.item.id())
1053                            .map(|(project_path, _)| (project_path, entry));
1054                    }
1055                }
1056            })
1057        } else {
1058            None
1059        };
1060
1061        if let Some((project_path, entry)) = to_load {
1062            // If the item was no longer present, then load it again from its previous path.
1063            let task = self.load_path(project_path, cx);
1064            cx.spawn(|workspace, mut cx| async move {
1065                let task = task.await;
1066                let mut navigated = false;
1067                if let Some((project_entry_id, build_item)) = task.log_err() {
1068                    let prev_active_item_id = pane.update(&mut cx, |pane, _| {
1069                        pane.nav_history_mut().set_mode(mode);
1070                        pane.active_item().map(|p| p.id())
1071                    })?;
1072
1073                    pane.update(&mut cx, |pane, cx| {
1074                        let item = pane.open_item(project_entry_id, true, cx, build_item);
1075                        navigated |= Some(item.id()) != prev_active_item_id;
1076                        pane.nav_history_mut().set_mode(NavigationMode::Normal);
1077                        if let Some(data) = entry.data {
1078                            navigated |= item.navigate(data, cx);
1079                        }
1080                    })?;
1081                }
1082
1083                if !navigated {
1084                    workspace
1085                        .update(&mut cx, |workspace, cx| {
1086                            Self::navigate_history(workspace, pane, mode, cx)
1087                        })?
1088                        .await?;
1089                }
1090
1091                Ok(())
1092            })
1093        } else {
1094            Task::ready(Ok(()))
1095        }
1096    }
1097
1098    pub fn go_back(
1099        &mut self,
1100        pane: WeakViewHandle<Pane>,
1101        cx: &mut ViewContext<Workspace>,
1102    ) -> Task<Result<()>> {
1103        self.navigate_history(pane, NavigationMode::GoingBack, cx)
1104    }
1105
1106    pub fn go_forward(
1107        &mut self,
1108        pane: WeakViewHandle<Pane>,
1109        cx: &mut ViewContext<Workspace>,
1110    ) -> Task<Result<()>> {
1111        self.navigate_history(pane, NavigationMode::GoingForward, cx)
1112    }
1113
1114    pub fn reopen_closed_item(&mut self, cx: &mut ViewContext<Workspace>) -> Task<Result<()>> {
1115        self.navigate_history(
1116            self.active_pane().downgrade(),
1117            NavigationMode::ReopeningClosedItem,
1118            cx,
1119        )
1120    }
1121
1122    pub fn client(&self) -> &Client {
1123        &self.app_state.client
1124    }
1125
1126    pub fn set_titlebar_item(&mut self, item: AnyViewHandle, cx: &mut ViewContext<Self>) {
1127        self.titlebar_item = Some(item);
1128        cx.notify();
1129    }
1130
1131    pub fn titlebar_item(&self) -> Option<AnyViewHandle> {
1132        self.titlebar_item.clone()
1133    }
1134
1135    /// Call the given callback with a workspace whose project is local.
1136    ///
1137    /// If the given workspace has a local project, then it will be passed
1138    /// to the callback. Otherwise, a new empty window will be created.
1139    pub fn with_local_workspace<T, F>(
1140        &mut self,
1141        cx: &mut ViewContext<Self>,
1142        callback: F,
1143    ) -> Task<Result<T>>
1144    where
1145        T: 'static,
1146        F: 'static + FnOnce(&mut Workspace, &mut ViewContext<Workspace>) -> T,
1147    {
1148        if self.project.read(cx).is_local() {
1149            Task::Ready(Some(Ok(callback(self, cx))))
1150        } else {
1151            let task = Self::new_local(Vec::new(), self.app_state.clone(), None, cx);
1152            cx.spawn(|_vh, mut cx| async move {
1153                let (workspace, _) = task.await;
1154                workspace.update(&mut cx, callback)
1155            })
1156        }
1157    }
1158
1159    pub fn worktrees<'a>(
1160        &self,
1161        cx: &'a AppContext,
1162    ) -> impl 'a + Iterator<Item = ModelHandle<Worktree>> {
1163        self.project.read(cx).worktrees(cx)
1164    }
1165
1166    pub fn visible_worktrees<'a>(
1167        &self,
1168        cx: &'a AppContext,
1169    ) -> impl 'a + Iterator<Item = ModelHandle<Worktree>> {
1170        self.project.read(cx).visible_worktrees(cx)
1171    }
1172
1173    pub fn worktree_scans_complete(&self, cx: &AppContext) -> impl Future<Output = ()> + 'static {
1174        let futures = self
1175            .worktrees(cx)
1176            .filter_map(|worktree| worktree.read(cx).as_local())
1177            .map(|worktree| worktree.scan_complete())
1178            .collect::<Vec<_>>();
1179        async move {
1180            for future in futures {
1181                future.await;
1182            }
1183        }
1184    }
1185
1186    pub fn close_global(_: &CloseWindow, cx: &mut AppContext) {
1187        cx.spawn(|mut cx| async move {
1188            let id = cx
1189                .window_ids()
1190                .into_iter()
1191                .find(|&id| cx.window_is_active(id));
1192            if let Some(id) = id {
1193                //This can only get called when the window's project connection has been lost
1194                //so we don't need to prompt the user for anything and instead just close the window
1195                cx.remove_window(id);
1196            }
1197        })
1198        .detach();
1199    }
1200
1201    pub fn close(
1202        &mut self,
1203        _: &CloseWindow,
1204        cx: &mut ViewContext<Self>,
1205    ) -> Option<Task<Result<()>>> {
1206        let window_id = cx.window_id();
1207        let prepare = self.prepare_to_close(false, cx);
1208        Some(cx.spawn(|_, mut cx| async move {
1209            if prepare.await? {
1210                cx.remove_window(window_id);
1211            }
1212            Ok(())
1213        }))
1214    }
1215
1216    pub fn prepare_to_close(
1217        &mut self,
1218        quitting: bool,
1219        cx: &mut ViewContext<Self>,
1220    ) -> Task<Result<bool>> {
1221        let active_call = self.active_call().cloned();
1222        let window_id = cx.window_id();
1223
1224        cx.spawn(|this, mut cx| async move {
1225            let workspace_count = cx
1226                .window_ids()
1227                .into_iter()
1228                .filter_map(|window_id| cx.root_view(window_id)?.clone().downcast::<Workspace>())
1229                .count();
1230
1231            if let Some(active_call) = active_call {
1232                if !quitting
1233                    && workspace_count == 1
1234                    && active_call.read_with(&cx, |call, _| call.room().is_some())
1235                {
1236                    let answer = cx.prompt(
1237                        window_id,
1238                        PromptLevel::Warning,
1239                        "Do you want to leave the current call?",
1240                        &["Close window and hang up", "Cancel"],
1241                    );
1242
1243                    if let Some(mut answer) = answer {
1244                        if answer.next().await == Some(1) {
1245                            return anyhow::Ok(false);
1246                        } else {
1247                            active_call
1248                                .update(&mut cx, |call, cx| call.hang_up(cx))
1249                                .await
1250                                .log_err();
1251                        }
1252                    }
1253                }
1254            }
1255
1256            Ok(this
1257                .update(&mut cx, |this, cx| this.save_all_internal(true, cx))?
1258                .await?)
1259        })
1260    }
1261
1262    fn save_all(&mut self, _: &SaveAll, cx: &mut ViewContext<Self>) -> Option<Task<Result<()>>> {
1263        let save_all = self.save_all_internal(false, cx);
1264        Some(cx.foreground().spawn(async move {
1265            save_all.await?;
1266            Ok(())
1267        }))
1268    }
1269
1270    fn save_all_internal(
1271        &mut self,
1272        should_prompt_to_save: bool,
1273        cx: &mut ViewContext<Self>,
1274    ) -> Task<Result<bool>> {
1275        if self.project.read(cx).is_read_only() {
1276            return Task::ready(Ok(true));
1277        }
1278
1279        let dirty_items = self
1280            .panes
1281            .iter()
1282            .flat_map(|pane| {
1283                pane.read(cx).items().filter_map(|item| {
1284                    if item.is_dirty(cx) {
1285                        Some((pane.downgrade(), item.boxed_clone()))
1286                    } else {
1287                        None
1288                    }
1289                })
1290            })
1291            .collect::<Vec<_>>();
1292
1293        let project = self.project.clone();
1294        cx.spawn(|_, mut cx| async move {
1295            for (pane, item) in dirty_items {
1296                let (singleton, project_entry_ids) =
1297                    cx.read(|cx| (item.is_singleton(cx), item.project_entry_ids(cx)));
1298                if singleton || !project_entry_ids.is_empty() {
1299                    if let Some(ix) =
1300                        pane.read_with(&cx, |pane, _| pane.index_for_item(item.as_ref()))?
1301                    {
1302                        if !Pane::save_item(
1303                            project.clone(),
1304                            &pane,
1305                            ix,
1306                            &*item,
1307                            should_prompt_to_save,
1308                            &mut cx,
1309                        )
1310                        .await?
1311                        {
1312                            return Ok(false);
1313                        }
1314                    }
1315                }
1316            }
1317            Ok(true)
1318        })
1319    }
1320
1321    pub fn open(&mut self, _: &Open, cx: &mut ViewContext<Self>) -> Option<Task<Result<()>>> {
1322        let mut paths = cx.prompt_for_paths(PathPromptOptions {
1323            files: true,
1324            directories: true,
1325            multiple: true,
1326        });
1327
1328        Some(cx.spawn(|this, mut cx| async move {
1329            if let Some(paths) = paths.recv().await.flatten() {
1330                if let Some(task) = this
1331                    .update(&mut cx, |this, cx| this.open_workspace_for_paths(paths, cx))
1332                    .log_err()
1333                {
1334                    task.await?
1335                }
1336            }
1337            Ok(())
1338        }))
1339    }
1340
1341    pub fn open_workspace_for_paths(
1342        &mut self,
1343        paths: Vec<PathBuf>,
1344        cx: &mut ViewContext<Self>,
1345    ) -> Task<Result<()>> {
1346        let window_id = cx.window_id();
1347        let is_remote = self.project.read(cx).is_remote();
1348        let has_worktree = self.project.read(cx).worktrees(cx).next().is_some();
1349        let has_dirty_items = self.items(cx).any(|item| item.is_dirty(cx));
1350        let close_task = if is_remote || has_worktree || has_dirty_items {
1351            None
1352        } else {
1353            Some(self.prepare_to_close(false, cx))
1354        };
1355        let app_state = self.app_state.clone();
1356
1357        cx.spawn(|_, mut cx| async move {
1358            let window_id_to_replace = if let Some(close_task) = close_task {
1359                if !close_task.await? {
1360                    return Ok(());
1361                }
1362                Some(window_id)
1363            } else {
1364                None
1365            };
1366            cx.update(|cx| open_paths(&paths, &app_state, window_id_to_replace, cx))
1367                .await?;
1368            Ok(())
1369        })
1370    }
1371
1372    #[allow(clippy::type_complexity)]
1373    pub fn open_paths(
1374        &mut self,
1375        mut abs_paths: Vec<PathBuf>,
1376        visible: bool,
1377        cx: &mut ViewContext<Self>,
1378    ) -> Task<Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>> {
1379        log::info!("open paths {:?}", abs_paths);
1380
1381        let fs = self.app_state.fs.clone();
1382
1383        // Sort the paths to ensure we add worktrees for parents before their children.
1384        abs_paths.sort_unstable();
1385        cx.spawn(|this, mut cx| async move {
1386            let mut project_paths = Vec::new();
1387            for path in &abs_paths {
1388                if let Some(project_path) = this
1389                    .update(&mut cx, |this, cx| {
1390                        Workspace::project_path_for_path(this.project.clone(), path, visible, cx)
1391                    })
1392                    .log_err()
1393                {
1394                    project_paths.push(project_path.await.log_err());
1395                } else {
1396                    project_paths.push(None);
1397                }
1398            }
1399
1400            let tasks = abs_paths
1401                .iter()
1402                .cloned()
1403                .zip(project_paths.into_iter())
1404                .map(|(abs_path, project_path)| {
1405                    let this = this.clone();
1406                    cx.spawn(|mut cx| {
1407                        let fs = fs.clone();
1408                        async move {
1409                            let (_worktree, project_path) = project_path?;
1410                            if fs.is_file(&abs_path).await {
1411                                Some(
1412                                    this.update(&mut cx, |this, cx| {
1413                                        this.open_path(project_path, None, true, cx)
1414                                    })
1415                                    .log_err()?
1416                                    .await,
1417                                )
1418                            } else {
1419                                None
1420                            }
1421                        }
1422                    })
1423                })
1424                .collect::<Vec<_>>();
1425
1426            futures::future::join_all(tasks).await
1427        })
1428    }
1429
1430    fn add_folder_to_project(&mut self, _: &AddFolderToProject, cx: &mut ViewContext<Self>) {
1431        let mut paths = cx.prompt_for_paths(PathPromptOptions {
1432            files: false,
1433            directories: true,
1434            multiple: true,
1435        });
1436        cx.spawn(|this, mut cx| async move {
1437            if let Some(paths) = paths.recv().await.flatten() {
1438                let results = this
1439                    .update(&mut cx, |this, cx| this.open_paths(paths, true, cx))?
1440                    .await;
1441                for result in results.into_iter().flatten() {
1442                    result.log_err();
1443                }
1444            }
1445            anyhow::Ok(())
1446        })
1447        .detach_and_log_err(cx);
1448    }
1449
1450    fn project_path_for_path(
1451        project: ModelHandle<Project>,
1452        abs_path: &Path,
1453        visible: bool,
1454        cx: &mut AppContext,
1455    ) -> Task<Result<(ModelHandle<Worktree>, ProjectPath)>> {
1456        let entry = project.update(cx, |project, cx| {
1457            project.find_or_create_local_worktree(abs_path, visible, cx)
1458        });
1459        cx.spawn(|cx| async move {
1460            let (worktree, path) = entry.await?;
1461            let worktree_id = worktree.read_with(&cx, |t, _| t.id());
1462            Ok((
1463                worktree,
1464                ProjectPath {
1465                    worktree_id,
1466                    path: path.into(),
1467                },
1468            ))
1469        })
1470    }
1471
1472    /// Returns the modal that was toggled closed if it was open.
1473    pub fn toggle_modal<V, F>(
1474        &mut self,
1475        cx: &mut ViewContext<Self>,
1476        add_view: F,
1477    ) -> Option<ViewHandle<V>>
1478    where
1479        V: 'static + Modal,
1480        F: FnOnce(&mut Self, &mut ViewContext<Self>) -> ViewHandle<V>,
1481    {
1482        cx.notify();
1483        // Whatever modal was visible is getting clobbered. If its the same type as V, then return
1484        // it. Otherwise, create a new modal and set it as active.
1485        let already_open_modal = self.modal.take().and_then(|modal| modal.downcast::<V>());
1486        if let Some(already_open_modal) = already_open_modal {
1487            cx.focus_self();
1488            Some(already_open_modal)
1489        } else {
1490            let modal = add_view(self, cx);
1491            cx.subscribe(&modal, |this, _, event, cx| {
1492                if V::dismiss_on_event(event) {
1493                    this.dismiss_modal(cx);
1494                }
1495            })
1496            .detach();
1497            cx.focus(&modal);
1498            self.modal = Some(modal.into_any());
1499            None
1500        }
1501    }
1502
1503    pub fn modal<V: 'static + View>(&self) -> Option<ViewHandle<V>> {
1504        self.modal
1505            .as_ref()
1506            .and_then(|modal| modal.clone().downcast::<V>())
1507    }
1508
1509    pub fn dismiss_modal(&mut self, cx: &mut ViewContext<Self>) {
1510        if self.modal.take().is_some() {
1511            cx.focus(&self.active_pane);
1512            cx.notify();
1513        }
1514    }
1515
1516    pub fn items<'a>(
1517        &'a self,
1518        cx: &'a AppContext,
1519    ) -> impl 'a + Iterator<Item = &Box<dyn ItemHandle>> {
1520        self.panes.iter().flat_map(|pane| pane.read(cx).items())
1521    }
1522
1523    pub fn item_of_type<T: Item>(&self, cx: &AppContext) -> Option<ViewHandle<T>> {
1524        self.items_of_type(cx).max_by_key(|item| item.id())
1525    }
1526
1527    pub fn items_of_type<'a, T: Item>(
1528        &'a self,
1529        cx: &'a AppContext,
1530    ) -> impl 'a + Iterator<Item = ViewHandle<T>> {
1531        self.panes
1532            .iter()
1533            .flat_map(|pane| pane.read(cx).items_of_type())
1534    }
1535
1536    pub fn active_item(&self, cx: &AppContext) -> Option<Box<dyn ItemHandle>> {
1537        self.active_pane().read(cx).active_item()
1538    }
1539
1540    fn active_project_path(&self, cx: &ViewContext<Self>) -> Option<ProjectPath> {
1541        self.active_item(cx).and_then(|item| item.project_path(cx))
1542    }
1543
1544    pub fn save_active_item(
1545        &mut self,
1546        force_name_change: bool,
1547        cx: &mut ViewContext<Self>,
1548    ) -> Task<Result<()>> {
1549        let project = self.project.clone();
1550        if let Some(item) = self.active_item(cx) {
1551            if !force_name_change && item.can_save(cx) {
1552                if item.has_conflict(cx) {
1553                    const CONFLICT_MESSAGE: &str = "This file has changed on disk since you started editing it. Do you want to overwrite it?";
1554
1555                    let mut answer = cx.prompt(
1556                        PromptLevel::Warning,
1557                        CONFLICT_MESSAGE,
1558                        &["Overwrite", "Cancel"],
1559                    );
1560                    cx.spawn(|this, mut cx| async move {
1561                        let answer = answer.recv().await;
1562                        if answer == Some(0) {
1563                            this.update(&mut cx, |this, cx| item.save(this.project.clone(), cx))?
1564                                .await?;
1565                        }
1566                        Ok(())
1567                    })
1568                } else {
1569                    item.save(self.project.clone(), cx)
1570                }
1571            } else if item.is_singleton(cx) {
1572                let worktree = self.worktrees(cx).next();
1573                let start_abs_path = worktree
1574                    .and_then(|w| w.read(cx).as_local())
1575                    .map_or(Path::new(""), |w| w.abs_path())
1576                    .to_path_buf();
1577                let mut abs_path = cx.prompt_for_new_path(&start_abs_path);
1578                cx.spawn(|this, mut cx| async move {
1579                    if let Some(abs_path) = abs_path.recv().await.flatten() {
1580                        this.update(&mut cx, |_, cx| item.save_as(project, abs_path, cx))?
1581                            .await?;
1582                    }
1583                    Ok(())
1584                })
1585            } else {
1586                Task::ready(Ok(()))
1587            }
1588        } else {
1589            Task::ready(Ok(()))
1590        }
1591    }
1592
1593    pub fn toggle_dock(&mut self, dock_side: DockPosition, cx: &mut ViewContext<Self>) {
1594        let dock = match dock_side {
1595            DockPosition::Left => &self.left_dock,
1596            DockPosition::Bottom => &self.bottom_dock,
1597            DockPosition::Right => &self.right_dock,
1598        };
1599        let mut focus_center = false;
1600        let mut reveal_dock = false;
1601        dock.update(cx, |dock, cx| {
1602            let other_is_zoomed = self.zoomed.is_some() && self.zoomed_position != Some(dock_side);
1603            let was_visible = dock.is_open() && !other_is_zoomed;
1604            dock.set_open(!was_visible, cx);
1605
1606            if let Some(active_panel) = dock.active_panel() {
1607                if was_visible {
1608                    if active_panel.has_focus(cx) {
1609                        focus_center = true;
1610                    }
1611                } else {
1612                    if active_panel.is_zoomed(cx) {
1613                        cx.focus(active_panel.as_any());
1614                    }
1615                    reveal_dock = true;
1616                }
1617            }
1618        });
1619
1620        if reveal_dock {
1621            self.dismiss_zoomed_items_to_reveal(Some(dock_side), cx);
1622        }
1623
1624        if focus_center {
1625            cx.focus_self();
1626        }
1627
1628        cx.notify();
1629        self.serialize_workspace(cx);
1630    }
1631
1632    /// Transfer focus to the panel of the given type.
1633    pub fn focus_panel<T: Panel>(&mut self, cx: &mut ViewContext<Self>) -> Option<ViewHandle<T>> {
1634        self.focus_or_unfocus_panel::<T>(cx, |_, _| true)?
1635            .as_any()
1636            .clone()
1637            .downcast()
1638    }
1639
1640    /// Focus the panel of the given type if it isn't already focused. If it is
1641    /// already focused, then transfer focus back to the workspace center.
1642    pub fn toggle_panel_focus<T: Panel>(&mut self, cx: &mut ViewContext<Self>) {
1643        self.focus_or_unfocus_panel::<T>(cx, |panel, cx| !panel.has_focus(cx));
1644    }
1645
1646    /// Focus or unfocus the given panel type, depending on the given callback.
1647    fn focus_or_unfocus_panel<T: Panel>(
1648        &mut self,
1649        cx: &mut ViewContext<Self>,
1650        should_focus: impl Fn(&dyn PanelHandle, &mut ViewContext<Dock>) -> bool,
1651    ) -> Option<Rc<dyn PanelHandle>> {
1652        for dock in [&self.left_dock, &self.bottom_dock, &self.right_dock] {
1653            if let Some(panel_index) = dock.read(cx).panel_index_for_type::<T>() {
1654                let mut focus_center = false;
1655                let mut reveal_dock = false;
1656                let panel = dock.update(cx, |dock, cx| {
1657                    dock.activate_panel(panel_index, cx);
1658
1659                    let panel = dock.active_panel().cloned();
1660                    if let Some(panel) = panel.as_ref() {
1661                        if should_focus(&**panel, cx) {
1662                            dock.set_open(true, cx);
1663                            cx.focus(panel.as_any());
1664                            reveal_dock = true;
1665                        } else {
1666                            // if panel.is_zoomed(cx) {
1667                            //     dock.set_open(false, cx);
1668                            // }
1669                            focus_center = true;
1670                        }
1671                    }
1672                    panel
1673                });
1674
1675                if focus_center {
1676                    cx.focus_self();
1677                }
1678
1679                self.serialize_workspace(cx);
1680                cx.notify();
1681                return panel;
1682            }
1683        }
1684        None
1685    }
1686
1687    fn zoom_out(&mut self, cx: &mut ViewContext<Self>) {
1688        for pane in &self.panes {
1689            pane.update(cx, |pane, cx| pane.set_zoomed(false, cx));
1690        }
1691
1692        self.left_dock.update(cx, |dock, cx| dock.zoom_out(cx));
1693        self.bottom_dock.update(cx, |dock, cx| dock.zoom_out(cx));
1694        self.right_dock.update(cx, |dock, cx| dock.zoom_out(cx));
1695        self.zoomed = None;
1696        self.zoomed_position = None;
1697
1698        cx.notify();
1699    }
1700
1701    fn dismiss_zoomed_items_to_reveal(
1702        &mut self,
1703        dock_to_reveal: Option<DockPosition>,
1704        cx: &mut ViewContext<Self>,
1705    ) {
1706        // If a center pane is zoomed, unzoom it.
1707        for pane in &self.panes {
1708            if pane != &self.active_pane {
1709                pane.update(cx, |pane, cx| pane.set_zoomed(false, cx));
1710            }
1711        }
1712
1713        // If another dock is zoomed, hide it.
1714        let mut focus_center = false;
1715        for dock in [&self.left_dock, &self.right_dock, &self.bottom_dock] {
1716            dock.update(cx, |dock, cx| {
1717                if Some(dock.position()) != dock_to_reveal {
1718                    if let Some(panel) = dock.active_panel() {
1719                        if panel.is_zoomed(cx) {
1720                            focus_center |= panel.has_focus(cx);
1721                            dock.set_open(false, cx);
1722                        }
1723                    }
1724                }
1725            });
1726        }
1727
1728        if focus_center {
1729            cx.focus_self();
1730        }
1731
1732        if self.zoomed_position != dock_to_reveal {
1733            self.zoomed = None;
1734            self.zoomed_position = None;
1735        }
1736
1737        cx.notify();
1738    }
1739
1740    fn add_pane(&mut self, cx: &mut ViewContext<Self>) -> ViewHandle<Pane> {
1741        let pane = cx.add_view(|cx| {
1742            Pane::new(
1743                self.weak_handle(),
1744                self.project.clone(),
1745                self.app_state.background_actions,
1746                self.pane_history_timestamp.clone(),
1747                cx,
1748            )
1749        });
1750        cx.subscribe(&pane, Self::handle_pane_event).detach();
1751        self.panes.push(pane.clone());
1752        cx.focus(&pane);
1753        cx.emit(Event::PaneAdded(pane.clone()));
1754        pane
1755    }
1756
1757    pub fn add_item_to_center(
1758        &mut self,
1759        item: Box<dyn ItemHandle>,
1760        cx: &mut ViewContext<Self>,
1761    ) -> bool {
1762        if let Some(center_pane) = self.last_active_center_pane.clone() {
1763            if let Some(center_pane) = center_pane.upgrade(cx) {
1764                center_pane.update(cx, |pane, cx| pane.add_item(item, true, true, None, cx));
1765                true
1766            } else {
1767                false
1768            }
1769        } else {
1770            false
1771        }
1772    }
1773
1774    pub fn add_item(&mut self, item: Box<dyn ItemHandle>, cx: &mut ViewContext<Self>) {
1775        self.active_pane
1776            .update(cx, |pane, cx| pane.add_item(item, true, true, None, cx));
1777    }
1778
1779    pub fn open_abs_path(
1780        &mut self,
1781        abs_path: PathBuf,
1782        visible: bool,
1783        cx: &mut ViewContext<Self>,
1784    ) -> Task<anyhow::Result<Box<dyn ItemHandle>>> {
1785        cx.spawn(|workspace, mut cx| async move {
1786            let open_paths_task_result = workspace
1787                .update(&mut cx, |workspace, cx| {
1788                    workspace.open_paths(vec![abs_path.clone()], visible, cx)
1789                })
1790                .with_context(|| format!("open abs path {abs_path:?} task spawn"))?
1791                .await;
1792            anyhow::ensure!(
1793                open_paths_task_result.len() == 1,
1794                "open abs path {abs_path:?} task returned incorrect number of results"
1795            );
1796            match open_paths_task_result
1797                .into_iter()
1798                .next()
1799                .expect("ensured single task result")
1800            {
1801                Some(open_result) => {
1802                    open_result.with_context(|| format!("open abs path {abs_path:?} task join"))
1803                }
1804                None => anyhow::bail!("open abs path {abs_path:?} task returned None"),
1805            }
1806        })
1807    }
1808
1809    pub fn open_path(
1810        &mut self,
1811        path: impl Into<ProjectPath>,
1812        pane: Option<WeakViewHandle<Pane>>,
1813        focus_item: bool,
1814        cx: &mut ViewContext<Self>,
1815    ) -> Task<Result<Box<dyn ItemHandle>, anyhow::Error>> {
1816        let pane = pane.unwrap_or_else(|| {
1817            self.last_active_center_pane.clone().unwrap_or_else(|| {
1818                self.panes
1819                    .first()
1820                    .expect("There must be an active pane")
1821                    .downgrade()
1822            })
1823        });
1824
1825        let task = self.load_path(path.into(), cx);
1826        cx.spawn(|_, mut cx| async move {
1827            let (project_entry_id, build_item) = task.await?;
1828            pane.update(&mut cx, |pane, cx| {
1829                pane.open_item(project_entry_id, focus_item, cx, build_item)
1830            })
1831        })
1832    }
1833
1834    pub(crate) fn load_path(
1835        &mut self,
1836        path: ProjectPath,
1837        cx: &mut ViewContext<Self>,
1838    ) -> Task<
1839        Result<(
1840            ProjectEntryId,
1841            impl 'static + FnOnce(&mut ViewContext<Pane>) -> Box<dyn ItemHandle>,
1842        )>,
1843    > {
1844        let project = self.project().clone();
1845        let project_item = project.update(cx, |project, cx| project.open_path(path, cx));
1846        cx.spawn(|_, mut cx| async move {
1847            let (project_entry_id, project_item) = project_item.await?;
1848            let build_item = cx.update(|cx| {
1849                cx.default_global::<ProjectItemBuilders>()
1850                    .get(&project_item.model_type())
1851                    .ok_or_else(|| anyhow!("no item builder for project item"))
1852                    .cloned()
1853            })?;
1854            let build_item =
1855                move |cx: &mut ViewContext<Pane>| build_item(project, project_item, cx);
1856            Ok((project_entry_id, build_item))
1857        })
1858    }
1859
1860    pub fn open_project_item<T>(
1861        &mut self,
1862        project_item: ModelHandle<T::Item>,
1863        cx: &mut ViewContext<Self>,
1864    ) -> ViewHandle<T>
1865    where
1866        T: ProjectItem,
1867    {
1868        use project::Item as _;
1869
1870        let entry_id = project_item.read(cx).entry_id(cx);
1871        if let Some(item) = entry_id
1872            .and_then(|entry_id| self.active_pane().read(cx).item_for_entry(entry_id, cx))
1873            .and_then(|item| item.downcast())
1874        {
1875            self.activate_item(&item, cx);
1876            return item;
1877        }
1878
1879        let item = cx.add_view(|cx| T::for_project_item(self.project().clone(), project_item, cx));
1880        self.add_item(Box::new(item.clone()), cx);
1881        item
1882    }
1883
1884    pub fn open_shared_screen(&mut self, peer_id: PeerId, cx: &mut ViewContext<Self>) {
1885        if let Some(shared_screen) = self.shared_screen_for_peer(peer_id, &self.active_pane, cx) {
1886            self.active_pane.update(cx, |pane, cx| {
1887                pane.add_item(Box::new(shared_screen), false, true, None, cx)
1888            });
1889        }
1890    }
1891
1892    pub fn activate_item(&mut self, item: &dyn ItemHandle, cx: &mut ViewContext<Self>) -> bool {
1893        let result = self.panes.iter().find_map(|pane| {
1894            pane.read(cx)
1895                .index_for_item(item)
1896                .map(|ix| (pane.clone(), ix))
1897        });
1898        if let Some((pane, ix)) = result {
1899            pane.update(cx, |pane, cx| pane.activate_item(ix, true, true, cx));
1900            true
1901        } else {
1902            false
1903        }
1904    }
1905
1906    fn activate_pane_at_index(&mut self, action: &ActivatePane, cx: &mut ViewContext<Self>) {
1907        let panes = self.center.panes();
1908        if let Some(pane) = panes.get(action.0).map(|p| (*p).clone()) {
1909            cx.focus(&pane);
1910        } else {
1911            self.split_pane(self.active_pane.clone(), SplitDirection::Right, cx);
1912        }
1913    }
1914
1915    pub fn activate_next_pane(&mut self, cx: &mut ViewContext<Self>) {
1916        let panes = self.center.panes();
1917        if let Some(ix) = panes.iter().position(|pane| **pane == self.active_pane) {
1918            let next_ix = (ix + 1) % panes.len();
1919            let next_pane = panes[next_ix].clone();
1920            cx.focus(&next_pane);
1921        }
1922    }
1923
1924    pub fn activate_previous_pane(&mut self, cx: &mut ViewContext<Self>) {
1925        let panes = self.center.panes();
1926        if let Some(ix) = panes.iter().position(|pane| **pane == self.active_pane) {
1927            let prev_ix = cmp::min(ix.wrapping_sub(1), panes.len() - 1);
1928            let prev_pane = panes[prev_ix].clone();
1929            cx.focus(&prev_pane);
1930        }
1931    }
1932
1933    fn handle_pane_focused(&mut self, pane: ViewHandle<Pane>, cx: &mut ViewContext<Self>) {
1934        if self.active_pane != pane {
1935            self.active_pane = pane.clone();
1936            self.status_bar.update(cx, |status_bar, cx| {
1937                status_bar.set_active_pane(&self.active_pane, cx);
1938            });
1939            self.active_item_path_changed(cx);
1940            self.last_active_center_pane = Some(pane.downgrade());
1941        }
1942
1943        self.dismiss_zoomed_items_to_reveal(None, cx);
1944        if pane.read(cx).is_zoomed() {
1945            self.zoomed = Some(pane.downgrade().into_any());
1946        } else {
1947            self.zoomed = None;
1948        }
1949        self.zoomed_position = None;
1950
1951        self.update_followers(
1952            proto::update_followers::Variant::UpdateActiveView(proto::UpdateActiveView {
1953                id: self.active_item(cx).and_then(|item| {
1954                    item.to_followable_item_handle(cx)?
1955                        .remote_id(&self.app_state.client, cx)
1956                        .map(|id| id.to_proto())
1957                }),
1958                leader_id: self.leader_for_pane(&pane),
1959            }),
1960            cx,
1961        );
1962
1963        cx.notify();
1964    }
1965
1966    fn handle_pane_event(
1967        &mut self,
1968        pane: ViewHandle<Pane>,
1969        event: &pane::Event,
1970        cx: &mut ViewContext<Self>,
1971    ) {
1972        match event {
1973            pane::Event::AddItem { item } => item.added_to_pane(self, pane, cx),
1974            pane::Event::Split(direction) => {
1975                self.split_pane(pane, *direction, cx);
1976            }
1977            pane::Event::Remove => self.remove_pane(pane, cx),
1978            pane::Event::ActivateItem { local } => {
1979                if *local {
1980                    self.unfollow(&pane, cx);
1981                }
1982                if &pane == self.active_pane() {
1983                    self.active_item_path_changed(cx);
1984                }
1985            }
1986            pane::Event::ChangeItemTitle => {
1987                if pane == self.active_pane {
1988                    self.active_item_path_changed(cx);
1989                }
1990                self.update_window_edited(cx);
1991            }
1992            pane::Event::RemoveItem { item_id } => {
1993                self.update_window_edited(cx);
1994                if let hash_map::Entry::Occupied(entry) = self.panes_by_item.entry(*item_id) {
1995                    if entry.get().id() == pane.id() {
1996                        entry.remove();
1997                    }
1998                }
1999            }
2000            pane::Event::Focus => {
2001                self.handle_pane_focused(pane.clone(), cx);
2002            }
2003            pane::Event::ZoomIn => {
2004                if pane == self.active_pane {
2005                    pane.update(cx, |pane, cx| pane.set_zoomed(true, cx));
2006                    if pane.read(cx).has_focus() {
2007                        self.zoomed = Some(pane.downgrade().into_any());
2008                        self.zoomed_position = None;
2009                    }
2010                    cx.notify();
2011                }
2012            }
2013            pane::Event::ZoomOut => {
2014                pane.update(cx, |pane, cx| pane.set_zoomed(false, cx));
2015                if self.zoomed_position.is_none() {
2016                    self.zoomed = None;
2017                }
2018                cx.notify();
2019            }
2020        }
2021
2022        self.serialize_workspace(cx);
2023    }
2024
2025    pub fn split_pane(
2026        &mut self,
2027        pane: ViewHandle<Pane>,
2028        direction: SplitDirection,
2029        cx: &mut ViewContext<Self>,
2030    ) -> Option<ViewHandle<Pane>> {
2031        let item = pane.read(cx).active_item()?;
2032        let maybe_pane_handle = if let Some(clone) = item.clone_on_split(self.database_id(), cx) {
2033            let new_pane = self.add_pane(cx);
2034            new_pane.update(cx, |pane, cx| pane.add_item(clone, true, true, None, cx));
2035            self.center.split(&pane, &new_pane, direction).unwrap();
2036            Some(new_pane)
2037        } else {
2038            None
2039        };
2040        cx.notify();
2041        maybe_pane_handle
2042    }
2043
2044    pub fn split_pane_with_item(
2045        &mut self,
2046        pane_to_split: WeakViewHandle<Pane>,
2047        split_direction: SplitDirection,
2048        from: WeakViewHandle<Pane>,
2049        item_id_to_move: usize,
2050        cx: &mut ViewContext<Self>,
2051    ) {
2052        let Some(pane_to_split) = pane_to_split.upgrade(cx) else { return; };
2053        let Some(from) = from.upgrade(cx) else { return; };
2054
2055        let new_pane = self.add_pane(cx);
2056        self.move_item(from.clone(), new_pane.clone(), item_id_to_move, 0, cx);
2057        self.center
2058            .split(&pane_to_split, &new_pane, split_direction)
2059            .unwrap();
2060        cx.notify();
2061    }
2062
2063    pub fn split_pane_with_project_entry(
2064        &mut self,
2065        pane_to_split: WeakViewHandle<Pane>,
2066        split_direction: SplitDirection,
2067        project_entry: ProjectEntryId,
2068        cx: &mut ViewContext<Self>,
2069    ) -> Option<Task<Result<()>>> {
2070        let pane_to_split = pane_to_split.upgrade(cx)?;
2071        let new_pane = self.add_pane(cx);
2072        self.center
2073            .split(&pane_to_split, &new_pane, split_direction)
2074            .unwrap();
2075
2076        let path = self.project.read(cx).path_for_entry(project_entry, cx)?;
2077        let task = self.open_path(path, Some(new_pane.downgrade()), true, cx);
2078        Some(cx.foreground().spawn(async move {
2079            task.await?;
2080            Ok(())
2081        }))
2082    }
2083
2084    pub fn move_item(
2085        &mut self,
2086        source: ViewHandle<Pane>,
2087        destination: ViewHandle<Pane>,
2088        item_id_to_move: usize,
2089        destination_index: usize,
2090        cx: &mut ViewContext<Self>,
2091    ) {
2092        let item_to_move = source
2093            .read(cx)
2094            .items()
2095            .enumerate()
2096            .find(|(_, item_handle)| item_handle.id() == item_id_to_move);
2097
2098        if item_to_move.is_none() {
2099            log::warn!("Tried to move item handle which was not in `from` pane. Maybe tab was closed during drop");
2100            return;
2101        }
2102        let (item_ix, item_handle) = item_to_move.unwrap();
2103        let item_handle = item_handle.clone();
2104
2105        if source != destination {
2106            // Close item from previous pane
2107            source.update(cx, |source, cx| {
2108                source.remove_item(item_ix, false, cx);
2109            });
2110        }
2111
2112        // This automatically removes duplicate items in the pane
2113        destination.update(cx, |destination, cx| {
2114            destination.add_item(item_handle, true, true, Some(destination_index), cx);
2115            cx.focus_self();
2116        });
2117    }
2118
2119    fn remove_pane(&mut self, pane: ViewHandle<Pane>, cx: &mut ViewContext<Self>) {
2120        if self.center.remove(&pane).unwrap() {
2121            self.force_remove_pane(&pane, cx);
2122            self.unfollow(&pane, cx);
2123            self.last_leaders_by_pane.remove(&pane.downgrade());
2124            for removed_item in pane.read(cx).items() {
2125                self.panes_by_item.remove(&removed_item.id());
2126            }
2127
2128            cx.notify();
2129        } else {
2130            self.active_item_path_changed(cx);
2131        }
2132    }
2133
2134    pub fn panes(&self) -> &[ViewHandle<Pane>] {
2135        &self.panes
2136    }
2137
2138    pub fn active_pane(&self) -> &ViewHandle<Pane> {
2139        &self.active_pane
2140    }
2141
2142    fn project_remote_id_changed(&mut self, remote_id: Option<u64>, cx: &mut ViewContext<Self>) {
2143        if let Some(remote_id) = remote_id {
2144            self.remote_entity_subscription = Some(
2145                self.app_state
2146                    .client
2147                    .add_view_for_remote_entity(remote_id, cx),
2148            );
2149        } else {
2150            self.remote_entity_subscription.take();
2151        }
2152    }
2153
2154    fn collaborator_left(&mut self, peer_id: PeerId, cx: &mut ViewContext<Self>) {
2155        self.leader_state.followers.remove(&peer_id);
2156        if let Some(states_by_pane) = self.follower_states_by_leader.remove(&peer_id) {
2157            for state in states_by_pane.into_values() {
2158                for item in state.items_by_leader_view_id.into_values() {
2159                    item.set_leader_replica_id(None, cx);
2160                }
2161            }
2162        }
2163        cx.notify();
2164    }
2165
2166    pub fn toggle_follow(
2167        &mut self,
2168        leader_id: PeerId,
2169        cx: &mut ViewContext<Self>,
2170    ) -> Option<Task<Result<()>>> {
2171        let pane = self.active_pane().clone();
2172
2173        if let Some(prev_leader_id) = self.unfollow(&pane, cx) {
2174            if leader_id == prev_leader_id {
2175                return None;
2176            }
2177        }
2178
2179        self.last_leaders_by_pane
2180            .insert(pane.downgrade(), leader_id);
2181        self.follower_states_by_leader
2182            .entry(leader_id)
2183            .or_default()
2184            .insert(pane.clone(), Default::default());
2185        cx.notify();
2186
2187        let project_id = self.project.read(cx).remote_id()?;
2188        let request = self.app_state.client.request(proto::Follow {
2189            project_id,
2190            leader_id: Some(leader_id),
2191        });
2192
2193        Some(cx.spawn(|this, mut cx| async move {
2194            let response = request.await?;
2195            this.update(&mut cx, |this, _| {
2196                let state = this
2197                    .follower_states_by_leader
2198                    .get_mut(&leader_id)
2199                    .and_then(|states_by_pane| states_by_pane.get_mut(&pane))
2200                    .ok_or_else(|| anyhow!("following interrupted"))?;
2201                state.active_view_id = if let Some(active_view_id) = response.active_view_id {
2202                    Some(ViewId::from_proto(active_view_id)?)
2203                } else {
2204                    None
2205                };
2206                Ok::<_, anyhow::Error>(())
2207            })??;
2208            Self::add_views_from_leader(
2209                this.clone(),
2210                leader_id,
2211                vec![pane],
2212                response.views,
2213                &mut cx,
2214            )
2215            .await?;
2216            this.update(&mut cx, |this, cx| this.leader_updated(leader_id, cx))?;
2217            Ok(())
2218        }))
2219    }
2220
2221    pub fn follow_next_collaborator(
2222        &mut self,
2223        _: &FollowNextCollaborator,
2224        cx: &mut ViewContext<Self>,
2225    ) -> Option<Task<Result<()>>> {
2226        let collaborators = self.project.read(cx).collaborators();
2227        let next_leader_id = if let Some(leader_id) = self.leader_for_pane(&self.active_pane) {
2228            let mut collaborators = collaborators.keys().copied();
2229            for peer_id in collaborators.by_ref() {
2230                if peer_id == leader_id {
2231                    break;
2232                }
2233            }
2234            collaborators.next()
2235        } else if let Some(last_leader_id) =
2236            self.last_leaders_by_pane.get(&self.active_pane.downgrade())
2237        {
2238            if collaborators.contains_key(last_leader_id) {
2239                Some(*last_leader_id)
2240            } else {
2241                None
2242            }
2243        } else {
2244            None
2245        };
2246
2247        next_leader_id
2248            .or_else(|| collaborators.keys().copied().next())
2249            .and_then(|leader_id| self.toggle_follow(leader_id, cx))
2250    }
2251
2252    pub fn unfollow(
2253        &mut self,
2254        pane: &ViewHandle<Pane>,
2255        cx: &mut ViewContext<Self>,
2256    ) -> Option<PeerId> {
2257        for (leader_id, states_by_pane) in &mut self.follower_states_by_leader {
2258            let leader_id = *leader_id;
2259            if let Some(state) = states_by_pane.remove(pane) {
2260                for (_, item) in state.items_by_leader_view_id {
2261                    item.set_leader_replica_id(None, cx);
2262                }
2263
2264                if states_by_pane.is_empty() {
2265                    self.follower_states_by_leader.remove(&leader_id);
2266                    if let Some(project_id) = self.project.read(cx).remote_id() {
2267                        self.app_state
2268                            .client
2269                            .send(proto::Unfollow {
2270                                project_id,
2271                                leader_id: Some(leader_id),
2272                            })
2273                            .log_err();
2274                    }
2275                }
2276
2277                cx.notify();
2278                return Some(leader_id);
2279            }
2280        }
2281        None
2282    }
2283
2284    pub fn is_being_followed(&self, peer_id: PeerId) -> bool {
2285        self.follower_states_by_leader.contains_key(&peer_id)
2286    }
2287
2288    pub fn is_followed_by(&self, peer_id: PeerId) -> bool {
2289        self.leader_state.followers.contains(&peer_id)
2290    }
2291
2292    fn render_titlebar(&self, theme: &Theme, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
2293        // TODO: There should be a better system in place for this
2294        // (https://github.com/zed-industries/zed/issues/1290)
2295        let is_fullscreen = cx.window_is_fullscreen();
2296        let container_theme = if is_fullscreen {
2297            let mut container_theme = theme.workspace.titlebar.container;
2298            container_theme.padding.left = container_theme.padding.right;
2299            container_theme
2300        } else {
2301            theme.workspace.titlebar.container
2302        };
2303
2304        enum TitleBar {}
2305        MouseEventHandler::<TitleBar, _>::new(0, cx, |_, cx| {
2306            Stack::new()
2307                .with_children(
2308                    self.titlebar_item
2309                        .as_ref()
2310                        .map(|item| ChildView::new(item, cx)),
2311                )
2312                .contained()
2313                .with_style(container_theme)
2314        })
2315        .on_click(MouseButton::Left, |event, _, cx| {
2316            if event.click_count == 2 {
2317                cx.zoom_window();
2318            }
2319        })
2320        .constrained()
2321        .with_height(theme.workspace.titlebar.height)
2322        .into_any_named("titlebar")
2323    }
2324
2325    fn active_item_path_changed(&mut self, cx: &mut ViewContext<Self>) {
2326        let active_entry = self.active_project_path(cx);
2327        self.project
2328            .update(cx, |project, cx| project.set_active_path(active_entry, cx));
2329        self.update_window_title(cx);
2330    }
2331
2332    fn update_window_title(&mut self, cx: &mut ViewContext<Self>) {
2333        let project = self.project().read(cx);
2334        let mut title = String::new();
2335
2336        if let Some(path) = self.active_item(cx).and_then(|item| item.project_path(cx)) {
2337            let filename = path
2338                .path
2339                .file_name()
2340                .map(|s| s.to_string_lossy())
2341                .or_else(|| {
2342                    Some(Cow::Borrowed(
2343                        project
2344                            .worktree_for_id(path.worktree_id, cx)?
2345                            .read(cx)
2346                            .root_name(),
2347                    ))
2348                });
2349
2350            if let Some(filename) = filename {
2351                title.push_str(filename.as_ref());
2352                title.push_str(" β€” ");
2353            }
2354        }
2355
2356        for (i, name) in project.worktree_root_names(cx).enumerate() {
2357            if i > 0 {
2358                title.push_str(", ");
2359            }
2360            title.push_str(name);
2361        }
2362
2363        if title.is_empty() {
2364            title = "empty project".to_string();
2365        }
2366
2367        if project.is_remote() {
2368            title.push_str(" ↙");
2369        } else if project.is_shared() {
2370            title.push_str(" β†—");
2371        }
2372
2373        cx.set_window_title(&title);
2374    }
2375
2376    fn update_window_edited(&mut self, cx: &mut ViewContext<Self>) {
2377        let is_edited = !self.project.read(cx).is_read_only()
2378            && self
2379                .items(cx)
2380                .any(|item| item.has_conflict(cx) || item.is_dirty(cx));
2381        if is_edited != self.window_edited {
2382            self.window_edited = is_edited;
2383            cx.set_window_edited(self.window_edited)
2384        }
2385    }
2386
2387    fn render_disconnected_overlay(
2388        &self,
2389        cx: &mut ViewContext<Workspace>,
2390    ) -> Option<AnyElement<Workspace>> {
2391        if self.project.read(cx).is_read_only() {
2392            enum DisconnectedOverlay {}
2393            Some(
2394                MouseEventHandler::<DisconnectedOverlay, _>::new(0, cx, |_, cx| {
2395                    let theme = &theme::current(cx);
2396                    Label::new(
2397                        "Your connection to the remote project has been lost.",
2398                        theme.workspace.disconnected_overlay.text.clone(),
2399                    )
2400                    .aligned()
2401                    .contained()
2402                    .with_style(theme.workspace.disconnected_overlay.container)
2403                })
2404                .with_cursor_style(CursorStyle::Arrow)
2405                .capture_all()
2406                .into_any_named("disconnected overlay"),
2407            )
2408        } else {
2409            None
2410        }
2411    }
2412
2413    fn render_notifications(
2414        &self,
2415        theme: &theme::Workspace,
2416        cx: &AppContext,
2417    ) -> Option<AnyElement<Workspace>> {
2418        if self.notifications.is_empty() {
2419            None
2420        } else {
2421            Some(
2422                Flex::column()
2423                    .with_children(self.notifications.iter().map(|(_, _, notification)| {
2424                        ChildView::new(notification.as_any(), cx)
2425                            .contained()
2426                            .with_style(theme.notification)
2427                    }))
2428                    .constrained()
2429                    .with_width(theme.notifications.width)
2430                    .contained()
2431                    .with_style(theme.notifications.container)
2432                    .aligned()
2433                    .bottom()
2434                    .right()
2435                    .into_any(),
2436            )
2437        }
2438    }
2439
2440    // RPC handlers
2441
2442    async fn handle_follow(
2443        this: WeakViewHandle<Self>,
2444        envelope: TypedEnvelope<proto::Follow>,
2445        _: Arc<Client>,
2446        mut cx: AsyncAppContext,
2447    ) -> Result<proto::FollowResponse> {
2448        this.update(&mut cx, |this, cx| {
2449            let client = &this.app_state.client;
2450            this.leader_state
2451                .followers
2452                .insert(envelope.original_sender_id()?);
2453
2454            let active_view_id = this.active_item(cx).and_then(|i| {
2455                Some(
2456                    i.to_followable_item_handle(cx)?
2457                        .remote_id(client, cx)?
2458                        .to_proto(),
2459                )
2460            });
2461
2462            cx.notify();
2463
2464            Ok(proto::FollowResponse {
2465                active_view_id,
2466                views: this
2467                    .panes()
2468                    .iter()
2469                    .flat_map(|pane| {
2470                        let leader_id = this.leader_for_pane(pane);
2471                        pane.read(cx).items().filter_map({
2472                            let cx = &cx;
2473                            move |item| {
2474                                let item = item.to_followable_item_handle(cx)?;
2475                                let id = item.remote_id(client, cx)?.to_proto();
2476                                let variant = item.to_state_proto(cx)?;
2477                                Some(proto::View {
2478                                    id: Some(id),
2479                                    leader_id,
2480                                    variant: Some(variant),
2481                                })
2482                            }
2483                        })
2484                    })
2485                    .collect(),
2486            })
2487        })?
2488    }
2489
2490    async fn handle_unfollow(
2491        this: WeakViewHandle<Self>,
2492        envelope: TypedEnvelope<proto::Unfollow>,
2493        _: Arc<Client>,
2494        mut cx: AsyncAppContext,
2495    ) -> Result<()> {
2496        this.update(&mut cx, |this, cx| {
2497            this.leader_state
2498                .followers
2499                .remove(&envelope.original_sender_id()?);
2500            cx.notify();
2501            Ok(())
2502        })?
2503    }
2504
2505    async fn handle_update_followers(
2506        this: WeakViewHandle<Self>,
2507        envelope: TypedEnvelope<proto::UpdateFollowers>,
2508        _: Arc<Client>,
2509        cx: AsyncAppContext,
2510    ) -> Result<()> {
2511        let leader_id = envelope.original_sender_id()?;
2512        this.read_with(&cx, |this, _| {
2513            this.leader_updates_tx
2514                .unbounded_send((leader_id, envelope.payload))
2515        })??;
2516        Ok(())
2517    }
2518
2519    async fn process_leader_update(
2520        this: &WeakViewHandle<Self>,
2521        leader_id: PeerId,
2522        update: proto::UpdateFollowers,
2523        cx: &mut AsyncAppContext,
2524    ) -> Result<()> {
2525        match update.variant.ok_or_else(|| anyhow!("invalid update"))? {
2526            proto::update_followers::Variant::UpdateActiveView(update_active_view) => {
2527                this.update(cx, |this, _| {
2528                    if let Some(state) = this.follower_states_by_leader.get_mut(&leader_id) {
2529                        for state in state.values_mut() {
2530                            state.active_view_id =
2531                                if let Some(active_view_id) = update_active_view.id.clone() {
2532                                    Some(ViewId::from_proto(active_view_id)?)
2533                                } else {
2534                                    None
2535                                };
2536                        }
2537                    }
2538                    anyhow::Ok(())
2539                })??;
2540            }
2541            proto::update_followers::Variant::UpdateView(update_view) => {
2542                let variant = update_view
2543                    .variant
2544                    .ok_or_else(|| anyhow!("missing update view variant"))?;
2545                let id = update_view
2546                    .id
2547                    .ok_or_else(|| anyhow!("missing update view id"))?;
2548                let mut tasks = Vec::new();
2549                this.update(cx, |this, cx| {
2550                    let project = this.project.clone();
2551                    if let Some(state) = this.follower_states_by_leader.get_mut(&leader_id) {
2552                        for state in state.values_mut() {
2553                            let view_id = ViewId::from_proto(id.clone())?;
2554                            if let Some(item) = state.items_by_leader_view_id.get(&view_id) {
2555                                tasks.push(item.apply_update_proto(&project, variant.clone(), cx));
2556                            }
2557                        }
2558                    }
2559                    anyhow::Ok(())
2560                })??;
2561                try_join_all(tasks).await.log_err();
2562            }
2563            proto::update_followers::Variant::CreateView(view) => {
2564                let panes = this.read_with(cx, |this, _| {
2565                    this.follower_states_by_leader
2566                        .get(&leader_id)
2567                        .into_iter()
2568                        .flat_map(|states_by_pane| states_by_pane.keys())
2569                        .cloned()
2570                        .collect()
2571                })?;
2572                Self::add_views_from_leader(this.clone(), leader_id, panes, vec![view], cx).await?;
2573            }
2574        }
2575        this.update(cx, |this, cx| this.leader_updated(leader_id, cx))?;
2576        Ok(())
2577    }
2578
2579    async fn add_views_from_leader(
2580        this: WeakViewHandle<Self>,
2581        leader_id: PeerId,
2582        panes: Vec<ViewHandle<Pane>>,
2583        views: Vec<proto::View>,
2584        cx: &mut AsyncAppContext,
2585    ) -> Result<()> {
2586        let project = this.read_with(cx, |this, _| this.project.clone())?;
2587        let replica_id = project
2588            .read_with(cx, |project, _| {
2589                project
2590                    .collaborators()
2591                    .get(&leader_id)
2592                    .map(|c| c.replica_id)
2593            })
2594            .ok_or_else(|| anyhow!("no such collaborator {}", leader_id))?;
2595
2596        let item_builders = cx.update(|cx| {
2597            cx.default_global::<FollowableItemBuilders>()
2598                .values()
2599                .map(|b| b.0)
2600                .collect::<Vec<_>>()
2601        });
2602
2603        let mut item_tasks_by_pane = HashMap::default();
2604        for pane in panes {
2605            let mut item_tasks = Vec::new();
2606            let mut leader_view_ids = Vec::new();
2607            for view in &views {
2608                let Some(id) = &view.id else { continue };
2609                let id = ViewId::from_proto(id.clone())?;
2610                let mut variant = view.variant.clone();
2611                if variant.is_none() {
2612                    Err(anyhow!("missing variant"))?;
2613                }
2614                for build_item in &item_builders {
2615                    let task = cx.update(|cx| {
2616                        build_item(pane.clone(), project.clone(), id, &mut variant, cx)
2617                    });
2618                    if let Some(task) = task {
2619                        item_tasks.push(task);
2620                        leader_view_ids.push(id);
2621                        break;
2622                    } else {
2623                        assert!(variant.is_some());
2624                    }
2625                }
2626            }
2627
2628            item_tasks_by_pane.insert(pane, (item_tasks, leader_view_ids));
2629        }
2630
2631        for (pane, (item_tasks, leader_view_ids)) in item_tasks_by_pane {
2632            let items = futures::future::try_join_all(item_tasks).await?;
2633            this.update(cx, |this, cx| {
2634                let state = this
2635                    .follower_states_by_leader
2636                    .get_mut(&leader_id)?
2637                    .get_mut(&pane)?;
2638
2639                for (id, item) in leader_view_ids.into_iter().zip(items) {
2640                    item.set_leader_replica_id(Some(replica_id), cx);
2641                    state.items_by_leader_view_id.insert(id, item);
2642                }
2643
2644                Some(())
2645            })?;
2646        }
2647        Ok(())
2648    }
2649
2650    fn update_followers(
2651        &self,
2652        update: proto::update_followers::Variant,
2653        cx: &AppContext,
2654    ) -> Option<()> {
2655        let project_id = self.project.read(cx).remote_id()?;
2656        if !self.leader_state.followers.is_empty() {
2657            self.app_state
2658                .client
2659                .send(proto::UpdateFollowers {
2660                    project_id,
2661                    follower_ids: self.leader_state.followers.iter().copied().collect(),
2662                    variant: Some(update),
2663                })
2664                .log_err();
2665        }
2666        None
2667    }
2668
2669    pub fn leader_for_pane(&self, pane: &ViewHandle<Pane>) -> Option<PeerId> {
2670        self.follower_states_by_leader
2671            .iter()
2672            .find_map(|(leader_id, state)| {
2673                if state.contains_key(pane) {
2674                    Some(*leader_id)
2675                } else {
2676                    None
2677                }
2678            })
2679    }
2680
2681    fn leader_updated(&mut self, leader_id: PeerId, cx: &mut ViewContext<Self>) -> Option<()> {
2682        cx.notify();
2683
2684        let call = self.active_call()?;
2685        let room = call.read(cx).room()?.read(cx);
2686        let participant = room.remote_participant_for_peer_id(leader_id)?;
2687        let mut items_to_activate = Vec::new();
2688        match participant.location {
2689            call::ParticipantLocation::SharedProject { project_id } => {
2690                if Some(project_id) == self.project.read(cx).remote_id() {
2691                    for (pane, state) in self.follower_states_by_leader.get(&leader_id)? {
2692                        if let Some(item) = state
2693                            .active_view_id
2694                            .and_then(|id| state.items_by_leader_view_id.get(&id))
2695                        {
2696                            items_to_activate.push((pane.clone(), item.boxed_clone()));
2697                        } else {
2698                            if let Some(shared_screen) =
2699                                self.shared_screen_for_peer(leader_id, pane, cx)
2700                            {
2701                                items_to_activate.push((pane.clone(), Box::new(shared_screen)));
2702                            }
2703                        }
2704                    }
2705                }
2706            }
2707            call::ParticipantLocation::UnsharedProject => {}
2708            call::ParticipantLocation::External => {
2709                for (pane, _) in self.follower_states_by_leader.get(&leader_id)? {
2710                    if let Some(shared_screen) = self.shared_screen_for_peer(leader_id, pane, cx) {
2711                        items_to_activate.push((pane.clone(), Box::new(shared_screen)));
2712                    }
2713                }
2714            }
2715        }
2716
2717        for (pane, item) in items_to_activate {
2718            let pane_was_focused = pane.read(cx).has_focus();
2719            if let Some(index) = pane.update(cx, |pane, _| pane.index_for_item(item.as_ref())) {
2720                pane.update(cx, |pane, cx| pane.activate_item(index, false, false, cx));
2721            } else {
2722                pane.update(cx, |pane, cx| {
2723                    pane.add_item(item.boxed_clone(), false, false, None, cx)
2724                });
2725            }
2726
2727            if pane_was_focused {
2728                pane.update(cx, |pane, cx| pane.focus_active_item(cx));
2729            }
2730        }
2731
2732        None
2733    }
2734
2735    fn shared_screen_for_peer(
2736        &self,
2737        peer_id: PeerId,
2738        pane: &ViewHandle<Pane>,
2739        cx: &mut ViewContext<Self>,
2740    ) -> Option<ViewHandle<SharedScreen>> {
2741        let call = self.active_call()?;
2742        let room = call.read(cx).room()?.read(cx);
2743        let participant = room.remote_participant_for_peer_id(peer_id)?;
2744        let track = participant.tracks.values().next()?.clone();
2745        let user = participant.user.clone();
2746
2747        for item in pane.read(cx).items_of_type::<SharedScreen>() {
2748            if item.read(cx).peer_id == peer_id {
2749                return Some(item);
2750            }
2751        }
2752
2753        Some(cx.add_view(|cx| SharedScreen::new(&track, peer_id, user.clone(), cx)))
2754    }
2755
2756    pub fn on_window_activation_changed(&mut self, active: bool, cx: &mut ViewContext<Self>) {
2757        if active {
2758            cx.background()
2759                .spawn(persistence::DB.update_timestamp(self.database_id()))
2760                .detach();
2761        } else {
2762            for pane in &self.panes {
2763                pane.update(cx, |pane, cx| {
2764                    if let Some(item) = pane.active_item() {
2765                        item.workspace_deactivated(cx);
2766                    }
2767                    if matches!(
2768                        settings::get::<WorkspaceSettings>(cx).autosave,
2769                        AutosaveSetting::OnWindowChange | AutosaveSetting::OnFocusChange
2770                    ) {
2771                        for item in pane.items() {
2772                            Pane::autosave_item(item.as_ref(), self.project.clone(), cx)
2773                                .detach_and_log_err(cx);
2774                        }
2775                    }
2776                });
2777            }
2778        }
2779    }
2780
2781    fn active_call(&self) -> Option<&ModelHandle<ActiveCall>> {
2782        self.active_call.as_ref().map(|(call, _)| call)
2783    }
2784
2785    fn on_active_call_event(
2786        &mut self,
2787        _: ModelHandle<ActiveCall>,
2788        event: &call::room::Event,
2789        cx: &mut ViewContext<Self>,
2790    ) {
2791        match event {
2792            call::room::Event::ParticipantLocationChanged { participant_id }
2793            | call::room::Event::RemoteVideoTracksChanged { participant_id } => {
2794                self.leader_updated(*participant_id, cx);
2795            }
2796            _ => {}
2797        }
2798    }
2799
2800    pub fn database_id(&self) -> WorkspaceId {
2801        self.database_id
2802    }
2803
2804    fn location(&self, cx: &AppContext) -> Option<WorkspaceLocation> {
2805        let project = self.project().read(cx);
2806
2807        if project.is_local() {
2808            Some(
2809                project
2810                    .visible_worktrees(cx)
2811                    .map(|worktree| worktree.read(cx).abs_path())
2812                    .collect::<Vec<_>>()
2813                    .into(),
2814            )
2815        } else {
2816            None
2817        }
2818    }
2819
2820    fn remove_panes(&mut self, member: Member, cx: &mut ViewContext<Workspace>) {
2821        match member {
2822            Member::Axis(PaneAxis { members, .. }) => {
2823                for child in members.iter() {
2824                    self.remove_panes(child.clone(), cx)
2825                }
2826            }
2827            Member::Pane(pane) => {
2828                self.force_remove_pane(&pane, cx);
2829            }
2830        }
2831    }
2832
2833    fn force_remove_pane(&mut self, pane: &ViewHandle<Pane>, cx: &mut ViewContext<Workspace>) {
2834        self.panes.retain(|p| p != pane);
2835        cx.focus(self.panes.last().unwrap());
2836        if self.last_active_center_pane == Some(pane.downgrade()) {
2837            self.last_active_center_pane = None;
2838        }
2839        cx.notify();
2840    }
2841
2842    fn serialize_workspace(&self, cx: &AppContext) {
2843        fn serialize_pane_handle(
2844            pane_handle: &ViewHandle<Pane>,
2845            cx: &AppContext,
2846        ) -> SerializedPane {
2847            let (items, active) = {
2848                let pane = pane_handle.read(cx);
2849                let active_item_id = pane.active_item().map(|item| item.id());
2850                (
2851                    pane.items()
2852                        .filter_map(|item_handle| {
2853                            Some(SerializedItem {
2854                                kind: Arc::from(item_handle.serialized_item_kind()?),
2855                                item_id: item_handle.id(),
2856                                active: Some(item_handle.id()) == active_item_id,
2857                            })
2858                        })
2859                        .collect::<Vec<_>>(),
2860                    pane.has_focus(),
2861                )
2862            };
2863
2864            SerializedPane::new(items, active)
2865        }
2866
2867        fn build_serialized_pane_group(
2868            pane_group: &Member,
2869            cx: &AppContext,
2870        ) -> SerializedPaneGroup {
2871            match pane_group {
2872                Member::Axis(PaneAxis { axis, members }) => SerializedPaneGroup::Group {
2873                    axis: *axis,
2874                    children: members
2875                        .iter()
2876                        .map(|member| build_serialized_pane_group(member, cx))
2877                        .collect::<Vec<_>>(),
2878                },
2879                Member::Pane(pane_handle) => {
2880                    SerializedPaneGroup::Pane(serialize_pane_handle(&pane_handle, cx))
2881                }
2882            }
2883        }
2884
2885        fn build_serialized_docks(this: &Workspace, cx: &AppContext) -> DockStructure {
2886            let left_dock = this.left_dock.read(cx);
2887            let left_visible = left_dock.is_open();
2888            let left_active_panel = left_dock.visible_panel().and_then(|panel| {
2889                Some(
2890                    cx.view_ui_name(panel.as_any().window_id(), panel.id())?
2891                        .to_string(),
2892                )
2893            });
2894
2895            let right_dock = this.right_dock.read(cx);
2896            let right_visible = right_dock.is_open();
2897            let right_active_panel = right_dock.visible_panel().and_then(|panel| {
2898                Some(
2899                    cx.view_ui_name(panel.as_any().window_id(), panel.id())?
2900                        .to_string(),
2901                )
2902            });
2903
2904            let bottom_dock = this.bottom_dock.read(cx);
2905            let bottom_visible = bottom_dock.is_open();
2906            let bottom_active_panel = bottom_dock.visible_panel().and_then(|panel| {
2907                Some(
2908                    cx.view_ui_name(panel.as_any().window_id(), panel.id())?
2909                        .to_string(),
2910                )
2911            });
2912
2913            DockStructure {
2914                left: DockData {
2915                    visible: left_visible,
2916                    active_panel: left_active_panel,
2917                },
2918                right: DockData {
2919                    visible: right_visible,
2920                    active_panel: right_active_panel,
2921                },
2922                bottom: DockData {
2923                    visible: bottom_visible,
2924                    active_panel: bottom_active_panel,
2925                },
2926            }
2927        }
2928
2929        if let Some(location) = self.location(cx) {
2930            // Load bearing special case:
2931            //  - with_local_workspace() relies on this to not have other stuff open
2932            //    when you open your log
2933            if !location.paths().is_empty() {
2934                let center_group = build_serialized_pane_group(&self.center.root, cx);
2935                let docks = build_serialized_docks(self, cx);
2936
2937                let serialized_workspace = SerializedWorkspace {
2938                    id: self.database_id,
2939                    location,
2940                    center_group,
2941                    bounds: Default::default(),
2942                    display: Default::default(),
2943                    docks,
2944                };
2945
2946                cx.background()
2947                    .spawn(persistence::DB.save_workspace(serialized_workspace))
2948                    .detach();
2949            }
2950        }
2951    }
2952
2953    pub(crate) fn load_workspace(
2954        workspace: WeakViewHandle<Workspace>,
2955        serialized_workspace: SerializedWorkspace,
2956        paths_to_open: Vec<Option<ProjectPath>>,
2957        cx: &mut AppContext,
2958    ) -> Task<Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>> {
2959        cx.spawn(|mut cx| async move {
2960            let result = async_iife! {{
2961                let (project, old_center_pane) =
2962                workspace.read_with(&cx, |workspace, _| {
2963                    (
2964                        workspace.project().clone(),
2965                        workspace.last_active_center_pane.clone(),
2966                    )
2967                })?;
2968
2969                let mut center_items = None;
2970                let mut center_group = None;
2971                // Traverse the splits tree and add to things
2972                if let Some((group, active_pane, items)) = serialized_workspace
2973                        .center_group
2974                        .deserialize(&project, serialized_workspace.id, &workspace, &mut cx)
2975                        .await {
2976                    center_items = Some(items);
2977                    center_group = Some((group, active_pane))
2978                }
2979
2980                let resulting_list = cx.read(|cx| {
2981                    let mut opened_items = center_items
2982                        .unwrap_or_default()
2983                        .into_iter()
2984                        .filter_map(|item| {
2985                            let item = item?;
2986                            let project_path = item.project_path(cx)?;
2987                            Some((project_path, item))
2988                        })
2989                        .collect::<HashMap<_, _>>();
2990
2991                    paths_to_open
2992                        .into_iter()
2993                        .map(|path_to_open| {
2994                            path_to_open.map(|path_to_open| {
2995                                Ok(opened_items.remove(&path_to_open))
2996                            })
2997                            .transpose()
2998                            .map(|item| item.flatten())
2999                            .transpose()
3000                        })
3001                        .collect::<Vec<_>>()
3002                });
3003
3004                // Remove old panes from workspace panes list
3005                workspace.update(&mut cx, |workspace, cx| {
3006                    if let Some((center_group, active_pane)) = center_group {
3007                        workspace.remove_panes(workspace.center.root.clone(), cx);
3008
3009                        // Swap workspace center group
3010                        workspace.center = PaneGroup::with_root(center_group);
3011
3012                        // Change the focus to the workspace first so that we retrigger focus in on the pane.
3013                        cx.focus_self();
3014
3015                        if let Some(active_pane) = active_pane {
3016                            cx.focus(&active_pane);
3017                        } else {
3018                            cx.focus(workspace.panes.last().unwrap());
3019                        }
3020                    } else {
3021                        let old_center_handle = old_center_pane.and_then(|weak| weak.upgrade(cx));
3022                        if let Some(old_center_handle) = old_center_handle {
3023                            cx.focus(&old_center_handle)
3024                        } else {
3025                            cx.focus_self()
3026                        }
3027                    }
3028
3029                    let docks = serialized_workspace.docks;
3030                    workspace.left_dock.update(cx, |dock, cx| {
3031                        dock.set_open(docks.left.visible, cx);
3032                        if let Some(active_panel) = docks.left.active_panel {
3033                            if let Some(ix) = dock.panel_index_for_ui_name(&active_panel, cx) {
3034                                dock.activate_panel(ix, cx);
3035                            }
3036                        }
3037                    });
3038                    workspace.right_dock.update(cx, |dock, cx| {
3039                        dock.set_open(docks.right.visible, cx);
3040                        if let Some(active_panel) = docks.right.active_panel {
3041                            if let Some(ix) = dock.panel_index_for_ui_name(&active_panel, cx) {
3042                                dock.activate_panel(ix, cx);
3043                            }
3044                        }
3045                    });
3046                    workspace.bottom_dock.update(cx, |dock, cx| {
3047                        dock.set_open(docks.bottom.visible, cx);
3048                        if let Some(active_panel) = docks.bottom.active_panel {
3049                            if let Some(ix) = dock.panel_index_for_ui_name(&active_panel, cx) {
3050                                dock.activate_panel(ix, cx);
3051                            }
3052                        }
3053                    });
3054
3055                    cx.notify();
3056                })?;
3057
3058                // Serialize ourself to make sure our timestamps and any pane / item changes are replicated
3059                workspace.read_with(&cx, |workspace, cx| workspace.serialize_workspace(cx))?;
3060
3061                Ok::<_, anyhow::Error>(resulting_list)
3062            }};
3063
3064            result.await.unwrap_or_default()
3065        })
3066    }
3067
3068    #[cfg(any(test, feature = "test-support"))]
3069    pub fn test_new(project: ModelHandle<Project>, cx: &mut ViewContext<Self>) -> Self {
3070        let app_state = Arc::new(AppState {
3071            languages: project.read(cx).languages().clone(),
3072            client: project.read(cx).client(),
3073            user_store: project.read(cx).user_store(),
3074            fs: project.read(cx).fs().clone(),
3075            build_window_options: |_, _, _| Default::default(),
3076            initialize_workspace: |_, _, _, _| Task::ready(Ok(())),
3077            background_actions: || &[],
3078        });
3079        Self::new(0, project, app_state, cx)
3080    }
3081
3082    fn render_dock(&self, position: DockPosition, cx: &WindowContext) -> Option<AnyElement<Self>> {
3083        let dock = match position {
3084            DockPosition::Left => &self.left_dock,
3085            DockPosition::Right => &self.right_dock,
3086            DockPosition::Bottom => &self.bottom_dock,
3087        };
3088        let active_panel = dock.read(cx).visible_panel()?;
3089        let element = if Some(active_panel.id()) == self.zoomed.as_ref().map(|zoomed| zoomed.id()) {
3090            dock.read(cx).render_placeholder(cx)
3091        } else {
3092            ChildView::new(dock, cx).into_any()
3093        };
3094
3095        Some(
3096            element
3097                .constrained()
3098                .dynamically(move |constraint, _, cx| match position {
3099                    DockPosition::Left | DockPosition::Right => SizeConstraint::new(
3100                        Vector2F::new(20., constraint.min.y()),
3101                        Vector2F::new(cx.window_size().x() * 0.8, constraint.max.y()),
3102                    ),
3103                    DockPosition::Bottom => SizeConstraint::new(
3104                        Vector2F::new(constraint.min.x(), 20.),
3105                        Vector2F::new(constraint.max.x(), cx.window_size().y() * 0.8),
3106                    ),
3107                })
3108                .into_any(),
3109        )
3110    }
3111}
3112
3113async fn open_items(
3114    serialized_workspace: Option<SerializedWorkspace>,
3115    workspace: &WeakViewHandle<Workspace>,
3116    mut project_paths_to_open: Vec<(PathBuf, Option<ProjectPath>)>,
3117    app_state: Arc<AppState>,
3118    mut cx: AsyncAppContext,
3119) -> Vec<Option<anyhow::Result<Box<dyn ItemHandle>>>> {
3120    let mut opened_items = Vec::with_capacity(project_paths_to_open.len());
3121
3122    if let Some(serialized_workspace) = serialized_workspace {
3123        let workspace = workspace.clone();
3124        let restored_items = cx
3125            .update(|cx| {
3126                Workspace::load_workspace(
3127                    workspace,
3128                    serialized_workspace,
3129                    project_paths_to_open
3130                        .iter()
3131                        .map(|(_, project_path)| project_path)
3132                        .cloned()
3133                        .collect(),
3134                    cx,
3135                )
3136            })
3137            .await;
3138
3139        let restored_project_paths = cx.read(|cx| {
3140            restored_items
3141                .iter()
3142                .filter_map(|item| item.as_ref()?.as_ref().ok()?.project_path(cx))
3143                .collect::<HashSet<_>>()
3144        });
3145
3146        opened_items = restored_items;
3147        project_paths_to_open
3148            .iter_mut()
3149            .for_each(|(_, project_path)| {
3150                if let Some(project_path_to_open) = project_path {
3151                    if restored_project_paths.contains(project_path_to_open) {
3152                        *project_path = None;
3153                    }
3154                }
3155            });
3156    } else {
3157        for _ in 0..project_paths_to_open.len() {
3158            opened_items.push(None);
3159        }
3160    }
3161    assert!(opened_items.len() == project_paths_to_open.len());
3162
3163    let tasks =
3164        project_paths_to_open
3165            .into_iter()
3166            .enumerate()
3167            .map(|(i, (abs_path, project_path))| {
3168                let workspace = workspace.clone();
3169                cx.spawn(|mut cx| {
3170                    let fs = app_state.fs.clone();
3171                    async move {
3172                        let file_project_path = project_path?;
3173                        if fs.is_file(&abs_path).await {
3174                            Some((
3175                                i,
3176                                workspace
3177                                    .update(&mut cx, |workspace, cx| {
3178                                        workspace.open_path(file_project_path, None, true, cx)
3179                                    })
3180                                    .log_err()?
3181                                    .await,
3182                            ))
3183                        } else {
3184                            None
3185                        }
3186                    }
3187                })
3188            });
3189
3190    for maybe_opened_path in futures::future::join_all(tasks.into_iter())
3191        .await
3192        .into_iter()
3193    {
3194        if let Some((i, path_open_result)) = maybe_opened_path {
3195            opened_items[i] = Some(path_open_result);
3196        }
3197    }
3198
3199    opened_items
3200}
3201
3202fn notify_of_new_dock(workspace: &WeakViewHandle<Workspace>, cx: &mut AsyncAppContext) {
3203    const NEW_PANEL_BLOG_POST: &str = "https://zed.dev/blog/new-panel-system";
3204    const NEW_DOCK_HINT_KEY: &str = "show_new_dock_key";
3205    const MESSAGE_ID: usize = 2;
3206
3207    if workspace
3208        .read_with(cx, |workspace, cx| {
3209            workspace.has_shown_notification_once::<MessageNotification>(MESSAGE_ID, cx)
3210        })
3211        .unwrap_or(false)
3212    {
3213        return;
3214    }
3215
3216    if db::kvp::KEY_VALUE_STORE
3217        .read_kvp(NEW_DOCK_HINT_KEY)
3218        .ok()
3219        .flatten()
3220        .is_some()
3221    {
3222        if !workspace
3223            .read_with(cx, |workspace, cx| {
3224                workspace.has_shown_notification_once::<MessageNotification>(MESSAGE_ID, cx)
3225            })
3226            .unwrap_or(false)
3227        {
3228            cx.update(|cx| {
3229                cx.update_global::<NotificationTracker, _, _>(|tracker, _| {
3230                    let entry = tracker
3231                        .entry(TypeId::of::<MessageNotification>())
3232                        .or_default();
3233                    if !entry.contains(&MESSAGE_ID) {
3234                        entry.push(MESSAGE_ID);
3235                    }
3236                });
3237            });
3238        }
3239
3240        return;
3241    }
3242
3243    cx.spawn(|_| async move {
3244        db::kvp::KEY_VALUE_STORE
3245            .write_kvp(NEW_DOCK_HINT_KEY.to_string(), "seen".to_string())
3246            .await
3247            .ok();
3248    })
3249    .detach();
3250
3251    workspace
3252        .update(cx, |workspace, cx| {
3253            workspace.show_notification_once(2, cx, |cx| {
3254                cx.add_view(|_| {
3255                    MessageNotification::new_element(|text, _| {
3256                        Text::new(
3257                            "Looking for the dock? Try ctrl-`!\nshift-escape now zooms your pane.",
3258                            text,
3259                        )
3260                        .with_custom_runs(vec![26..32, 34..46], |_, bounds, scene, cx| {
3261                            let code_span_background_color = settings::get::<ThemeSettings>(cx)
3262                                .theme
3263                                .editor
3264                                .document_highlight_read_background;
3265
3266                            scene.push_quad(gpui::Quad {
3267                                bounds,
3268                                background: Some(code_span_background_color),
3269                                border: Default::default(),
3270                                corner_radius: 2.0,
3271                            })
3272                        })
3273                        .into_any()
3274                    })
3275                    .with_click_message("Read more about the new panel system")
3276                    .on_click(|cx| cx.platform().open_url(NEW_PANEL_BLOG_POST))
3277                })
3278            })
3279        })
3280        .ok();
3281}
3282
3283fn notify_if_database_failed(workspace: &WeakViewHandle<Workspace>, cx: &mut AsyncAppContext) {
3284    const REPORT_ISSUE_URL: &str ="https://github.com/zed-industries/community/issues/new?assignees=&labels=defect%2Ctriage&template=2_bug_report.yml";
3285
3286    workspace
3287        .update(cx, |workspace, cx| {
3288            if (*db::ALL_FILE_DB_FAILED).load(std::sync::atomic::Ordering::Acquire) {
3289                workspace.show_notification_once(0, cx, |cx| {
3290                    cx.add_view(|_| {
3291                        MessageNotification::new("Failed to load any database file.")
3292                            .with_click_message("Click to let us know about this error")
3293                            .on_click(|cx| cx.platform().open_url(REPORT_ISSUE_URL))
3294                    })
3295                });
3296            } else {
3297                let backup_path = (*db::BACKUP_DB_PATH).read();
3298                if let Some(backup_path) = backup_path.clone() {
3299                    workspace.show_notification_once(1, cx, move |cx| {
3300                        cx.add_view(move |_| {
3301                            MessageNotification::new(format!(
3302                                "Database file was corrupted. Old database backed up to {}",
3303                                backup_path.display()
3304                            ))
3305                            .with_click_message("Click to show old database in finder")
3306                            .on_click(move |cx| {
3307                                cx.platform().open_url(&backup_path.to_string_lossy())
3308                            })
3309                        })
3310                    });
3311                }
3312            }
3313        })
3314        .log_err();
3315}
3316
3317impl Entity for Workspace {
3318    type Event = Event;
3319}
3320
3321impl View for Workspace {
3322    fn ui_name() -> &'static str {
3323        "Workspace"
3324    }
3325
3326    fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
3327        let theme = theme::current(cx).clone();
3328        Stack::new()
3329            .with_child(
3330                Flex::column()
3331                    .with_child(self.render_titlebar(&theme, cx))
3332                    .with_child(
3333                        Stack::new()
3334                            .with_child({
3335                                let project = self.project.clone();
3336                                Flex::row()
3337                                    .with_children(self.render_dock(DockPosition::Left, cx))
3338                                    .with_child(
3339                                        Flex::column()
3340                                            .with_child(
3341                                                FlexItem::new(
3342                                                    self.center.render(
3343                                                        &project,
3344                                                        &theme,
3345                                                        &self.follower_states_by_leader,
3346                                                        self.active_call(),
3347                                                        self.active_pane(),
3348                                                        self.zoomed
3349                                                            .as_ref()
3350                                                            .and_then(|zoomed| zoomed.upgrade(cx))
3351                                                            .as_ref(),
3352                                                        &self.app_state,
3353                                                        cx,
3354                                                    ),
3355                                                )
3356                                                .flex(1., true),
3357                                            )
3358                                            .with_children(
3359                                                self.render_dock(DockPosition::Bottom, cx),
3360                                            )
3361                                            .flex(1., true),
3362                                    )
3363                                    .with_children(self.render_dock(DockPosition::Right, cx))
3364                            })
3365                            .with_child(Overlay::new(
3366                                Stack::new()
3367                                    .with_children(self.zoomed.as_ref().and_then(|zoomed| {
3368                                        enum ZoomBackground {}
3369                                        let zoomed = zoomed.upgrade(cx)?;
3370
3371                                        let mut foreground_style =
3372                                            theme.workspace.zoomed_pane_foreground;
3373                                        if let Some(zoomed_dock_position) = self.zoomed_position {
3374                                            foreground_style =
3375                                                theme.workspace.zoomed_panel_foreground;
3376                                            let margin = foreground_style.margin.top;
3377                                            let border = foreground_style.border.top;
3378
3379                                            // Only include a margin and border on the opposite side.
3380                                            foreground_style.margin.top = 0.;
3381                                            foreground_style.margin.left = 0.;
3382                                            foreground_style.margin.bottom = 0.;
3383                                            foreground_style.margin.right = 0.;
3384                                            foreground_style.border.top = false;
3385                                            foreground_style.border.left = false;
3386                                            foreground_style.border.bottom = false;
3387                                            foreground_style.border.right = false;
3388                                            match zoomed_dock_position {
3389                                                DockPosition::Left => {
3390                                                    foreground_style.margin.right = margin;
3391                                                    foreground_style.border.right = border;
3392                                                }
3393                                                DockPosition::Right => {
3394                                                    foreground_style.margin.left = margin;
3395                                                    foreground_style.border.left = border;
3396                                                }
3397                                                DockPosition::Bottom => {
3398                                                    foreground_style.margin.top = margin;
3399                                                    foreground_style.border.top = border;
3400                                                }
3401                                            }
3402                                        }
3403
3404                                        Some(
3405                                            ChildView::new(&zoomed, cx)
3406                                                .contained()
3407                                                .with_style(foreground_style)
3408                                                .aligned()
3409                                                .contained()
3410                                                .with_style(theme.workspace.zoomed_background)
3411                                                .mouse::<ZoomBackground>(0)
3412                                                .capture_all()
3413                                                .on_down(
3414                                                    MouseButton::Left,
3415                                                    |_, this: &mut Self, cx| {
3416                                                        this.zoom_out(cx);
3417                                                    },
3418                                                ),
3419                                        )
3420                                    }))
3421                                    .with_children(self.modal.as_ref().map(|modal| {
3422                                        ChildView::new(modal, cx)
3423                                            .contained()
3424                                            .with_style(theme.workspace.modal)
3425                                            .aligned()
3426                                            .top()
3427                                    }))
3428                                    .with_children(self.render_notifications(&theme.workspace, cx)),
3429                            ))
3430                            .flex(1.0, true),
3431                    )
3432                    .with_child(ChildView::new(&self.status_bar, cx))
3433                    .contained()
3434                    .with_background_color(theme.workspace.background),
3435            )
3436            .with_children(DragAndDrop::render(cx))
3437            .with_children(self.render_disconnected_overlay(cx))
3438            .into_any_named("workspace")
3439    }
3440
3441    fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
3442        if cx.is_self_focused() {
3443            cx.focus(&self.active_pane);
3444        }
3445    }
3446}
3447
3448impl ViewId {
3449    pub(crate) fn from_proto(message: proto::ViewId) -> Result<Self> {
3450        Ok(Self {
3451            creator: message
3452                .creator
3453                .ok_or_else(|| anyhow!("creator is missing"))?,
3454            id: message.id,
3455        })
3456    }
3457
3458    pub(crate) fn to_proto(&self) -> proto::ViewId {
3459        proto::ViewId {
3460            creator: Some(self.creator),
3461            id: self.id,
3462        }
3463    }
3464}
3465
3466pub trait WorkspaceHandle {
3467    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath>;
3468}
3469
3470impl WorkspaceHandle for ViewHandle<Workspace> {
3471    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath> {
3472        self.read(cx)
3473            .worktrees(cx)
3474            .flat_map(|worktree| {
3475                let worktree_id = worktree.read(cx).id();
3476                worktree.read(cx).files(true, 0).map(move |f| ProjectPath {
3477                    worktree_id,
3478                    path: f.path.clone(),
3479                })
3480            })
3481            .collect::<Vec<_>>()
3482    }
3483}
3484
3485impl std::fmt::Debug for OpenPaths {
3486    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3487        f.debug_struct("OpenPaths")
3488            .field("paths", &self.paths)
3489            .finish()
3490    }
3491}
3492
3493pub struct WorkspaceCreated(WeakViewHandle<Workspace>);
3494
3495pub fn activate_workspace_for_project(
3496    cx: &mut AsyncAppContext,
3497    predicate: impl Fn(&mut Project, &mut ModelContext<Project>) -> bool,
3498) -> Option<WeakViewHandle<Workspace>> {
3499    for window_id in cx.window_ids() {
3500        let handle = cx
3501            .update_window(window_id, |cx| {
3502                if let Some(workspace_handle) = cx.root_view().clone().downcast::<Workspace>() {
3503                    let project = workspace_handle.read(cx).project.clone();
3504                    if project.update(cx, &predicate) {
3505                        cx.activate_window();
3506                        return Some(workspace_handle.clone());
3507                    }
3508                }
3509                None
3510            })
3511            .flatten();
3512
3513        if let Some(handle) = handle {
3514            return Some(handle.downgrade());
3515        }
3516    }
3517    None
3518}
3519
3520pub async fn last_opened_workspace_paths() -> Option<WorkspaceLocation> {
3521    DB.last_workspace().await.log_err().flatten()
3522}
3523
3524#[allow(clippy::type_complexity)]
3525pub fn open_paths(
3526    abs_paths: &[PathBuf],
3527    app_state: &Arc<AppState>,
3528    requesting_window_id: Option<usize>,
3529    cx: &mut AppContext,
3530) -> Task<
3531    Result<(
3532        WeakViewHandle<Workspace>,
3533        Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
3534    )>,
3535> {
3536    let app_state = app_state.clone();
3537    let abs_paths = abs_paths.to_vec();
3538    cx.spawn(|mut cx| async move {
3539        // Open paths in existing workspace if possible
3540        let existing = activate_workspace_for_project(&mut cx, |project, cx| {
3541            project.contains_paths(&abs_paths, cx)
3542        });
3543
3544        if let Some(existing) = existing {
3545            Ok((
3546                existing.clone(),
3547                existing
3548                    .update(&mut cx, |workspace, cx| {
3549                        workspace.open_paths(abs_paths, true, cx)
3550                    })?
3551                    .await,
3552            ))
3553        } else {
3554            Ok(cx
3555                .update(|cx| {
3556                    Workspace::new_local(abs_paths, app_state.clone(), requesting_window_id, cx)
3557                })
3558                .await)
3559        }
3560    })
3561}
3562
3563pub fn open_new(
3564    app_state: &Arc<AppState>,
3565    cx: &mut AppContext,
3566    init: impl FnOnce(&mut Workspace, &mut ViewContext<Workspace>) + 'static,
3567) -> Task<()> {
3568    let task = Workspace::new_local(Vec::new(), app_state.clone(), None, cx);
3569    cx.spawn(|mut cx| async move {
3570        let (workspace, opened_paths) = task.await;
3571
3572        workspace
3573            .update(&mut cx, |workspace, cx| {
3574                if opened_paths.is_empty() {
3575                    init(workspace, cx)
3576                }
3577            })
3578            .log_err();
3579    })
3580}
3581
3582pub fn create_and_open_local_file(
3583    path: &'static Path,
3584    cx: &mut ViewContext<Workspace>,
3585    default_content: impl 'static + Send + FnOnce() -> Rope,
3586) -> Task<Result<Box<dyn ItemHandle>>> {
3587    cx.spawn(|workspace, mut cx| async move {
3588        let fs = workspace.read_with(&cx, |workspace, _| workspace.app_state().fs.clone())?;
3589        if !fs.is_file(path).await {
3590            fs.create_file(path, Default::default()).await?;
3591            fs.save(path, &default_content(), Default::default())
3592                .await?;
3593        }
3594
3595        let mut items = workspace
3596            .update(&mut cx, |workspace, cx| {
3597                workspace.with_local_workspace(cx, |workspace, cx| {
3598                    workspace.open_paths(vec![path.to_path_buf()], false, cx)
3599                })
3600            })?
3601            .await?
3602            .await;
3603
3604        let item = items.pop().flatten();
3605        item.ok_or_else(|| anyhow!("path {path:?} is not a file"))?
3606    })
3607}
3608
3609pub fn join_remote_project(
3610    project_id: u64,
3611    follow_user_id: u64,
3612    app_state: Arc<AppState>,
3613    cx: &mut AppContext,
3614) -> Task<Result<()>> {
3615    cx.spawn(|mut cx| async move {
3616        let existing_workspace = cx
3617            .window_ids()
3618            .into_iter()
3619            .filter_map(|window_id| cx.root_view(window_id)?.clone().downcast::<Workspace>())
3620            .find(|workspace| {
3621                cx.read_window(workspace.window_id(), |cx| {
3622                    workspace.read(cx).project().read(cx).remote_id() == Some(project_id)
3623                })
3624                .unwrap_or(false)
3625            });
3626
3627        let workspace = if let Some(existing_workspace) = existing_workspace {
3628            existing_workspace.downgrade()
3629        } else {
3630            let active_call = cx.read(ActiveCall::global);
3631            let room = active_call
3632                .read_with(&cx, |call, _| call.room().cloned())
3633                .ok_or_else(|| anyhow!("not in a call"))?;
3634            let project = room
3635                .update(&mut cx, |room, cx| {
3636                    room.join_project(
3637                        project_id,
3638                        app_state.languages.clone(),
3639                        app_state.fs.clone(),
3640                        cx,
3641                    )
3642                })
3643                .await?;
3644
3645            let (_, workspace) = cx.add_window(
3646                (app_state.build_window_options)(None, None, cx.platform().as_ref()),
3647                |cx| Workspace::new(0, project, app_state.clone(), cx),
3648            );
3649            (app_state.initialize_workspace)(
3650                workspace.downgrade(),
3651                false,
3652                app_state.clone(),
3653                cx.clone(),
3654            )
3655            .await
3656            .log_err();
3657
3658            workspace.downgrade()
3659        };
3660
3661        cx.activate_window(workspace.window_id());
3662        cx.platform().activate(true);
3663
3664        workspace.update(&mut cx, |workspace, cx| {
3665            if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
3666                let follow_peer_id = room
3667                    .read(cx)
3668                    .remote_participants()
3669                    .iter()
3670                    .find(|(_, participant)| participant.user.id == follow_user_id)
3671                    .map(|(_, p)| p.peer_id)
3672                    .or_else(|| {
3673                        // If we couldn't follow the given user, follow the host instead.
3674                        let collaborator = workspace
3675                            .project()
3676                            .read(cx)
3677                            .collaborators()
3678                            .values()
3679                            .find(|collaborator| collaborator.replica_id == 0)?;
3680                        Some(collaborator.peer_id)
3681                    });
3682
3683                if let Some(follow_peer_id) = follow_peer_id {
3684                    if !workspace.is_being_followed(follow_peer_id) {
3685                        workspace
3686                            .toggle_follow(follow_peer_id, cx)
3687                            .map(|follow| follow.detach_and_log_err(cx));
3688                    }
3689                }
3690            }
3691        })?;
3692
3693        anyhow::Ok(())
3694    })
3695}
3696
3697pub fn restart(_: &Restart, cx: &mut AppContext) {
3698    let should_confirm = settings::get::<WorkspaceSettings>(cx).confirm_quit;
3699    cx.spawn(|mut cx| async move {
3700        let mut workspaces = cx
3701            .window_ids()
3702            .into_iter()
3703            .filter_map(|window_id| {
3704                Some(
3705                    cx.root_view(window_id)?
3706                        .clone()
3707                        .downcast::<Workspace>()?
3708                        .downgrade(),
3709                )
3710            })
3711            .collect::<Vec<_>>();
3712
3713        // If multiple windows have unsaved changes, and need a save prompt,
3714        // prompt in the active window before switching to a different window.
3715        workspaces.sort_by_key(|workspace| !cx.window_is_active(workspace.window_id()));
3716
3717        if let (true, Some(workspace)) = (should_confirm, workspaces.first()) {
3718            let answer = cx.prompt(
3719                workspace.window_id(),
3720                PromptLevel::Info,
3721                "Are you sure you want to restart?",
3722                &["Restart", "Cancel"],
3723            );
3724
3725            if let Some(mut answer) = answer {
3726                let answer = answer.next().await;
3727                if answer != Some(0) {
3728                    return Ok(());
3729                }
3730            }
3731        }
3732
3733        // If the user cancels any save prompt, then keep the app open.
3734        for workspace in workspaces {
3735            if !workspace
3736                .update(&mut cx, |workspace, cx| {
3737                    workspace.prepare_to_close(true, cx)
3738                })?
3739                .await?
3740            {
3741                return Ok(());
3742            }
3743        }
3744        cx.platform().restart();
3745        anyhow::Ok(())
3746    })
3747    .detach_and_log_err(cx);
3748}
3749
3750fn parse_pixel_position_env_var(value: &str) -> Option<Vector2F> {
3751    let mut parts = value.split(',');
3752    let width: usize = parts.next()?.parse().ok()?;
3753    let height: usize = parts.next()?.parse().ok()?;
3754    Some(vec2f(width as f32, height as f32))
3755}
3756
3757#[cfg(test)]
3758mod tests {
3759    use super::*;
3760    use crate::{
3761        dock::test::{TestPanel, TestPanelEvent},
3762        item::test::{TestItem, TestItemEvent, TestProjectItem},
3763    };
3764    use fs::FakeFs;
3765    use gpui::{executor::Deterministic, test::EmptyView, TestAppContext};
3766    use project::{Project, ProjectEntryId};
3767    use serde_json::json;
3768    use settings::SettingsStore;
3769    use std::{cell::RefCell, rc::Rc};
3770
3771    #[gpui::test]
3772    async fn test_tab_disambiguation(cx: &mut TestAppContext) {
3773        init_test(cx);
3774
3775        let fs = FakeFs::new(cx.background());
3776        let project = Project::test(fs, [], cx).await;
3777        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
3778
3779        // Adding an item with no ambiguity renders the tab without detail.
3780        let item1 = cx.add_view(window_id, |_| {
3781            let mut item = TestItem::new();
3782            item.tab_descriptions = Some(vec!["c", "b1/c", "a/b1/c"]);
3783            item
3784        });
3785        workspace.update(cx, |workspace, cx| {
3786            workspace.add_item(Box::new(item1.clone()), cx);
3787        });
3788        item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), None));
3789
3790        // Adding an item that creates ambiguity increases the level of detail on
3791        // both tabs.
3792        let item2 = cx.add_view(window_id, |_| {
3793            let mut item = TestItem::new();
3794            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
3795            item
3796        });
3797        workspace.update(cx, |workspace, cx| {
3798            workspace.add_item(Box::new(item2.clone()), cx);
3799        });
3800        item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
3801        item2.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
3802
3803        // Adding an item that creates ambiguity increases the level of detail only
3804        // on the ambiguous tabs. In this case, the ambiguity can't be resolved so
3805        // we stop at the highest detail available.
3806        let item3 = cx.add_view(window_id, |_| {
3807            let mut item = TestItem::new();
3808            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
3809            item
3810        });
3811        workspace.update(cx, |workspace, cx| {
3812            workspace.add_item(Box::new(item3.clone()), cx);
3813        });
3814        item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
3815        item2.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
3816        item3.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
3817    }
3818
3819    #[gpui::test]
3820    async fn test_tracking_active_path(cx: &mut TestAppContext) {
3821        init_test(cx);
3822
3823        let fs = FakeFs::new(cx.background());
3824        fs.insert_tree(
3825            "/root1",
3826            json!({
3827                "one.txt": "",
3828                "two.txt": "",
3829            }),
3830        )
3831        .await;
3832        fs.insert_tree(
3833            "/root2",
3834            json!({
3835                "three.txt": "",
3836            }),
3837        )
3838        .await;
3839
3840        let project = Project::test(fs, ["root1".as_ref()], cx).await;
3841        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
3842        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
3843        let worktree_id = project.read_with(cx, |project, cx| {
3844            project.worktrees(cx).next().unwrap().read(cx).id()
3845        });
3846
3847        let item1 = cx.add_view(window_id, |cx| {
3848            TestItem::new().with_project_items(&[TestProjectItem::new(1, "one.txt", cx)])
3849        });
3850        let item2 = cx.add_view(window_id, |cx| {
3851            TestItem::new().with_project_items(&[TestProjectItem::new(2, "two.txt", cx)])
3852        });
3853
3854        // Add an item to an empty pane
3855        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item1), cx));
3856        project.read_with(cx, |project, cx| {
3857            assert_eq!(
3858                project.active_entry(),
3859                project
3860                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
3861                    .map(|e| e.id)
3862            );
3863        });
3864        assert_eq!(
3865            cx.current_window_title(window_id).as_deref(),
3866            Some("one.txt β€” root1")
3867        );
3868
3869        // Add a second item to a non-empty pane
3870        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item2), cx));
3871        assert_eq!(
3872            cx.current_window_title(window_id).as_deref(),
3873            Some("two.txt β€” root1")
3874        );
3875        project.read_with(cx, |project, cx| {
3876            assert_eq!(
3877                project.active_entry(),
3878                project
3879                    .entry_for_path(&(worktree_id, "two.txt").into(), cx)
3880                    .map(|e| e.id)
3881            );
3882        });
3883
3884        // Close the active item
3885        pane.update(cx, |pane, cx| {
3886            pane.close_active_item(&Default::default(), cx).unwrap()
3887        })
3888        .await
3889        .unwrap();
3890        assert_eq!(
3891            cx.current_window_title(window_id).as_deref(),
3892            Some("one.txt β€” root1")
3893        );
3894        project.read_with(cx, |project, cx| {
3895            assert_eq!(
3896                project.active_entry(),
3897                project
3898                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
3899                    .map(|e| e.id)
3900            );
3901        });
3902
3903        // Add a project folder
3904        project
3905            .update(cx, |project, cx| {
3906                project.find_or_create_local_worktree("/root2", true, cx)
3907            })
3908            .await
3909            .unwrap();
3910        assert_eq!(
3911            cx.current_window_title(window_id).as_deref(),
3912            Some("one.txt β€” root1, root2")
3913        );
3914
3915        // Remove a project folder
3916        project.update(cx, |project, cx| project.remove_worktree(worktree_id, cx));
3917        assert_eq!(
3918            cx.current_window_title(window_id).as_deref(),
3919            Some("one.txt β€” root2")
3920        );
3921    }
3922
3923    #[gpui::test]
3924    async fn test_close_window(cx: &mut TestAppContext) {
3925        init_test(cx);
3926
3927        let fs = FakeFs::new(cx.background());
3928        fs.insert_tree("/root", json!({ "one": "" })).await;
3929
3930        let project = Project::test(fs, ["root".as_ref()], cx).await;
3931        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
3932
3933        // When there are no dirty items, there's nothing to do.
3934        let item1 = cx.add_view(window_id, |_| TestItem::new());
3935        workspace.update(cx, |w, cx| w.add_item(Box::new(item1.clone()), cx));
3936        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
3937        assert!(task.await.unwrap());
3938
3939        // When there are dirty untitled items, prompt to save each one. If the user
3940        // cancels any prompt, then abort.
3941        let item2 = cx.add_view(window_id, |_| TestItem::new().with_dirty(true));
3942        let item3 = cx.add_view(window_id, |cx| {
3943            TestItem::new()
3944                .with_dirty(true)
3945                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
3946        });
3947        workspace.update(cx, |w, cx| {
3948            w.add_item(Box::new(item2.clone()), cx);
3949            w.add_item(Box::new(item3.clone()), cx);
3950        });
3951        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
3952        cx.foreground().run_until_parked();
3953        cx.simulate_prompt_answer(window_id, 2 /* cancel */);
3954        cx.foreground().run_until_parked();
3955        assert!(!cx.has_pending_prompt(window_id));
3956        assert!(!task.await.unwrap());
3957    }
3958
3959    #[gpui::test]
3960    async fn test_close_pane_items(cx: &mut TestAppContext) {
3961        init_test(cx);
3962
3963        let fs = FakeFs::new(cx.background());
3964
3965        let project = Project::test(fs, None, cx).await;
3966        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
3967
3968        let item1 = cx.add_view(window_id, |cx| {
3969            TestItem::new()
3970                .with_dirty(true)
3971                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
3972        });
3973        let item2 = cx.add_view(window_id, |cx| {
3974            TestItem::new()
3975                .with_dirty(true)
3976                .with_conflict(true)
3977                .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)])
3978        });
3979        let item3 = cx.add_view(window_id, |cx| {
3980            TestItem::new()
3981                .with_dirty(true)
3982                .with_conflict(true)
3983                .with_project_items(&[TestProjectItem::new(3, "3.txt", cx)])
3984        });
3985        let item4 = cx.add_view(window_id, |cx| {
3986            TestItem::new()
3987                .with_dirty(true)
3988                .with_project_items(&[TestProjectItem::new_untitled(cx)])
3989        });
3990        let pane = workspace.update(cx, |workspace, cx| {
3991            workspace.add_item(Box::new(item1.clone()), cx);
3992            workspace.add_item(Box::new(item2.clone()), cx);
3993            workspace.add_item(Box::new(item3.clone()), cx);
3994            workspace.add_item(Box::new(item4.clone()), cx);
3995            workspace.active_pane().clone()
3996        });
3997
3998        let close_items = pane.update(cx, |pane, cx| {
3999            pane.activate_item(1, true, true, cx);
4000            assert_eq!(pane.active_item().unwrap().id(), item2.id());
4001            let item1_id = item1.id();
4002            let item3_id = item3.id();
4003            let item4_id = item4.id();
4004            pane.close_items(cx, move |id| [item1_id, item3_id, item4_id].contains(&id))
4005        });
4006        cx.foreground().run_until_parked();
4007
4008        // There's a prompt to save item 1.
4009        pane.read_with(cx, |pane, _| {
4010            assert_eq!(pane.items_len(), 4);
4011            assert_eq!(pane.active_item().unwrap().id(), item1.id());
4012        });
4013        assert!(cx.has_pending_prompt(window_id));
4014
4015        // Confirm saving item 1.
4016        cx.simulate_prompt_answer(window_id, 0);
4017        cx.foreground().run_until_parked();
4018
4019        // Item 1 is saved. There's a prompt to save item 3.
4020        pane.read_with(cx, |pane, cx| {
4021            assert_eq!(item1.read(cx).save_count, 1);
4022            assert_eq!(item1.read(cx).save_as_count, 0);
4023            assert_eq!(item1.read(cx).reload_count, 0);
4024            assert_eq!(pane.items_len(), 3);
4025            assert_eq!(pane.active_item().unwrap().id(), item3.id());
4026        });
4027        assert!(cx.has_pending_prompt(window_id));
4028
4029        // Cancel saving item 3.
4030        cx.simulate_prompt_answer(window_id, 1);
4031        cx.foreground().run_until_parked();
4032
4033        // Item 3 is reloaded. There's a prompt to save item 4.
4034        pane.read_with(cx, |pane, cx| {
4035            assert_eq!(item3.read(cx).save_count, 0);
4036            assert_eq!(item3.read(cx).save_as_count, 0);
4037            assert_eq!(item3.read(cx).reload_count, 1);
4038            assert_eq!(pane.items_len(), 2);
4039            assert_eq!(pane.active_item().unwrap().id(), item4.id());
4040        });
4041        assert!(cx.has_pending_prompt(window_id));
4042
4043        // Confirm saving item 4.
4044        cx.simulate_prompt_answer(window_id, 0);
4045        cx.foreground().run_until_parked();
4046
4047        // There's a prompt for a path for item 4.
4048        cx.simulate_new_path_selection(|_| Some(Default::default()));
4049        close_items.await.unwrap();
4050
4051        // The requested items are closed.
4052        pane.read_with(cx, |pane, cx| {
4053            assert_eq!(item4.read(cx).save_count, 0);
4054            assert_eq!(item4.read(cx).save_as_count, 1);
4055            assert_eq!(item4.read(cx).reload_count, 0);
4056            assert_eq!(pane.items_len(), 1);
4057            assert_eq!(pane.active_item().unwrap().id(), item2.id());
4058        });
4059    }
4060
4061    #[gpui::test]
4062    async fn test_prompting_to_save_only_on_last_item_for_entry(cx: &mut TestAppContext) {
4063        init_test(cx);
4064
4065        let fs = FakeFs::new(cx.background());
4066
4067        let project = Project::test(fs, [], cx).await;
4068        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
4069
4070        // Create several workspace items with single project entries, and two
4071        // workspace items with multiple project entries.
4072        let single_entry_items = (0..=4)
4073            .map(|project_entry_id| {
4074                cx.add_view(window_id, |cx| {
4075                    TestItem::new()
4076                        .with_dirty(true)
4077                        .with_project_items(&[TestProjectItem::new(
4078                            project_entry_id,
4079                            &format!("{project_entry_id}.txt"),
4080                            cx,
4081                        )])
4082                })
4083            })
4084            .collect::<Vec<_>>();
4085        let item_2_3 = cx.add_view(window_id, |cx| {
4086            TestItem::new()
4087                .with_dirty(true)
4088                .with_singleton(false)
4089                .with_project_items(&[
4090                    single_entry_items[2].read(cx).project_items[0].clone(),
4091                    single_entry_items[3].read(cx).project_items[0].clone(),
4092                ])
4093        });
4094        let item_3_4 = cx.add_view(window_id, |cx| {
4095            TestItem::new()
4096                .with_dirty(true)
4097                .with_singleton(false)
4098                .with_project_items(&[
4099                    single_entry_items[3].read(cx).project_items[0].clone(),
4100                    single_entry_items[4].read(cx).project_items[0].clone(),
4101                ])
4102        });
4103
4104        // Create two panes that contain the following project entries:
4105        //   left pane:
4106        //     multi-entry items:   (2, 3)
4107        //     single-entry items:  0, 1, 2, 3, 4
4108        //   right pane:
4109        //     single-entry items:  1
4110        //     multi-entry items:   (3, 4)
4111        let left_pane = workspace.update(cx, |workspace, cx| {
4112            let left_pane = workspace.active_pane().clone();
4113            workspace.add_item(Box::new(item_2_3.clone()), cx);
4114            for item in single_entry_items {
4115                workspace.add_item(Box::new(item), cx);
4116            }
4117            left_pane.update(cx, |pane, cx| {
4118                pane.activate_item(2, true, true, cx);
4119            });
4120
4121            workspace
4122                .split_pane(left_pane.clone(), SplitDirection::Right, cx)
4123                .unwrap();
4124
4125            left_pane
4126        });
4127
4128        //Need to cause an effect flush in order to respect new focus
4129        workspace.update(cx, |workspace, cx| {
4130            workspace.add_item(Box::new(item_3_4.clone()), cx);
4131            cx.focus(&left_pane);
4132        });
4133
4134        // When closing all of the items in the left pane, we should be prompted twice:
4135        // once for project entry 0, and once for project entry 2. After those two
4136        // prompts, the task should complete.
4137
4138        let close = left_pane.update(cx, |pane, cx| pane.close_items(cx, |_| true));
4139        cx.foreground().run_until_parked();
4140        left_pane.read_with(cx, |pane, cx| {
4141            assert_eq!(
4142                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
4143                &[ProjectEntryId::from_proto(0)]
4144            );
4145        });
4146        cx.simulate_prompt_answer(window_id, 0);
4147
4148        cx.foreground().run_until_parked();
4149        left_pane.read_with(cx, |pane, cx| {
4150            assert_eq!(
4151                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
4152                &[ProjectEntryId::from_proto(2)]
4153            );
4154        });
4155        cx.simulate_prompt_answer(window_id, 0);
4156
4157        cx.foreground().run_until_parked();
4158        close.await.unwrap();
4159        left_pane.read_with(cx, |pane, _| {
4160            assert_eq!(pane.items_len(), 0);
4161        });
4162    }
4163
4164    #[gpui::test]
4165    async fn test_autosave(deterministic: Arc<Deterministic>, cx: &mut gpui::TestAppContext) {
4166        init_test(cx);
4167
4168        let fs = FakeFs::new(cx.background());
4169
4170        let project = Project::test(fs, [], cx).await;
4171        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
4172        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
4173
4174        let item = cx.add_view(window_id, |cx| {
4175            TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4176        });
4177        let item_id = item.id();
4178        workspace.update(cx, |workspace, cx| {
4179            workspace.add_item(Box::new(item.clone()), cx);
4180        });
4181
4182        // Autosave on window change.
4183        item.update(cx, |item, cx| {
4184            cx.update_global(|settings: &mut SettingsStore, cx| {
4185                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4186                    settings.autosave = Some(AutosaveSetting::OnWindowChange);
4187                })
4188            });
4189            item.is_dirty = true;
4190        });
4191
4192        // Deactivating the window saves the file.
4193        cx.simulate_window_activation(None);
4194        deterministic.run_until_parked();
4195        item.read_with(cx, |item, _| assert_eq!(item.save_count, 1));
4196
4197        // Autosave on focus change.
4198        item.update(cx, |item, cx| {
4199            cx.focus_self();
4200            cx.update_global(|settings: &mut SettingsStore, cx| {
4201                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4202                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
4203                })
4204            });
4205            item.is_dirty = true;
4206        });
4207
4208        // Blurring the item saves the file.
4209        item.update(cx, |_, cx| cx.blur());
4210        deterministic.run_until_parked();
4211        item.read_with(cx, |item, _| assert_eq!(item.save_count, 2));
4212
4213        // Deactivating the window still saves the file.
4214        cx.simulate_window_activation(Some(window_id));
4215        item.update(cx, |item, cx| {
4216            cx.focus_self();
4217            item.is_dirty = true;
4218        });
4219        cx.simulate_window_activation(None);
4220
4221        deterministic.run_until_parked();
4222        item.read_with(cx, |item, _| assert_eq!(item.save_count, 3));
4223
4224        // Autosave after delay.
4225        item.update(cx, |item, cx| {
4226            cx.update_global(|settings: &mut SettingsStore, cx| {
4227                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4228                    settings.autosave = Some(AutosaveSetting::AfterDelay { milliseconds: 500 });
4229                })
4230            });
4231            item.is_dirty = true;
4232            cx.emit(TestItemEvent::Edit);
4233        });
4234
4235        // Delay hasn't fully expired, so the file is still dirty and unsaved.
4236        deterministic.advance_clock(Duration::from_millis(250));
4237        item.read_with(cx, |item, _| assert_eq!(item.save_count, 3));
4238
4239        // After delay expires, the file is saved.
4240        deterministic.advance_clock(Duration::from_millis(250));
4241        item.read_with(cx, |item, _| assert_eq!(item.save_count, 4));
4242
4243        // Autosave on focus change, ensuring closing the tab counts as such.
4244        item.update(cx, |item, cx| {
4245            cx.update_global(|settings: &mut SettingsStore, cx| {
4246                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4247                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
4248                })
4249            });
4250            item.is_dirty = true;
4251        });
4252
4253        pane.update(cx, |pane, cx| pane.close_items(cx, move |id| id == item_id))
4254            .await
4255            .unwrap();
4256        assert!(!cx.has_pending_prompt(window_id));
4257        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
4258
4259        // Add the item again, ensuring autosave is prevented if the underlying file has been deleted.
4260        workspace.update(cx, |workspace, cx| {
4261            workspace.add_item(Box::new(item.clone()), cx);
4262        });
4263        item.update(cx, |item, cx| {
4264            item.project_items[0].update(cx, |item, _| {
4265                item.entry_id = None;
4266            });
4267            item.is_dirty = true;
4268            cx.blur();
4269        });
4270        deterministic.run_until_parked();
4271        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
4272
4273        // Ensure autosave is prevented for deleted files also when closing the buffer.
4274        let _close_items =
4275            pane.update(cx, |pane, cx| pane.close_items(cx, move |id| id == item_id));
4276        deterministic.run_until_parked();
4277        assert!(cx.has_pending_prompt(window_id));
4278        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
4279    }
4280
4281    #[gpui::test]
4282    async fn test_pane_navigation(cx: &mut gpui::TestAppContext) {
4283        init_test(cx);
4284
4285        let fs = FakeFs::new(cx.background());
4286
4287        let project = Project::test(fs, [], cx).await;
4288        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
4289
4290        let item = cx.add_view(window_id, |cx| {
4291            TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4292        });
4293        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
4294        let toolbar = pane.read_with(cx, |pane, _| pane.toolbar().clone());
4295        let toolbar_notify_count = Rc::new(RefCell::new(0));
4296
4297        workspace.update(cx, |workspace, cx| {
4298            workspace.add_item(Box::new(item.clone()), cx);
4299            let toolbar_notification_count = toolbar_notify_count.clone();
4300            cx.observe(&toolbar, move |_, _, _| {
4301                *toolbar_notification_count.borrow_mut() += 1
4302            })
4303            .detach();
4304        });
4305
4306        pane.read_with(cx, |pane, _| {
4307            assert!(!pane.can_navigate_backward());
4308            assert!(!pane.can_navigate_forward());
4309        });
4310
4311        item.update(cx, |item, cx| {
4312            item.set_state("one".to_string(), cx);
4313        });
4314
4315        // Toolbar must be notified to re-render the navigation buttons
4316        assert_eq!(*toolbar_notify_count.borrow(), 1);
4317
4318        pane.read_with(cx, |pane, _| {
4319            assert!(pane.can_navigate_backward());
4320            assert!(!pane.can_navigate_forward());
4321        });
4322
4323        workspace
4324            .update(cx, |workspace, cx| workspace.go_back(pane.downgrade(), cx))
4325            .await
4326            .unwrap();
4327
4328        assert_eq!(*toolbar_notify_count.borrow(), 3);
4329        pane.read_with(cx, |pane, _| {
4330            assert!(!pane.can_navigate_backward());
4331            assert!(pane.can_navigate_forward());
4332        });
4333    }
4334
4335    #[gpui::test]
4336    async fn test_toggle_docks_and_panels(cx: &mut gpui::TestAppContext) {
4337        init_test(cx);
4338        let fs = FakeFs::new(cx.background());
4339
4340        let project = Project::test(fs, [], cx).await;
4341        let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
4342
4343        let panel = workspace.update(cx, |workspace, cx| {
4344            let panel = cx.add_view(|_| TestPanel::new(DockPosition::Right));
4345            workspace.add_panel(panel.clone(), cx);
4346
4347            workspace
4348                .right_dock()
4349                .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
4350
4351            panel
4352        });
4353
4354        // Transfer focus from center to panel
4355        workspace.update(cx, |workspace, cx| {
4356            workspace.toggle_panel_focus::<TestPanel>(cx);
4357        });
4358
4359        workspace.read_with(cx, |workspace, cx| {
4360            assert!(workspace.right_dock().read(cx).is_open());
4361            assert!(!panel.is_zoomed(cx));
4362            assert!(panel.has_focus(cx));
4363        });
4364
4365        // Transfer focus from panel to center
4366        workspace.update(cx, |workspace, cx| {
4367            workspace.toggle_panel_focus::<TestPanel>(cx);
4368        });
4369
4370        workspace.read_with(cx, |workspace, cx| {
4371            assert!(workspace.right_dock().read(cx).is_open());
4372            assert!(!panel.is_zoomed(cx));
4373            assert!(!panel.has_focus(cx));
4374        });
4375
4376        // Close the dock
4377        workspace.update(cx, |workspace, cx| {
4378            workspace.toggle_dock(DockPosition::Right, cx);
4379        });
4380
4381        workspace.read_with(cx, |workspace, cx| {
4382            assert!(!workspace.right_dock().read(cx).is_open());
4383            assert!(!panel.is_zoomed(cx));
4384            assert!(!panel.has_focus(cx));
4385        });
4386
4387        // Open the dock
4388        workspace.update(cx, |workspace, cx| {
4389            workspace.toggle_dock(DockPosition::Right, cx);
4390        });
4391
4392        workspace.read_with(cx, |workspace, cx| {
4393            assert!(workspace.right_dock().read(cx).is_open());
4394            assert!(!panel.is_zoomed(cx));
4395            assert!(!panel.has_focus(cx));
4396        });
4397
4398        // Focus and zoom panel
4399        panel.update(cx, |panel, cx| {
4400            cx.focus_self();
4401            panel.set_zoomed(true, cx)
4402        });
4403
4404        workspace.read_with(cx, |workspace, cx| {
4405            assert!(workspace.right_dock().read(cx).is_open());
4406            assert!(panel.is_zoomed(cx));
4407            assert!(panel.has_focus(cx));
4408        });
4409
4410        // Transfer focus to the center closes the dock
4411        workspace.update(cx, |workspace, cx| {
4412            workspace.toggle_panel_focus::<TestPanel>(cx);
4413        });
4414
4415        workspace.read_with(cx, |workspace, cx| {
4416            assert!(!workspace.right_dock().read(cx).is_open());
4417            assert!(panel.is_zoomed(cx));
4418            assert!(!panel.has_focus(cx));
4419        });
4420
4421        // Transfering focus back to the panel keeps it zoomed
4422        workspace.update(cx, |workspace, cx| {
4423            workspace.toggle_panel_focus::<TestPanel>(cx);
4424        });
4425
4426        workspace.read_with(cx, |workspace, cx| {
4427            assert!(workspace.right_dock().read(cx).is_open());
4428            assert!(panel.is_zoomed(cx));
4429            assert!(panel.has_focus(cx));
4430        });
4431
4432        // Close the dock while it is zoomed
4433        workspace.update(cx, |workspace, cx| {
4434            workspace.toggle_dock(DockPosition::Right, cx)
4435        });
4436
4437        workspace.read_with(cx, |workspace, cx| {
4438            assert!(!workspace.right_dock().read(cx).is_open());
4439            assert!(panel.is_zoomed(cx));
4440            assert!(workspace.zoomed.is_none());
4441            assert!(!panel.has_focus(cx));
4442        });
4443
4444        // Opening the dock, when it's zoomed, retains focus
4445        workspace.update(cx, |workspace, cx| {
4446            workspace.toggle_dock(DockPosition::Right, cx)
4447        });
4448
4449        workspace.read_with(cx, |workspace, cx| {
4450            assert!(workspace.right_dock().read(cx).is_open());
4451            assert!(panel.is_zoomed(cx));
4452            assert!(workspace.zoomed.is_some());
4453            assert!(panel.has_focus(cx));
4454        });
4455    }
4456
4457    #[gpui::test]
4458    async fn test_panels(cx: &mut gpui::TestAppContext) {
4459        init_test(cx);
4460        let fs = FakeFs::new(cx.background());
4461
4462        let project = Project::test(fs, [], cx).await;
4463        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
4464
4465        let (panel_1, panel_2) = workspace.update(cx, |workspace, cx| {
4466            // Add panel_1 on the left, panel_2 on the right.
4467            let panel_1 = cx.add_view(|_| TestPanel::new(DockPosition::Left));
4468            workspace.add_panel(panel_1.clone(), cx);
4469            workspace
4470                .left_dock()
4471                .update(cx, |left_dock, cx| left_dock.set_open(true, cx));
4472            let panel_2 = cx.add_view(|_| TestPanel::new(DockPosition::Right));
4473            workspace.add_panel(panel_2.clone(), cx);
4474            workspace
4475                .right_dock()
4476                .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
4477
4478            let left_dock = workspace.left_dock();
4479            assert_eq!(
4480                left_dock.read(cx).visible_panel().unwrap().id(),
4481                panel_1.id()
4482            );
4483            assert_eq!(
4484                left_dock.read(cx).active_panel_size(cx).unwrap(),
4485                panel_1.size(cx)
4486            );
4487
4488            left_dock.update(cx, |left_dock, cx| left_dock.resize_active_panel(1337., cx));
4489            assert_eq!(
4490                workspace
4491                    .right_dock()
4492                    .read(cx)
4493                    .visible_panel()
4494                    .unwrap()
4495                    .id(),
4496                panel_2.id()
4497            );
4498
4499            (panel_1, panel_2)
4500        });
4501
4502        // Move panel_1 to the right
4503        panel_1.update(cx, |panel_1, cx| {
4504            panel_1.set_position(DockPosition::Right, cx)
4505        });
4506
4507        workspace.update(cx, |workspace, cx| {
4508            // Since panel_1 was visible on the left, it should now be visible now that it's been moved to the right.
4509            // Since it was the only panel on the left, the left dock should now be closed.
4510            assert!(!workspace.left_dock().read(cx).is_open());
4511            assert!(workspace.left_dock().read(cx).visible_panel().is_none());
4512            let right_dock = workspace.right_dock();
4513            assert_eq!(
4514                right_dock.read(cx).visible_panel().unwrap().id(),
4515                panel_1.id()
4516            );
4517            assert_eq!(right_dock.read(cx).active_panel_size(cx).unwrap(), 1337.);
4518
4519            // Now we move panel_2Β to the left
4520            panel_2.set_position(DockPosition::Left, cx);
4521        });
4522
4523        workspace.update(cx, |workspace, cx| {
4524            // Since panel_2 was not visible on the right, we don't open the left dock.
4525            assert!(!workspace.left_dock().read(cx).is_open());
4526            // And the right dock is unaffected in it's displaying of panel_1
4527            assert!(workspace.right_dock().read(cx).is_open());
4528            assert_eq!(
4529                workspace
4530                    .right_dock()
4531                    .read(cx)
4532                    .visible_panel()
4533                    .unwrap()
4534                    .id(),
4535                panel_1.id()
4536            );
4537        });
4538
4539        // Move panel_1 back to the left
4540        panel_1.update(cx, |panel_1, cx| {
4541            panel_1.set_position(DockPosition::Left, cx)
4542        });
4543
4544        workspace.update(cx, |workspace, cx| {
4545            // Since panel_1 was visible on the right, we open the left dock and make panel_1 active.
4546            let left_dock = workspace.left_dock();
4547            assert!(left_dock.read(cx).is_open());
4548            assert_eq!(
4549                left_dock.read(cx).visible_panel().unwrap().id(),
4550                panel_1.id()
4551            );
4552            assert_eq!(left_dock.read(cx).active_panel_size(cx).unwrap(), 1337.);
4553            // And right the dock should be closed as it no longer has any panels.
4554            assert!(!workspace.right_dock().read(cx).is_open());
4555
4556            // Now we move panel_1 to the bottom
4557            panel_1.set_position(DockPosition::Bottom, cx);
4558        });
4559
4560        workspace.update(cx, |workspace, cx| {
4561            // Since panel_1 was visible on the left, we close the left dock.
4562            assert!(!workspace.left_dock().read(cx).is_open());
4563            // The bottom dock is sized based on the panel's default size,
4564            // since the panel orientation changed from vertical to horizontal.
4565            let bottom_dock = workspace.bottom_dock();
4566            assert_eq!(
4567                bottom_dock.read(cx).active_panel_size(cx).unwrap(),
4568                panel_1.size(cx),
4569            );
4570            // Close bottom dock and move panel_1 back to the left.
4571            bottom_dock.update(cx, |bottom_dock, cx| bottom_dock.set_open(false, cx));
4572            panel_1.set_position(DockPosition::Left, cx);
4573        });
4574
4575        // Emit activated event on panel 1
4576        panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::Activated));
4577
4578        // Now the left dock is open and panel_1 is active and focused.
4579        workspace.read_with(cx, |workspace, cx| {
4580            let left_dock = workspace.left_dock();
4581            assert!(left_dock.read(cx).is_open());
4582            assert_eq!(
4583                left_dock.read(cx).visible_panel().unwrap().id(),
4584                panel_1.id()
4585            );
4586            assert!(panel_1.is_focused(cx));
4587        });
4588
4589        // Emit closed event on panel 2, which is not active
4590        panel_2.update(cx, |_, cx| cx.emit(TestPanelEvent::Closed));
4591
4592        // Wo don't close the left dock, because panel_2 wasn't the active panel
4593        workspace.read_with(cx, |workspace, cx| {
4594            let left_dock = workspace.left_dock();
4595            assert!(left_dock.read(cx).is_open());
4596            assert_eq!(
4597                left_dock.read(cx).visible_panel().unwrap().id(),
4598                panel_1.id()
4599            );
4600        });
4601
4602        // Emitting a ZoomIn event shows the panel as zoomed.
4603        panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::ZoomIn));
4604        workspace.read_with(cx, |workspace, _| {
4605            assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
4606            assert_eq!(workspace.zoomed_position, Some(DockPosition::Left));
4607        });
4608
4609        // Move panel to another dock while it is zoomed
4610        panel_1.update(cx, |panel, cx| panel.set_position(DockPosition::Right, cx));
4611        workspace.read_with(cx, |workspace, _| {
4612            assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
4613            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
4614        });
4615
4616        // If focus is transferred to another view that's not a panel or another pane, we still show
4617        // the panel as zoomed.
4618        let focus_receiver = cx.add_view(window_id, |_| EmptyView);
4619        focus_receiver.update(cx, |_, cx| cx.focus_self());
4620        workspace.read_with(cx, |workspace, _| {
4621            assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
4622            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
4623        });
4624
4625        // If focus is transferred elsewhere in the workspace, the panel is no longer zoomed.
4626        workspace.update(cx, |_, cx| cx.focus_self());
4627        workspace.read_with(cx, |workspace, _| {
4628            assert_eq!(workspace.zoomed, None);
4629            assert_eq!(workspace.zoomed_position, None);
4630        });
4631
4632        // If focus is transferred again to another view that's not a panel or a pane, we won't
4633        // show the panel as zoomed because it wasn't zoomed before.
4634        focus_receiver.update(cx, |_, cx| cx.focus_self());
4635        workspace.read_with(cx, |workspace, _| {
4636            assert_eq!(workspace.zoomed, None);
4637            assert_eq!(workspace.zoomed_position, None);
4638        });
4639
4640        // When focus is transferred back to the panel, it is zoomed again.
4641        panel_1.update(cx, |_, cx| cx.focus_self());
4642        workspace.read_with(cx, |workspace, _| {
4643            assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
4644            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
4645        });
4646
4647        // Emitting a ZoomOut event unzooms the panel.
4648        panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::ZoomOut));
4649        workspace.read_with(cx, |workspace, _| {
4650            assert_eq!(workspace.zoomed, None);
4651            assert_eq!(workspace.zoomed_position, None);
4652        });
4653
4654        // Emit closed event on panel 1, which is active
4655        panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::Closed));
4656
4657        // Now the left dock is closed, because panel_1 was the active panel
4658        workspace.read_with(cx, |workspace, cx| {
4659            let right_dock = workspace.right_dock();
4660            assert!(!right_dock.read(cx).is_open());
4661        });
4662    }
4663
4664    pub fn init_test(cx: &mut TestAppContext) {
4665        cx.foreground().forbid_parking();
4666        cx.update(|cx| {
4667            cx.set_global(SettingsStore::test(cx));
4668            theme::init((), cx);
4669            language::init(cx);
4670            crate::init_settings(cx);
4671        });
4672    }
4673}