workspace.rs

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