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