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