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