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