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