workspace2.rs

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