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, prelude::*, rems, size, Action, AnyModel, AnyView, AnyWeakView,
  40    AppContext, AsyncAppContext, AsyncWindowContext, Bounds, Component, Div, Entity, EntityId,
  41    EventEmitter, FocusHandle, GlobalPixels, KeyContext, Model, ModelContext, ParentComponent,
  42    Point, Render, Size, Styled, Subscription, Task, View, ViewContext, WeakView, WindowBounds,
  43    WindowContext, WindowHandle, WindowOptions,
  44};
  45use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ItemSettings, ProjectItem};
  46use itertools::Itertools;
  47use language2::{LanguageRegistry, Rope};
  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, Button, ButtonVariant, KeyBinding, Label, TextColor, TextTooltip};
  72use util::ResultExt;
  73use uuid::Uuid;
  74pub use 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 node_runtime::FakeNodeRuntime;
 350        use settings2::SettingsStore;
 351
 352        if !cx.has_global::<SettingsStore>() {
 353            let settings_store = SettingsStore::test(cx);
 354            cx.set_global(settings_store);
 355        }
 356
 357        let fs = fs2::FakeFs::new(cx.background_executor().clone());
 358        let languages = Arc::new(LanguageRegistry::test());
 359        let http_client = util::http::FakeHttpClient::with_404_response();
 360        let client = Client::new(http_client.clone(), cx);
 361        let user_store = cx.build_model(|cx| UserStore::new(client.clone(), http_client, cx));
 362        let workspace_store = cx.build_model(|cx| WorkspaceStore::new(client.clone(), cx));
 363
 364        theme2::init(cx);
 365        client2::init(&client, cx);
 366        crate::init_settings(cx);
 367
 368        Arc::new(Self {
 369            client,
 370            fs,
 371            languages,
 372            user_store,
 373            workspace_store,
 374            node_runtime: FakeNodeRuntime::new(),
 375            initialize_workspace: |_, _, _, _| Task::ready(Ok(())),
 376            build_window_options: |_, _, _| Default::default(),
 377        })
 378    }
 379}
 380
 381struct DelayedDebouncedEditAction {
 382    task: Option<Task<()>>,
 383    cancel_channel: Option<oneshot::Sender<()>>,
 384}
 385
 386impl DelayedDebouncedEditAction {
 387    fn new() -> DelayedDebouncedEditAction {
 388        DelayedDebouncedEditAction {
 389            task: None,
 390            cancel_channel: None,
 391        }
 392    }
 393
 394    fn fire_new<F>(&mut self, delay: Duration, cx: &mut ViewContext<Workspace>, func: F)
 395    where
 396        F: 'static + Send + FnOnce(&mut Workspace, &mut ViewContext<Workspace>) -> Task<Result<()>>,
 397    {
 398        if let Some(channel) = self.cancel_channel.take() {
 399            _ = channel.send(());
 400        }
 401
 402        let (sender, mut receiver) = oneshot::channel::<()>();
 403        self.cancel_channel = Some(sender);
 404
 405        let previous_task = self.task.take();
 406        self.task = Some(cx.spawn(move |workspace, mut cx| async move {
 407            let mut timer = cx.background_executor().timer(delay).fuse();
 408            if let Some(previous_task) = previous_task {
 409                previous_task.await;
 410            }
 411
 412            futures::select_biased! {
 413                _ = receiver => return,
 414                    _ = timer => {}
 415            }
 416
 417            if let Some(result) = workspace
 418                .update(&mut cx, |workspace, cx| (func)(workspace, cx))
 419                .log_err()
 420            {
 421                result.await.log_err();
 422            }
 423        }));
 424    }
 425}
 426
 427pub enum Event {
 428    PaneAdded(View<Pane>),
 429    ContactRequestedJoin(u64),
 430    WorkspaceCreated(WeakView<Workspace>),
 431}
 432
 433pub struct Workspace {
 434    weak_self: WeakView<Self>,
 435    focus_handle: FocusHandle,
 436    workspace_actions: Vec<Box<dyn Fn(Div<Workspace>) -> Div<Workspace>>>,
 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: View<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 = cx.build_view(|cx| 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            workspace_actions: Default::default(),
 683        }
 684    }
 685
 686    fn new_local(
 687        abs_paths: Vec<PathBuf>,
 688        app_state: Arc<AppState>,
 689        _requesting_window: Option<WindowHandle<Workspace>>,
 690        cx: &mut AppContext,
 691    ) -> Task<
 692        anyhow::Result<(
 693            WindowHandle<Workspace>,
 694            Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
 695        )>,
 696    > {
 697        let project_handle = Project::local(
 698            app_state.client.clone(),
 699            app_state.node_runtime.clone(),
 700            app_state.user_store.clone(),
 701            app_state.languages.clone(),
 702            app_state.fs.clone(),
 703            cx,
 704        );
 705
 706        cx.spawn(|mut cx| async move {
 707            let serialized_workspace: Option<SerializedWorkspace> = None; //persistence::DB.workspace_for_roots(&abs_paths.as_slice());
 708
 709            let paths_to_open = Arc::new(abs_paths);
 710
 711            // Get project paths for all of the abs_paths
 712            let mut worktree_roots: HashSet<Arc<Path>> = Default::default();
 713            let mut project_paths: Vec<(PathBuf, Option<ProjectPath>)> =
 714                Vec::with_capacity(paths_to_open.len());
 715            for path in paths_to_open.iter().cloned() {
 716                if let Some((worktree, project_entry)) = cx
 717                    .update(|cx| {
 718                        Workspace::project_path_for_path(project_handle.clone(), &path, true, cx)
 719                    })?
 720                    .await
 721                    .log_err()
 722                {
 723                    worktree_roots.extend(worktree.update(&mut cx, |tree, _| tree.abs_path()).ok());
 724                    project_paths.push((path, Some(project_entry)));
 725                } else {
 726                    project_paths.push((path, None));
 727                }
 728            }
 729
 730            let workspace_id = if let Some(serialized_workspace) = serialized_workspace.as_ref() {
 731                serialized_workspace.id
 732            } else {
 733                DB.next_id().await.unwrap_or(0)
 734            };
 735
 736            // todo!()
 737            let window = /*if let Some(window) = requesting_window {
 738                cx.update_window(window.into(), |old_workspace, cx| {
 739                    cx.replace_root_view(|cx| {
 740                        Workspace::new(workspace_id, project_handle.clone(), app_state.clone(), cx)
 741                    });
 742                });
 743                window
 744                } else */ {
 745                let window_bounds_override = window_bounds_env_override(&cx);
 746                let (bounds, display) = if let Some(bounds) = window_bounds_override {
 747                    (Some(bounds), None)
 748                } else {
 749                    serialized_workspace
 750                        .as_ref()
 751                        .and_then(|serialized_workspace| {
 752                            let serialized_display = serialized_workspace.display?;
 753                            let mut bounds = serialized_workspace.bounds?;
 754
 755                            // Stored bounds are relative to the containing display.
 756                            // So convert back to global coordinates if that screen still exists
 757                            if let WindowBounds::Fixed(mut window_bounds) = bounds {
 758                                let screen =
 759                                    cx.update(|cx|
 760                                        cx.displays()
 761                                            .into_iter()
 762                                            .find(|display| display.uuid().ok() == Some(serialized_display))
 763                                    ).ok()??;
 764                                let screen_bounds = screen.bounds();
 765                                window_bounds.origin.x += screen_bounds.origin.x;
 766                                window_bounds.origin.y += screen_bounds.origin.y;
 767                                bounds = WindowBounds::Fixed(window_bounds);
 768                            }
 769
 770                            Some((bounds, serialized_display))
 771                        })
 772                        .unzip()
 773                };
 774
 775                // Use the serialized workspace to construct the new window
 776                let options =
 777                    cx.update(|cx| (app_state.build_window_options)(bounds, display, cx))?;
 778
 779                cx.open_window(options, {
 780                    let app_state = app_state.clone();
 781                    let workspace_id = workspace_id.clone();
 782                    let project_handle = project_handle.clone();
 783                    move |cx| {
 784                        cx.build_view(|cx| {
 785                            Workspace::new(workspace_id, project_handle, app_state, cx)
 786                        })
 787                    }
 788                })?
 789            };
 790
 791            // todo!() Ask how to do this
 792            let weak_view = window.update(&mut cx, |_, cx| cx.view().downgrade())?;
 793            let async_cx = window.update(&mut cx, |_, cx| cx.to_async())?;
 794
 795            (app_state.initialize_workspace)(
 796                weak_view,
 797                serialized_workspace.is_some(),
 798                app_state.clone(),
 799                async_cx,
 800            )
 801            .await
 802            .log_err();
 803
 804            window
 805                .update(&mut cx, |_, cx| cx.activate_window())
 806                .log_err();
 807
 808            notify_if_database_failed(window, &mut cx);
 809            let opened_items = window
 810                .update(&mut cx, |_workspace, cx| {
 811                    open_items(
 812                        serialized_workspace,
 813                        project_paths,
 814                        app_state,
 815                        cx,
 816                    )
 817                })?
 818                .await
 819                .unwrap_or_default();
 820
 821            Ok((window, opened_items))
 822        })
 823    }
 824
 825    pub fn weak_handle(&self) -> WeakView<Self> {
 826        self.weak_self.clone()
 827    }
 828
 829    pub fn left_dock(&self) -> &View<Dock> {
 830        &self.left_dock
 831    }
 832
 833    pub fn bottom_dock(&self) -> &View<Dock> {
 834        &self.bottom_dock
 835    }
 836
 837    pub fn right_dock(&self) -> &View<Dock> {
 838        &self.right_dock
 839    }
 840
 841    pub fn add_panel<T: Panel>(&mut self, panel: View<T>, cx: &mut ViewContext<Self>) {
 842        let dock = match panel.position(cx) {
 843            DockPosition::Left => &self.left_dock,
 844            DockPosition::Bottom => &self.bottom_dock,
 845            DockPosition::Right => &self.right_dock,
 846        };
 847
 848        dock.update(cx, |dock, cx| dock.add_panel(panel, cx));
 849    }
 850
 851    pub fn status_bar(&self) -> &View<StatusBar> {
 852        &self.status_bar
 853    }
 854
 855    pub fn app_state(&self) -> &Arc<AppState> {
 856        &self.app_state
 857    }
 858
 859    pub fn user_store(&self) -> &Model<UserStore> {
 860        &self.app_state.user_store
 861    }
 862
 863    pub fn project(&self) -> &Model<Project> {
 864        &self.project
 865    }
 866
 867    pub fn recent_navigation_history(
 868        &self,
 869        limit: Option<usize>,
 870        cx: &AppContext,
 871    ) -> Vec<(ProjectPath, Option<PathBuf>)> {
 872        let mut abs_paths_opened: HashMap<PathBuf, HashSet<ProjectPath>> = HashMap::default();
 873        let mut history: HashMap<ProjectPath, (Option<PathBuf>, usize)> = HashMap::default();
 874        for pane in &self.panes {
 875            let pane = pane.read(cx);
 876            pane.nav_history()
 877                .for_each_entry(cx, |entry, (project_path, fs_path)| {
 878                    if let Some(fs_path) = &fs_path {
 879                        abs_paths_opened
 880                            .entry(fs_path.clone())
 881                            .or_default()
 882                            .insert(project_path.clone());
 883                    }
 884                    let timestamp = entry.timestamp;
 885                    match history.entry(project_path) {
 886                        hash_map::Entry::Occupied(mut entry) => {
 887                            let (_, old_timestamp) = entry.get();
 888                            if &timestamp > old_timestamp {
 889                                entry.insert((fs_path, timestamp));
 890                            }
 891                        }
 892                        hash_map::Entry::Vacant(entry) => {
 893                            entry.insert((fs_path, timestamp));
 894                        }
 895                    }
 896                });
 897        }
 898
 899        history
 900            .into_iter()
 901            .sorted_by_key(|(_, (_, timestamp))| *timestamp)
 902            .map(|(project_path, (fs_path, _))| (project_path, fs_path))
 903            .rev()
 904            .filter(|(history_path, abs_path)| {
 905                let latest_project_path_opened = abs_path
 906                    .as_ref()
 907                    .and_then(|abs_path| abs_paths_opened.get(abs_path))
 908                    .and_then(|project_paths| {
 909                        project_paths
 910                            .iter()
 911                            .max_by(|b1, b2| b1.worktree_id.cmp(&b2.worktree_id))
 912                    });
 913
 914                match latest_project_path_opened {
 915                    Some(latest_project_path_opened) => latest_project_path_opened == history_path,
 916                    None => true,
 917                }
 918            })
 919            .take(limit.unwrap_or(usize::MAX))
 920            .collect()
 921    }
 922
 923    fn navigate_history(
 924        &mut self,
 925        pane: WeakView<Pane>,
 926        mode: NavigationMode,
 927        cx: &mut ViewContext<Workspace>,
 928    ) -> Task<Result<()>> {
 929        let to_load = if let Some(pane) = pane.upgrade() {
 930            // todo!("focus")
 931            // cx.focus(&pane);
 932
 933            pane.update(cx, |pane, cx| {
 934                loop {
 935                    // Retrieve the weak item handle from the history.
 936                    let entry = pane.nav_history_mut().pop(mode, cx)?;
 937
 938                    // If the item is still present in this pane, then activate it.
 939                    if let Some(index) = entry
 940                        .item
 941                        .upgrade()
 942                        .and_then(|v| pane.index_for_item(v.as_ref()))
 943                    {
 944                        let prev_active_item_index = pane.active_item_index();
 945                        pane.nav_history_mut().set_mode(mode);
 946                        pane.activate_item(index, true, true, cx);
 947                        pane.nav_history_mut().set_mode(NavigationMode::Normal);
 948
 949                        let mut navigated = prev_active_item_index != pane.active_item_index();
 950                        if let Some(data) = entry.data {
 951                            navigated |= pane.active_item()?.navigate(data, cx);
 952                        }
 953
 954                        if navigated {
 955                            break None;
 956                        }
 957                    }
 958                    // If the item is no longer present in this pane, then retrieve its
 959                    // project path in order to reopen it.
 960                    else {
 961                        break pane
 962                            .nav_history()
 963                            .path_for_item(entry.item.id())
 964                            .map(|(project_path, _)| (project_path, entry));
 965                    }
 966                }
 967            })
 968        } else {
 969            None
 970        };
 971
 972        if let Some((project_path, entry)) = to_load {
 973            // If the item was no longer present, then load it again from its previous path.
 974            let task = self.load_path(project_path, cx);
 975            cx.spawn(|workspace, mut cx| async move {
 976                let task = task.await;
 977                let mut navigated = false;
 978                if let Some((project_entry_id, build_item)) = task.log_err() {
 979                    let prev_active_item_id = pane.update(&mut cx, |pane, _| {
 980                        pane.nav_history_mut().set_mode(mode);
 981                        pane.active_item().map(|p| p.id())
 982                    })?;
 983
 984                    pane.update(&mut cx, |pane, cx| {
 985                        let item = pane.open_item(project_entry_id, true, cx, build_item);
 986                        navigated |= Some(item.id()) != prev_active_item_id;
 987                        pane.nav_history_mut().set_mode(NavigationMode::Normal);
 988                        if let Some(data) = entry.data {
 989                            navigated |= item.navigate(data, cx);
 990                        }
 991                    })?;
 992                }
 993
 994                if !navigated {
 995                    workspace
 996                        .update(&mut cx, |workspace, cx| {
 997                            Self::navigate_history(workspace, pane, mode, cx)
 998                        })?
 999                        .await?;
1000                }
1001
1002                Ok(())
1003            })
1004        } else {
1005            Task::ready(Ok(()))
1006        }
1007    }
1008
1009    pub fn go_back(
1010        &mut self,
1011        pane: WeakView<Pane>,
1012        cx: &mut ViewContext<Workspace>,
1013    ) -> Task<Result<()>> {
1014        self.navigate_history(pane, NavigationMode::GoingBack, cx)
1015    }
1016
1017    pub fn go_forward(
1018        &mut self,
1019        pane: WeakView<Pane>,
1020        cx: &mut ViewContext<Workspace>,
1021    ) -> Task<Result<()>> {
1022        self.navigate_history(pane, NavigationMode::GoingForward, cx)
1023    }
1024
1025    pub fn reopen_closed_item(&mut self, cx: &mut ViewContext<Workspace>) -> Task<Result<()>> {
1026        self.navigate_history(
1027            self.active_pane().downgrade(),
1028            NavigationMode::ReopeningClosedItem,
1029            cx,
1030        )
1031    }
1032
1033    pub fn client(&self) -> &Client {
1034        &self.app_state.client
1035    }
1036
1037    // todo!()
1038    // pub fn set_titlebar_item(&mut self, item: AnyViewHandle, cx: &mut ViewContext<Self>) {
1039    //     self.titlebar_item = Some(item);
1040    //     cx.notify();
1041    // }
1042
1043    // pub fn titlebar_item(&self) -> Option<AnyViewHandle> {
1044    //     self.titlebar_item.clone()
1045    // }
1046
1047    /// Call the given callback with a workspace whose project is local.
1048    ///
1049    /// If the given workspace has a local project, then it will be passed
1050    /// to the callback. Otherwise, a new empty window will be created.
1051    pub fn with_local_workspace<T, F>(
1052        &mut self,
1053        cx: &mut ViewContext<Self>,
1054        callback: F,
1055    ) -> Task<Result<T>>
1056    where
1057        T: 'static,
1058        F: 'static + FnOnce(&mut Workspace, &mut ViewContext<Workspace>) -> T,
1059    {
1060        if self.project.read(cx).is_local() {
1061            Task::Ready(Some(Ok(callback(self, cx))))
1062        } else {
1063            let task = Self::new_local(Vec::new(), self.app_state.clone(), None, cx);
1064            cx.spawn(|_vh, mut cx| async move {
1065                let (workspace, _) = task.await?;
1066                workspace.update(&mut cx, callback)
1067            })
1068        }
1069    }
1070
1071    pub fn worktrees<'a>(&self, cx: &'a AppContext) -> impl 'a + Iterator<Item = Model<Worktree>> {
1072        self.project.read(cx).worktrees()
1073    }
1074
1075    pub fn visible_worktrees<'a>(
1076        &self,
1077        cx: &'a AppContext,
1078    ) -> impl 'a + Iterator<Item = Model<Worktree>> {
1079        self.project.read(cx).visible_worktrees(cx)
1080    }
1081
1082    pub fn worktree_scans_complete(&self, cx: &AppContext) -> impl Future<Output = ()> + 'static {
1083        let futures = self
1084            .worktrees(cx)
1085            .filter_map(|worktree| worktree.read(cx).as_local())
1086            .map(|worktree| worktree.scan_complete())
1087            .collect::<Vec<_>>();
1088        async move {
1089            for future in futures {
1090                future.await;
1091            }
1092        }
1093    }
1094
1095    //     pub fn close_global(_: &CloseWindow, cx: &mut AppContext) {
1096    //         cx.spawn(|mut cx| async move {
1097    //             let window = cx
1098    //                 .windows()
1099    //                 .into_iter()
1100    //                 .find(|window| window.is_active(&cx).unwrap_or(false));
1101    //             if let Some(window) = window {
1102    //                 //This can only get called when the window's project connection has been lost
1103    //                 //so we don't need to prompt the user for anything and instead just close the window
1104    //                 window.remove(&mut cx);
1105    //             }
1106    //         })
1107    //         .detach();
1108    //     }
1109
1110    //     pub fn close(
1111    //         &mut self,
1112    //         _: &CloseWindow,
1113    //         cx: &mut ViewContext<Self>,
1114    //     ) -> Option<Task<Result<()>>> {
1115    //         let window = cx.window();
1116    //         let prepare = self.prepare_to_close(false, cx);
1117    //         Some(cx.spawn(|_, mut cx| async move {
1118    //             if prepare.await? {
1119    //                 window.remove(&mut cx);
1120    //             }
1121    //             Ok(())
1122    //         }))
1123    //     }
1124
1125    pub fn prepare_to_close(
1126        &mut self,
1127        quitting: bool,
1128        cx: &mut ViewContext<Self>,
1129    ) -> Task<Result<bool>> {
1130        //todo!(saveing)
1131        // let active_call = self.active_call().cloned();
1132        // let window = cx.window();
1133
1134        cx.spawn(|this, mut cx| async move {
1135            // let workspace_count = cx
1136            //     .windows()
1137            //     .into_iter()
1138            //     .filter(|window| window.root_is::<Workspace>())
1139            //     .count();
1140
1141            // if let Some(active_call) = active_call {
1142            //     if !quitting
1143            //         && workspace_count == 1
1144            //         && active_call.read_with(&cx, |call, _| call.room().is_some())
1145            //     {
1146            //         let answer = window.prompt(
1147            //             PromptLevel::Warning,
1148            //             "Do you want to leave the current call?",
1149            //             &["Close window and hang up", "Cancel"],
1150            //             &mut cx,
1151            //         );
1152
1153            //         if let Some(mut answer) = answer {
1154            //             if answer.next().await == Some(1) {
1155            //                 return anyhow::Ok(false);
1156            //             } else {
1157            //                 active_call
1158            //                     .update(&mut cx, |call, cx| call.hang_up(cx))
1159            //                     .await
1160            //                     .log_err();
1161            //             }
1162            //         }
1163            //     }
1164            // }
1165
1166            Ok(
1167                false, // this
1168                      // .update(&mut cx, |this, cx| {
1169                      //     this.save_all_internal(SaveIntent::Close, cx)
1170                      // })?
1171                      // .await?
1172            )
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            .when(
2452                !matches!(cx.window_bounds(), WindowBounds::Fullscreen),
2453                |s| s.pl_20(),
2454            )
2455            .w_full()
2456            .h(rems(1.75))
2457            .bg(cx.theme().colors().title_bar_background)
2458            .on_click(|_, event, cx| {
2459                if event.up.click_count == 2 {
2460                    cx.zoom_window();
2461                }
2462            })
2463            .child(
2464                h_stack()
2465                    // TODO - Add player menu
2466                    .child(
2467                        div()
2468                            .id("project_owner_indicator")
2469                            .child(
2470                                Button::new("player")
2471                                    .variant(ButtonVariant::Ghost)
2472                                    .color(Some(TextColor::Player(0))),
2473                            )
2474                            .tooltip(move |_, cx| {
2475                                cx.build_view(|cx| TextTooltip::new("Toggle following"))
2476                            }),
2477                    )
2478                    // TODO - Add project menu
2479                    .child(
2480                        div()
2481                            .id("titlebar_project_menu_button")
2482                            .child(Button::new("project_name").variant(ButtonVariant::Ghost))
2483                            .tooltip(move |_, cx| {
2484                                cx.build_view(|cx| TextTooltip::new("Recent Projects"))
2485                            }),
2486                    )
2487                    // TODO - Add git menu
2488                    .child(
2489                        div()
2490                            .id("titlebar_git_menu_button")
2491                            .child(
2492                                Button::new("branch_name")
2493                                    .variant(ButtonVariant::Ghost)
2494                                    .color(Some(TextColor::Muted)),
2495                            )
2496                            .tooltip(move |_, cx| {
2497                                // todo!() Replace with real action.
2498                                #[gpui::action]
2499                                struct NoAction {}
2500
2501                                cx.build_view(|cx| {
2502                                    TextTooltip::new("Recent Branches")
2503                                        .key_binding(KeyBinding::new(gpui::KeyBinding::new(
2504                                            "cmd-b",
2505                                            NoAction {},
2506                                            None,
2507                                        )))
2508                                        .meta("Only local branches shown")
2509                                })
2510                            }),
2511                    ),
2512            ) // self.titlebar_item
2513            .child(h_stack().child(Label::new("Right side titlebar item")))
2514    }
2515
2516    fn active_item_path_changed(&mut self, cx: &mut ViewContext<Self>) {
2517        let active_entry = self.active_project_path(cx);
2518        self.project
2519            .update(cx, |project, cx| project.set_active_path(active_entry, cx));
2520        self.update_window_title(cx);
2521    }
2522
2523    fn update_window_title(&mut self, cx: &mut ViewContext<Self>) {
2524        let project = self.project().read(cx);
2525        let mut title = String::new();
2526
2527        if let Some(path) = self.active_item(cx).and_then(|item| item.project_path(cx)) {
2528            let filename = path
2529                .path
2530                .file_name()
2531                .map(|s| s.to_string_lossy())
2532                .or_else(|| {
2533                    Some(Cow::Borrowed(
2534                        project
2535                            .worktree_for_id(path.worktree_id, cx)?
2536                            .read(cx)
2537                            .root_name(),
2538                    ))
2539                });
2540
2541            if let Some(filename) = filename {
2542                title.push_str(filename.as_ref());
2543                title.push_str(" β€” ");
2544            }
2545        }
2546
2547        for (i, name) in project.worktree_root_names(cx).enumerate() {
2548            if i > 0 {
2549                title.push_str(", ");
2550            }
2551            title.push_str(name);
2552        }
2553
2554        if title.is_empty() {
2555            title = "empty project".to_string();
2556        }
2557
2558        if project.is_remote() {
2559            title.push_str(" ↙");
2560        } else if project.is_shared() {
2561            title.push_str(" β†—");
2562        }
2563
2564        // todo!()
2565        // cx.set_window_title(&title);
2566    }
2567
2568    fn update_window_edited(&mut self, cx: &mut ViewContext<Self>) {
2569        let is_edited = !self.project.read(cx).is_read_only()
2570            && self
2571                .items(cx)
2572                .any(|item| item.has_conflict(cx) || item.is_dirty(cx));
2573        if is_edited != self.window_edited {
2574            self.window_edited = is_edited;
2575            // todo!()
2576            // cx.set_window_edited(self.window_edited)
2577        }
2578    }
2579
2580    //     fn render_disconnected_overlay(
2581    //         &self,
2582    //         cx: &mut ViewContext<Workspace>,
2583    //     ) -> Option<AnyElement<Workspace>> {
2584    //         if self.project.read(cx).is_read_only() {
2585    //             enum DisconnectedOverlay {}
2586    //             Some(
2587    //                 MouseEventHandler::new::<DisconnectedOverlay, _>(0, cx, |_, cx| {
2588    //                     let theme = &theme::current(cx);
2589    //                     Label::new(
2590    //                         "Your connection to the remote project has been lost.",
2591    //                         theme.workspace.disconnected_overlay.text.clone(),
2592    //                     )
2593    //                     .aligned()
2594    //                     .contained()
2595    //                     .with_style(theme.workspace.disconnected_overlay.container)
2596    //                 })
2597    //                 .with_cursor_style(CursorStyle::Arrow)
2598    //                 .capture_all()
2599    //                 .into_any_named("disconnected overlay"),
2600    //             )
2601    //         } else {
2602    //             None
2603    //         }
2604    //     }
2605
2606    //     fn render_notifications(
2607    //         &self,
2608    //         theme: &theme::Workspace,
2609    //         cx: &AppContext,
2610    //     ) -> Option<AnyElement<Workspace>> {
2611    //         if self.notifications.is_empty() {
2612    //             None
2613    //         } else {
2614    //             Some(
2615    //                 Flex::column()
2616    //                     .with_children(self.notifications.iter().map(|(_, _, notification)| {
2617    //                         ChildView::new(notification.as_any(), cx)
2618    //                             .contained()
2619    //                             .with_style(theme.notification)
2620    //                     }))
2621    //                     .constrained()
2622    //                     .with_width(theme.notifications.width)
2623    //                     .contained()
2624    //                     .with_style(theme.notifications.container)
2625    //                     .aligned()
2626    //                     .bottom()
2627    //                     .right()
2628    //                     .into_any(),
2629    //             )
2630    //         }
2631    //     }
2632
2633    //     // RPC handlers
2634
2635    fn handle_follow(
2636        &mut self,
2637        _follower_project_id: Option<u64>,
2638        _cx: &mut ViewContext<Self>,
2639    ) -> proto::FollowResponse {
2640        todo!()
2641
2642        //     let client = &self.app_state.client;
2643        //     let project_id = self.project.read(cx).remote_id();
2644
2645        //     let active_view_id = self.active_item(cx).and_then(|i| {
2646        //         Some(
2647        //             i.to_followable_item_handle(cx)?
2648        //                 .remote_id(client, cx)?
2649        //                 .to_proto(),
2650        //         )
2651        //     });
2652
2653        //     cx.notify();
2654
2655        //     self.last_active_view_id = active_view_id.clone();
2656        //     proto::FollowResponse {
2657        //         active_view_id,
2658        //         views: self
2659        //             .panes()
2660        //             .iter()
2661        //             .flat_map(|pane| {
2662        //                 let leader_id = self.leader_for_pane(pane);
2663        //                 pane.read(cx).items().filter_map({
2664        //                     let cx = &cx;
2665        //                     move |item| {
2666        //                         let item = item.to_followable_item_handle(cx)?;
2667        //                         if (project_id.is_none() || project_id != follower_project_id)
2668        //                             && item.is_project_item(cx)
2669        //                         {
2670        //                             return None;
2671        //                         }
2672        //                         let id = item.remote_id(client, cx)?.to_proto();
2673        //                         let variant = item.to_state_proto(cx)?;
2674        //                         Some(proto::View {
2675        //                             id: Some(id),
2676        //                             leader_id,
2677        //                             variant: Some(variant),
2678        //                         })
2679        //                     }
2680        //                 })
2681        //             })
2682        //             .collect(),
2683        //     }
2684    }
2685
2686    fn handle_update_followers(
2687        &mut self,
2688        leader_id: PeerId,
2689        message: proto::UpdateFollowers,
2690        _cx: &mut ViewContext<Self>,
2691    ) {
2692        self.leader_updates_tx
2693            .unbounded_send((leader_id, message))
2694            .ok();
2695    }
2696
2697    async fn process_leader_update(
2698        this: &WeakView<Self>,
2699        leader_id: PeerId,
2700        update: proto::UpdateFollowers,
2701        cx: &mut AsyncWindowContext,
2702    ) -> Result<()> {
2703        match update.variant.ok_or_else(|| anyhow!("invalid update"))? {
2704            proto::update_followers::Variant::UpdateActiveView(update_active_view) => {
2705                this.update(cx, |this, _| {
2706                    for (_, state) in &mut this.follower_states {
2707                        if state.leader_id == leader_id {
2708                            state.active_view_id =
2709                                if let Some(active_view_id) = update_active_view.id.clone() {
2710                                    Some(ViewId::from_proto(active_view_id)?)
2711                                } else {
2712                                    None
2713                                };
2714                        }
2715                    }
2716                    anyhow::Ok(())
2717                })??;
2718            }
2719            proto::update_followers::Variant::UpdateView(update_view) => {
2720                let variant = update_view
2721                    .variant
2722                    .ok_or_else(|| anyhow!("missing update view variant"))?;
2723                let id = update_view
2724                    .id
2725                    .ok_or_else(|| anyhow!("missing update view id"))?;
2726                let mut tasks = Vec::new();
2727                this.update(cx, |this, cx| {
2728                    let project = this.project.clone();
2729                    for (_, state) in &mut this.follower_states {
2730                        if state.leader_id == leader_id {
2731                            let view_id = ViewId::from_proto(id.clone())?;
2732                            if let Some(item) = state.items_by_leader_view_id.get(&view_id) {
2733                                tasks.push(item.apply_update_proto(&project, variant.clone(), cx));
2734                            }
2735                        }
2736                    }
2737                    anyhow::Ok(())
2738                })??;
2739                try_join_all(tasks).await.log_err();
2740            }
2741            proto::update_followers::Variant::CreateView(view) => {
2742                let panes = this.update(cx, |this, _| {
2743                    this.follower_states
2744                        .iter()
2745                        .filter_map(|(pane, state)| (state.leader_id == leader_id).then_some(pane))
2746                        .cloned()
2747                        .collect()
2748                })?;
2749                Self::add_views_from_leader(this.clone(), leader_id, panes, vec![view], cx).await?;
2750            }
2751        }
2752        this.update(cx, |this, cx| this.leader_updated(leader_id, cx))?;
2753        Ok(())
2754    }
2755
2756    async fn add_views_from_leader(
2757        this: WeakView<Self>,
2758        leader_id: PeerId,
2759        panes: Vec<View<Pane>>,
2760        views: Vec<proto::View>,
2761        cx: &mut AsyncWindowContext,
2762    ) -> Result<()> {
2763        let this = this.upgrade().context("workspace dropped")?;
2764
2765        let item_builders = cx.update(|_, cx| {
2766            cx.default_global::<FollowableItemBuilders>()
2767                .values()
2768                .map(|b| b.0)
2769                .collect::<Vec<_>>()
2770        })?;
2771
2772        let mut item_tasks_by_pane = HashMap::default();
2773        for pane in panes {
2774            let mut item_tasks = Vec::new();
2775            let mut leader_view_ids = Vec::new();
2776            for view in &views {
2777                let Some(id) = &view.id else { continue };
2778                let id = ViewId::from_proto(id.clone())?;
2779                let mut variant = view.variant.clone();
2780                if variant.is_none() {
2781                    Err(anyhow!("missing view variant"))?;
2782                }
2783                for build_item in &item_builders {
2784                    let task = cx.update(|_, cx| {
2785                        build_item(pane.clone(), this.clone(), id, &mut variant, cx)
2786                    })?;
2787                    if let Some(task) = task {
2788                        item_tasks.push(task);
2789                        leader_view_ids.push(id);
2790                        break;
2791                    } else {
2792                        assert!(variant.is_some());
2793                    }
2794                }
2795            }
2796
2797            item_tasks_by_pane.insert(pane, (item_tasks, leader_view_ids));
2798        }
2799
2800        for (pane, (item_tasks, leader_view_ids)) in item_tasks_by_pane {
2801            let items = futures::future::try_join_all(item_tasks).await?;
2802            this.update(cx, |this, cx| {
2803                let state = this.follower_states.get_mut(&pane)?;
2804                for (id, item) in leader_view_ids.into_iter().zip(items) {
2805                    item.set_leader_peer_id(Some(leader_id), cx);
2806                    state.items_by_leader_view_id.insert(id, item);
2807                }
2808
2809                Some(())
2810            })?;
2811        }
2812        Ok(())
2813    }
2814
2815    fn update_active_view_for_followers(&mut self, cx: &mut ViewContext<Self>) {
2816        let mut is_project_item = true;
2817        let mut update = proto::UpdateActiveView::default();
2818        if self.active_pane.read(cx).has_focus(cx) {
2819            let item = self
2820                .active_item(cx)
2821                .and_then(|item| item.to_followable_item_handle(cx));
2822            if let Some(item) = item {
2823                is_project_item = item.is_project_item(cx);
2824                update = proto::UpdateActiveView {
2825                    id: item
2826                        .remote_id(&self.app_state.client, cx)
2827                        .map(|id| id.to_proto()),
2828                    leader_id: self.leader_for_pane(&self.active_pane),
2829                };
2830            }
2831        }
2832
2833        if update.id != self.last_active_view_id {
2834            self.last_active_view_id = update.id.clone();
2835            self.update_followers(
2836                is_project_item,
2837                proto::update_followers::Variant::UpdateActiveView(update),
2838                cx,
2839            );
2840        }
2841    }
2842
2843    fn update_followers(
2844        &self,
2845        project_only: bool,
2846        update: proto::update_followers::Variant,
2847        cx: &mut WindowContext,
2848    ) -> Option<()> {
2849        let project_id = if project_only {
2850            self.project.read(cx).remote_id()
2851        } else {
2852            None
2853        };
2854        self.app_state().workspace_store.update(cx, |store, cx| {
2855            store.update_followers(project_id, update, cx)
2856        })
2857    }
2858
2859    pub fn leader_for_pane(&self, pane: &View<Pane>) -> Option<PeerId> {
2860        self.follower_states.get(pane).map(|state| state.leader_id)
2861    }
2862
2863    fn leader_updated(&mut self, leader_id: PeerId, cx: &mut ViewContext<Self>) -> Option<()> {
2864        cx.notify();
2865
2866        let call = self.active_call()?;
2867        let room = call.read(cx).room()?.read(cx);
2868        let participant = room.remote_participant_for_peer_id(leader_id)?;
2869        let mut items_to_activate = Vec::new();
2870
2871        let leader_in_this_app;
2872        let leader_in_this_project;
2873        match participant.location {
2874            call2::ParticipantLocation::SharedProject { project_id } => {
2875                leader_in_this_app = true;
2876                leader_in_this_project = Some(project_id) == self.project.read(cx).remote_id();
2877            }
2878            call2::ParticipantLocation::UnsharedProject => {
2879                leader_in_this_app = true;
2880                leader_in_this_project = false;
2881            }
2882            call2::ParticipantLocation::External => {
2883                leader_in_this_app = false;
2884                leader_in_this_project = false;
2885            }
2886        };
2887
2888        for (pane, state) in &self.follower_states {
2889            if state.leader_id != leader_id {
2890                continue;
2891            }
2892            if let (Some(active_view_id), true) = (state.active_view_id, leader_in_this_app) {
2893                if let Some(item) = state.items_by_leader_view_id.get(&active_view_id) {
2894                    if leader_in_this_project || !item.is_project_item(cx) {
2895                        items_to_activate.push((pane.clone(), item.boxed_clone()));
2896                    }
2897                } else {
2898                    log::warn!(
2899                        "unknown view id {:?} for leader {:?}",
2900                        active_view_id,
2901                        leader_id
2902                    );
2903                }
2904                continue;
2905            }
2906            // todo!()
2907            // if let Some(shared_screen) = self.shared_screen_for_peer(leader_id, pane, cx) {
2908            //     items_to_activate.push((pane.clone(), Box::new(shared_screen)));
2909            // }
2910        }
2911
2912        for (pane, item) in items_to_activate {
2913            let pane_was_focused = pane.read(cx).has_focus(cx);
2914            if let Some(index) = pane.update(cx, |pane, _| pane.index_for_item(item.as_ref())) {
2915                pane.update(cx, |pane, cx| pane.activate_item(index, false, false, cx));
2916            } else {
2917                pane.update(cx, |pane, cx| {
2918                    pane.add_item(item.boxed_clone(), false, false, None, cx)
2919                });
2920            }
2921
2922            if pane_was_focused {
2923                pane.update(cx, |pane, cx| pane.focus_active_item(cx));
2924            }
2925        }
2926
2927        None
2928    }
2929
2930    // todo!()
2931    //     fn shared_screen_for_peer(
2932    //         &self,
2933    //         peer_id: PeerId,
2934    //         pane: &View<Pane>,
2935    //         cx: &mut ViewContext<Self>,
2936    //     ) -> Option<View<SharedScreen>> {
2937    //         let call = self.active_call()?;
2938    //         let room = call.read(cx).room()?.read(cx);
2939    //         let participant = room.remote_participant_for_peer_id(peer_id)?;
2940    //         let track = participant.video_tracks.values().next()?.clone();
2941    //         let user = participant.user.clone();
2942
2943    //         for item in pane.read(cx).items_of_type::<SharedScreen>() {
2944    //             if item.read(cx).peer_id == peer_id {
2945    //                 return Some(item);
2946    //             }
2947    //         }
2948
2949    //         Some(cx.build_view(|cx| SharedScreen::new(&track, peer_id, user.clone(), cx)))
2950    //     }
2951
2952    pub fn on_window_activation_changed(&mut self, cx: &mut ViewContext<Self>) {
2953        if cx.is_window_active() {
2954            self.update_active_view_for_followers(cx);
2955            cx.background_executor()
2956                .spawn(persistence::DB.update_timestamp(self.database_id()))
2957                .detach();
2958        } else {
2959            for pane in &self.panes {
2960                pane.update(cx, |pane, cx| {
2961                    if let Some(item) = pane.active_item() {
2962                        item.workspace_deactivated(cx);
2963                    }
2964                    if matches!(
2965                        WorkspaceSettings::get_global(cx).autosave,
2966                        AutosaveSetting::OnWindowChange | AutosaveSetting::OnFocusChange
2967                    ) {
2968                        for item in pane.items() {
2969                            Pane::autosave_item(item.as_ref(), self.project.clone(), cx)
2970                                .detach_and_log_err(cx);
2971                        }
2972                    }
2973                });
2974            }
2975        }
2976    }
2977
2978    fn active_call(&self) -> Option<&Model<ActiveCall>> {
2979        self.active_call.as_ref().map(|(call, _)| call)
2980    }
2981
2982    fn on_active_call_event(
2983        &mut self,
2984        _: Model<ActiveCall>,
2985        event: &call2::room::Event,
2986        cx: &mut ViewContext<Self>,
2987    ) {
2988        match event {
2989            call2::room::Event::ParticipantLocationChanged { participant_id }
2990            | call2::room::Event::RemoteVideoTracksChanged { participant_id } => {
2991                self.leader_updated(*participant_id, cx);
2992            }
2993            _ => {}
2994        }
2995    }
2996
2997    pub fn database_id(&self) -> WorkspaceId {
2998        self.database_id
2999    }
3000
3001    fn location(&self, cx: &AppContext) -> Option<WorkspaceLocation> {
3002        let project = self.project().read(cx);
3003
3004        if project.is_local() {
3005            Some(
3006                project
3007                    .visible_worktrees(cx)
3008                    .map(|worktree| worktree.read(cx).abs_path())
3009                    .collect::<Vec<_>>()
3010                    .into(),
3011            )
3012        } else {
3013            None
3014        }
3015    }
3016
3017    fn remove_panes(&mut self, member: Member, cx: &mut ViewContext<Workspace>) {
3018        match member {
3019            Member::Axis(PaneAxis { members, .. }) => {
3020                for child in members.iter() {
3021                    self.remove_panes(child.clone(), cx)
3022                }
3023            }
3024            Member::Pane(pane) => {
3025                self.force_remove_pane(&pane, cx);
3026            }
3027        }
3028    }
3029
3030    fn force_remove_pane(&mut self, pane: &View<Pane>, cx: &mut ViewContext<Workspace>) {
3031        self.panes.retain(|p| p != pane);
3032        if true {
3033            todo!()
3034            // cx.focus(self.panes.last().unwrap());
3035        }
3036        if self.last_active_center_pane == Some(pane.downgrade()) {
3037            self.last_active_center_pane = None;
3038        }
3039        cx.notify();
3040    }
3041
3042    //     fn schedule_serialize(&mut self, cx: &mut ViewContext<Self>) {
3043    //         self._schedule_serialize = Some(cx.spawn(|this, cx| async move {
3044    //             cx.background().timer(Duration::from_millis(100)).await;
3045    //             this.read_with(&cx, |this, cx| this.serialize_workspace(cx))
3046    //                 .ok();
3047    //         }));
3048    //     }
3049
3050    fn serialize_workspace(&self, cx: &mut ViewContext<Self>) {
3051        fn serialize_pane_handle(pane_handle: &View<Pane>, cx: &WindowContext) -> SerializedPane {
3052            let (items, active) = {
3053                let pane = pane_handle.read(cx);
3054                let active_item_id = pane.active_item().map(|item| item.id());
3055                (
3056                    pane.items()
3057                        .filter_map(|item_handle| {
3058                            Some(SerializedItem {
3059                                kind: Arc::from(item_handle.serialized_item_kind()?),
3060                                item_id: item_handle.id().as_u64() as usize,
3061                                active: Some(item_handle.id()) == active_item_id,
3062                            })
3063                        })
3064                        .collect::<Vec<_>>(),
3065                    pane.has_focus(cx),
3066                )
3067            };
3068
3069            SerializedPane::new(items, active)
3070        }
3071
3072        fn build_serialized_pane_group(
3073            pane_group: &Member,
3074            cx: &WindowContext,
3075        ) -> SerializedPaneGroup {
3076            match pane_group {
3077                Member::Axis(PaneAxis {
3078                    axis,
3079                    members,
3080                    flexes,
3081                    bounding_boxes: _,
3082                }) => SerializedPaneGroup::Group {
3083                    axis: *axis,
3084                    children: members
3085                        .iter()
3086                        .map(|member| build_serialized_pane_group(member, cx))
3087                        .collect::<Vec<_>>(),
3088                    flexes: Some(flexes.lock().clone()),
3089                },
3090                Member::Pane(pane_handle) => {
3091                    SerializedPaneGroup::Pane(serialize_pane_handle(&pane_handle, cx))
3092                }
3093            }
3094        }
3095
3096        fn build_serialized_docks(
3097            this: &Workspace,
3098            cx: &mut ViewContext<Workspace>,
3099        ) -> DockStructure {
3100            let left_dock = this.left_dock.read(cx);
3101            let left_visible = left_dock.is_open();
3102            let left_active_panel = left_dock
3103                .visible_panel()
3104                .and_then(|panel| Some(panel.persistent_name(cx).to_string()));
3105            let left_dock_zoom = left_dock
3106                .visible_panel()
3107                .map(|panel| panel.is_zoomed(cx))
3108                .unwrap_or(false);
3109
3110            let right_dock = this.right_dock.read(cx);
3111            let right_visible = right_dock.is_open();
3112            let right_active_panel = right_dock
3113                .visible_panel()
3114                .and_then(|panel| Some(panel.persistent_name(cx).to_string()));
3115            let right_dock_zoom = right_dock
3116                .visible_panel()
3117                .map(|panel| panel.is_zoomed(cx))
3118                .unwrap_or(false);
3119
3120            let bottom_dock = this.bottom_dock.read(cx);
3121            let bottom_visible = bottom_dock.is_open();
3122            let bottom_active_panel = bottom_dock
3123                .visible_panel()
3124                .and_then(|panel| Some(panel.persistent_name(cx).to_string()));
3125            let bottom_dock_zoom = bottom_dock
3126                .visible_panel()
3127                .map(|panel| panel.is_zoomed(cx))
3128                .unwrap_or(false);
3129
3130            DockStructure {
3131                left: DockData {
3132                    visible: left_visible,
3133                    active_panel: left_active_panel,
3134                    zoom: left_dock_zoom,
3135                },
3136                right: DockData {
3137                    visible: right_visible,
3138                    active_panel: right_active_panel,
3139                    zoom: right_dock_zoom,
3140                },
3141                bottom: DockData {
3142                    visible: bottom_visible,
3143                    active_panel: bottom_active_panel,
3144                    zoom: bottom_dock_zoom,
3145                },
3146            }
3147        }
3148
3149        if let Some(location) = self.location(cx) {
3150            // Load bearing special case:
3151            //  - with_local_workspace() relies on this to not have other stuff open
3152            //    when you open your log
3153            if !location.paths().is_empty() {
3154                let center_group = build_serialized_pane_group(&self.center.root, cx);
3155                let docks = build_serialized_docks(self, cx);
3156
3157                let serialized_workspace = SerializedWorkspace {
3158                    id: self.database_id,
3159                    location,
3160                    center_group,
3161                    bounds: Default::default(),
3162                    display: Default::default(),
3163                    docks,
3164                };
3165
3166                cx.spawn(|_, _| persistence::DB.save_workspace(serialized_workspace))
3167                    .detach();
3168            }
3169        }
3170    }
3171
3172    pub(crate) fn load_workspace(
3173        serialized_workspace: SerializedWorkspace,
3174        paths_to_open: Vec<Option<ProjectPath>>,
3175        cx: &mut ViewContext<Workspace>,
3176    ) -> Task<Result<Vec<Option<Box<dyn ItemHandle>>>>> {
3177        cx.spawn(|workspace, mut cx| async move {
3178            let (project, old_center_pane) = workspace.update(&mut cx, |workspace, _| {
3179                (
3180                    workspace.project().clone(),
3181                    workspace.last_active_center_pane.clone(),
3182                )
3183            })?;
3184
3185            let mut center_group = None;
3186            let mut center_items = None;
3187
3188            // Traverse the splits tree and add to things
3189            if let Some((group, active_pane, items)) = serialized_workspace
3190                .center_group
3191                .deserialize(
3192                    &project,
3193                    serialized_workspace.id,
3194                    workspace.clone(),
3195                    &mut cx,
3196                )
3197                .await
3198            {
3199                center_items = Some(items);
3200                center_group = Some((group, active_pane))
3201            }
3202
3203            let mut items_by_project_path = cx.update(|_, cx| {
3204                center_items
3205                    .unwrap_or_default()
3206                    .into_iter()
3207                    .filter_map(|item| {
3208                        let item = item?;
3209                        let project_path = item.project_path(cx)?;
3210                        Some((project_path, item))
3211                    })
3212                    .collect::<HashMap<_, _>>()
3213            })?;
3214
3215            let opened_items = paths_to_open
3216                .into_iter()
3217                .map(|path_to_open| {
3218                    path_to_open
3219                        .and_then(|path_to_open| items_by_project_path.remove(&path_to_open))
3220                })
3221                .collect::<Vec<_>>();
3222
3223            // Remove old panes from workspace panes list
3224            workspace.update(&mut cx, |workspace, cx| {
3225                if let Some((center_group, active_pane)) = center_group {
3226                    workspace.remove_panes(workspace.center.root.clone(), cx);
3227
3228                    // Swap workspace center group
3229                    workspace.center = PaneGroup::with_root(center_group);
3230
3231                    // Change the focus to the workspace first so that we retrigger focus in on the pane.
3232                    // todo!()
3233                    // cx.focus_self();
3234                    // if let Some(active_pane) = active_pane {
3235                    //     cx.focus(&active_pane);
3236                    // } else {
3237                    //     cx.focus(workspace.panes.last().unwrap());
3238                    // }
3239                } else {
3240                    // todo!()
3241                    // let old_center_handle = old_center_pane.and_then(|weak| weak.upgrade());
3242                    // if let Some(old_center_handle) = old_center_handle {
3243                    //     cx.focus(&old_center_handle)
3244                    // } else {
3245                    //     cx.focus_self()
3246                    // }
3247                }
3248
3249                let docks = serialized_workspace.docks;
3250                workspace.left_dock.update(cx, |dock, cx| {
3251                    dock.set_open(docks.left.visible, cx);
3252                    if let Some(active_panel) = docks.left.active_panel {
3253                        if let Some(ix) = dock.panel_index_for_ui_name(&active_panel, cx) {
3254                            dock.activate_panel(ix, cx);
3255                        }
3256                    }
3257                    dock.active_panel()
3258                        .map(|panel| panel.set_zoomed(docks.left.zoom, cx));
3259                    if docks.left.visible && docks.left.zoom {
3260                        // todo!()
3261                        // cx.focus_self()
3262                    }
3263                });
3264                // TODO: I think the bug is that setting zoom or active undoes the bottom zoom or something
3265                workspace.right_dock.update(cx, |dock, cx| {
3266                    dock.set_open(docks.right.visible, cx);
3267                    if let Some(active_panel) = docks.right.active_panel {
3268                        if let Some(ix) = dock.panel_index_for_ui_name(&active_panel, cx) {
3269                            dock.activate_panel(ix, cx);
3270                        }
3271                    }
3272                    dock.active_panel()
3273                        .map(|panel| panel.set_zoomed(docks.right.zoom, cx));
3274
3275                    if docks.right.visible && docks.right.zoom {
3276                        // todo!()
3277                        // cx.focus_self()
3278                    }
3279                });
3280                workspace.bottom_dock.update(cx, |dock, cx| {
3281                    dock.set_open(docks.bottom.visible, cx);
3282                    if let Some(active_panel) = docks.bottom.active_panel {
3283                        if let Some(ix) = dock.panel_index_for_ui_name(&active_panel, cx) {
3284                            dock.activate_panel(ix, cx);
3285                        }
3286                    }
3287
3288                    dock.active_panel()
3289                        .map(|panel| panel.set_zoomed(docks.bottom.zoom, cx));
3290
3291                    if docks.bottom.visible && docks.bottom.zoom {
3292                        // todo!()
3293                        // cx.focus_self()
3294                    }
3295                });
3296
3297                cx.notify();
3298            })?;
3299
3300            // Serialize ourself to make sure our timestamps and any pane / item changes are replicated
3301            workspace.update(&mut cx, |workspace, cx| workspace.serialize_workspace(cx))?;
3302
3303            Ok(opened_items)
3304        })
3305    }
3306
3307    fn actions(div: Div<Self>) -> Div<Self> {
3308        div
3309            //     cx.add_async_action(Workspace::open);
3310            //     cx.add_async_action(Workspace::follow_next_collaborator);
3311            //     cx.add_async_action(Workspace::close);
3312            //     cx.add_async_action(Workspace::close_inactive_items_and_panes);
3313            //     cx.add_async_action(Workspace::close_all_items_and_panes);
3314            //     cx.add_global_action(Workspace::close_global);
3315            //     cx.add_global_action(restart);
3316            //     cx.add_async_action(Workspace::save_all);
3317            //     cx.add_action(Workspace::add_folder_to_project);
3318            //     cx.add_action(
3319            //         |workspace: &mut Workspace, _: &Unfollow, cx: &mut ViewContext<Workspace>| {
3320            //             let pane = workspace.active_pane().clone();
3321            //             workspace.unfollow(&pane, cx);
3322            //         },
3323            //     );
3324            //     cx.add_action(
3325            //         |workspace: &mut Workspace, action: &Save, cx: &mut ViewContext<Workspace>| {
3326            //             workspace
3327            //                 .save_active_item(action.save_intent.unwrap_or(SaveIntent::Save), cx)
3328            //                 .detach_and_log_err(cx);
3329            //         },
3330            //     );
3331            //     cx.add_action(
3332            //         |workspace: &mut Workspace, _: &SaveAs, cx: &mut ViewContext<Workspace>| {
3333            //             workspace
3334            //                 .save_active_item(SaveIntent::SaveAs, cx)
3335            //                 .detach_and_log_err(cx);
3336            //         },
3337            //     );
3338            //     cx.add_action(|workspace: &mut Workspace, _: &ActivatePreviousPane, cx| {
3339            //         workspace.activate_previous_pane(cx)
3340            //     });
3341            //     cx.add_action(|workspace: &mut Workspace, _: &ActivateNextPane, cx| {
3342            //         workspace.activate_next_pane(cx)
3343            //     });
3344            //     cx.add_action(
3345            //         |workspace: &mut Workspace, action: &ActivatePaneInDirection, cx| {
3346            //             workspace.activate_pane_in_direction(action.0, cx)
3347            //         },
3348            //     );
3349            //     cx.add_action(
3350            //         |workspace: &mut Workspace, action: &SwapPaneInDirection, cx| {
3351            //             workspace.swap_pane_in_direction(action.0, cx)
3352            //         },
3353            //     );
3354            .on_action(|this, e: &ToggleLeftDock, cx| {
3355                println!("TOGGLING DOCK");
3356                this.toggle_dock(DockPosition::Left, cx);
3357            })
3358        //     cx.add_action(|workspace: &mut Workspace, _: &ToggleRightDock, cx| {
3359        //         workspace.toggle_dock(DockPosition::Right, cx);
3360        //     });
3361        //     cx.add_action(|workspace: &mut Workspace, _: &ToggleBottomDock, cx| {
3362        //         workspace.toggle_dock(DockPosition::Bottom, cx);
3363        //     });
3364        //     cx.add_action(|workspace: &mut Workspace, _: &CloseAllDocks, cx| {
3365        //         workspace.close_all_docks(cx);
3366        //     });
3367        //     cx.add_action(Workspace::activate_pane_at_index);
3368        //     cx.add_action(|workspace: &mut Workspace, _: &ReopenClosedItem, cx| {
3369        //         workspace.reopen_closed_item(cx).detach();
3370        //     });
3371        //     cx.add_action(|workspace: &mut Workspace, _: &GoBack, cx| {
3372        //         workspace
3373        //             .go_back(workspace.active_pane().downgrade(), cx)
3374        //             .detach();
3375        //     });
3376        //     cx.add_action(|workspace: &mut Workspace, _: &GoForward, cx| {
3377        //         workspace
3378        //             .go_forward(workspace.active_pane().downgrade(), cx)
3379        //             .detach();
3380        //     });
3381
3382        //     cx.add_action(|_: &mut Workspace, _: &install_cli::Install, cx| {
3383        //         cx.spawn(|workspace, mut cx| async move {
3384        //             let err = install_cli::install_cli(&cx)
3385        //                 .await
3386        //                 .context("Failed to create CLI symlink");
3387
3388        //             workspace.update(&mut cx, |workspace, cx| {
3389        //                 if matches!(err, Err(_)) {
3390        //                     err.notify_err(workspace, cx);
3391        //                 } else {
3392        //                     workspace.show_notification(1, cx, |cx| {
3393        //                         cx.build_view(|_| {
3394        //                             MessageNotification::new("Successfully installed the `zed` binary")
3395        //                         })
3396        //                     });
3397        //                 }
3398        //             })
3399        //         })
3400        //         .detach();
3401        //     });
3402    }
3403
3404    // todo!()
3405    //     #[cfg(any(test, feature = "test-support"))]
3406    //     pub fn test_new(project: ModelHandle<Project>, cx: &mut ViewContext<Self>) -> Self {
3407    //         use node_runtime::FakeNodeRuntime;
3408    #[cfg(any(test, feature = "test-support"))]
3409    pub fn test_new(project: Model<Project>, cx: &mut ViewContext<Self>) -> Self {
3410        use node_runtime::FakeNodeRuntime;
3411
3412        let client = project.read(cx).client();
3413        let user_store = project.read(cx).user_store();
3414
3415        let workspace_store = cx.build_model(|cx| WorkspaceStore::new(client.clone(), cx));
3416        let app_state = Arc::new(AppState {
3417            languages: project.read(cx).languages().clone(),
3418            workspace_store,
3419            client,
3420            user_store,
3421            fs: project.read(cx).fs().clone(),
3422            build_window_options: |_, _, _| Default::default(),
3423            initialize_workspace: |_, _, _, _| Task::ready(Ok(())),
3424            node_runtime: FakeNodeRuntime::new(),
3425        });
3426        Self::new(0, project, app_state, cx)
3427    }
3428
3429    //     fn render_dock(&self, position: DockPosition, cx: &WindowContext) -> Option<AnyElement<Self>> {
3430    //         let dock = match position {
3431    //             DockPosition::Left => &self.left_dock,
3432    //             DockPosition::Right => &self.right_dock,
3433    //             DockPosition::Bottom => &self.bottom_dock,
3434    //         };
3435    //         let active_panel = dock.read(cx).visible_panel()?;
3436    //         let element = if Some(active_panel.id()) == self.zoomed.as_ref().map(|zoomed| zoomed.id()) {
3437    //             dock.read(cx).render_placeholder(cx)
3438    //         } else {
3439    //             ChildView::new(dock, cx).into_any()
3440    //         };
3441
3442    //         Some(
3443    //             element
3444    //                 .constrained()
3445    //                 .dynamically(move |constraint, _, cx| match position {
3446    //                     DockPosition::Left | DockPosition::Right => SizeConstraint::new(
3447    //                         Vector2F::new(20., constraint.min.y()),
3448    //                         Vector2F::new(cx.window_size().x() * 0.8, constraint.max.y()),
3449    //                     ),
3450    //                     DockPosition::Bottom => SizeConstraint::new(
3451    //                         Vector2F::new(constraint.min.x(), 20.),
3452    //                         Vector2F::new(constraint.max.x(), cx.window_size().y() * 0.8),
3453    //                     ),
3454    //                 })
3455    //                 .into_any(),
3456    //         )
3457    //     }
3458    // }
3459    pub fn register_action<A: Action>(
3460        &mut self,
3461        callback: impl Fn(&mut Self, &A, &mut ViewContext<Self>) + 'static,
3462    ) -> &mut Self {
3463        let callback = Arc::new(callback);
3464
3465        self.workspace_actions.push(Box::new(move |div| {
3466            let callback = callback.clone();
3467            div.on_action(move |workspace, event, cx| (callback.clone())(workspace, event, cx))
3468        }));
3469        self
3470    }
3471
3472    fn add_workspace_actions_listeners(&self, mut div: Div<Workspace>) -> Div<Workspace> {
3473        for action in self.workspace_actions.iter() {
3474            div = (action)(div)
3475        }
3476        div
3477    }
3478
3479    pub fn toggle_modal<V: Modal, B>(&mut self, cx: &mut ViewContext<Self>, build: B)
3480    where
3481        B: FnOnce(&mut ViewContext<V>) -> V,
3482    {
3483        self.modal_layer
3484            .update(cx, |modal_layer, cx| modal_layer.toggle_modal(cx, build))
3485    }
3486}
3487
3488fn window_bounds_env_override(cx: &AsyncAppContext) -> Option<WindowBounds> {
3489    let display_origin = cx
3490        .update(|cx| Some(cx.displays().first()?.bounds().origin))
3491        .ok()??;
3492    ZED_WINDOW_POSITION
3493        .zip(*ZED_WINDOW_SIZE)
3494        .map(|(position, size)| {
3495            WindowBounds::Fixed(Bounds {
3496                origin: display_origin + position,
3497                size,
3498            })
3499        })
3500}
3501
3502fn open_items(
3503    serialized_workspace: Option<SerializedWorkspace>,
3504    mut project_paths_to_open: Vec<(PathBuf, Option<ProjectPath>)>,
3505    app_state: Arc<AppState>,
3506    cx: &mut ViewContext<Workspace>,
3507) -> impl 'static + Future<Output = Result<Vec<Option<Result<Box<dyn ItemHandle>>>>>> {
3508    let restored_items = serialized_workspace.map(|serialized_workspace| {
3509        Workspace::load_workspace(
3510            serialized_workspace,
3511            project_paths_to_open
3512                .iter()
3513                .map(|(_, project_path)| project_path)
3514                .cloned()
3515                .collect(),
3516            cx,
3517        )
3518    });
3519
3520    cx.spawn(|workspace, mut cx| async move {
3521        let mut opened_items = Vec::with_capacity(project_paths_to_open.len());
3522
3523        if let Some(restored_items) = restored_items {
3524            let restored_items = restored_items.await?;
3525
3526            let restored_project_paths = restored_items
3527                .iter()
3528                .filter_map(|item| {
3529                    cx.update(|_, cx| item.as_ref()?.project_path(cx))
3530                        .ok()
3531                        .flatten()
3532                })
3533                .collect::<HashSet<_>>();
3534
3535            for restored_item in restored_items {
3536                opened_items.push(restored_item.map(Ok));
3537            }
3538
3539            project_paths_to_open
3540                .iter_mut()
3541                .for_each(|(_, project_path)| {
3542                    if let Some(project_path_to_open) = project_path {
3543                        if restored_project_paths.contains(project_path_to_open) {
3544                            *project_path = None;
3545                        }
3546                    }
3547                });
3548        } else {
3549            for _ in 0..project_paths_to_open.len() {
3550                opened_items.push(None);
3551            }
3552        }
3553        assert!(opened_items.len() == project_paths_to_open.len());
3554
3555        let tasks =
3556            project_paths_to_open
3557                .into_iter()
3558                .enumerate()
3559                .map(|(i, (abs_path, project_path))| {
3560                    let workspace = workspace.clone();
3561                    cx.spawn(|mut cx| {
3562                        let fs = app_state.fs.clone();
3563                        async move {
3564                            let file_project_path = project_path?;
3565                            if fs.is_file(&abs_path).await {
3566                                Some((
3567                                    i,
3568                                    workspace
3569                                        .update(&mut cx, |workspace, cx| {
3570                                            workspace.open_path(file_project_path, None, true, cx)
3571                                        })
3572                                        .log_err()?
3573                                        .await,
3574                                ))
3575                            } else {
3576                                None
3577                            }
3578                        }
3579                    })
3580                });
3581
3582        let tasks = tasks.collect::<Vec<_>>();
3583
3584        let tasks = futures::future::join_all(tasks.into_iter());
3585        for maybe_opened_path in tasks.await.into_iter() {
3586            if let Some((i, path_open_result)) = maybe_opened_path {
3587                opened_items[i] = Some(path_open_result);
3588            }
3589        }
3590
3591        Ok(opened_items)
3592    })
3593}
3594
3595// todo!()
3596// fn notify_of_new_dock(workspace: &WeakView<Workspace>, cx: &mut AsyncAppContext) {
3597//     const NEW_PANEL_BLOG_POST: &str = "https://zed.dev/blog/new-panel-system";
3598//     const NEW_DOCK_HINT_KEY: &str = "show_new_dock_key";
3599//     const MESSAGE_ID: usize = 2;
3600
3601//     if workspace
3602//         .read_with(cx, |workspace, cx| {
3603//             workspace.has_shown_notification_once::<MessageNotification>(MESSAGE_ID, cx)
3604//         })
3605//         .unwrap_or(false)
3606//     {
3607//         return;
3608//     }
3609
3610//     if db::kvp::KEY_VALUE_STORE
3611//         .read_kvp(NEW_DOCK_HINT_KEY)
3612//         .ok()
3613//         .flatten()
3614//         .is_some()
3615//     {
3616//         if !workspace
3617//             .read_with(cx, |workspace, cx| {
3618//                 workspace.has_shown_notification_once::<MessageNotification>(MESSAGE_ID, cx)
3619//             })
3620//             .unwrap_or(false)
3621//         {
3622//             cx.update(|cx| {
3623//                 cx.update_global::<NotificationTracker, _, _>(|tracker, _| {
3624//                     let entry = tracker
3625//                         .entry(TypeId::of::<MessageNotification>())
3626//                         .or_default();
3627//                     if !entry.contains(&MESSAGE_ID) {
3628//                         entry.push(MESSAGE_ID);
3629//                     }
3630//                 });
3631//             });
3632//         }
3633
3634//         return;
3635//     }
3636
3637//     cx.spawn(|_| async move {
3638//         db::kvp::KEY_VALUE_STORE
3639//             .write_kvp(NEW_DOCK_HINT_KEY.to_string(), "seen".to_string())
3640//             .await
3641//             .ok();
3642//     })
3643//     .detach();
3644
3645//     workspace
3646//         .update(cx, |workspace, cx| {
3647//             workspace.show_notification_once(2, cx, |cx| {
3648//                 cx.build_view(|_| {
3649//                     MessageNotification::new_element(|text, _| {
3650//                         Text::new(
3651//                             "Looking for the dock? Try ctrl-`!\nshift-escape now zooms your pane.",
3652//                             text,
3653//                         )
3654//                         .with_custom_runs(vec![26..32, 34..46], |_, bounds, cx| {
3655//                             let code_span_background_color = settings::get::<ThemeSettings>(cx)
3656//                                 .theme
3657//                                 .editor
3658//                                 .document_highlight_read_background;
3659
3660//                             cx.scene().push_quad(gpui::Quad {
3661//                                 bounds,
3662//                                 background: Some(code_span_background_color),
3663//                                 border: Default::default(),
3664//                                 corner_radii: (2.0).into(),
3665//                             })
3666//                         })
3667//                         .into_any()
3668//                     })
3669//                     .with_click_message("Read more about the new panel system")
3670//                     .on_click(|cx| cx.platform().open_url(NEW_PANEL_BLOG_POST))
3671//                 })
3672//             })
3673//         })
3674//         .ok();
3675
3676fn notify_if_database_failed(workspace: WindowHandle<Workspace>, cx: &mut AsyncAppContext) {
3677    const REPORT_ISSUE_URL: &str ="https://github.com/zed-industries/community/issues/new?assignees=&labels=defect%2Ctriage&template=2_bug_report.yml";
3678
3679    workspace
3680        .update(cx, |workspace, cx| {
3681            if (*db2::ALL_FILE_DB_FAILED).load(std::sync::atomic::Ordering::Acquire) {
3682                workspace.show_notification_once(0, cx, |cx| {
3683                    cx.build_view(|_| {
3684                        MessageNotification::new("Failed to load the database file.")
3685                            .with_click_message("Click to let us know about this error")
3686                            .on_click(|cx| cx.open_url(REPORT_ISSUE_URL))
3687                    })
3688                });
3689            }
3690        })
3691        .log_err();
3692}
3693
3694impl EventEmitter<Event> for Workspace {}
3695
3696impl Render for Workspace {
3697    type Element = Div<Self>;
3698
3699    fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
3700        let mut context = KeyContext::default();
3701        context.add("Workspace");
3702
3703        self.add_workspace_actions_listeners(div())
3704            .key_context(context)
3705            .relative()
3706            .size_full()
3707            .flex()
3708            .flex_col()
3709            .font("Zed Sans")
3710            .gap_0()
3711            .justify_start()
3712            .items_start()
3713            .text_color(cx.theme().colors().text)
3714            .bg(cx.theme().colors().background)
3715            .child(self.render_titlebar(cx))
3716            .child(
3717                // todo! should this be a component a view?
3718                div()
3719                    .id("workspace")
3720                    .relative()
3721                    .flex_1()
3722                    .w_full()
3723                    .flex()
3724                    .overflow_hidden()
3725                    .border_t()
3726                    .border_b()
3727                    .border_color(cx.theme().colors().border)
3728                    .child(self.modal_layer.clone())
3729                    .child(
3730                        div()
3731                            .flex()
3732                            .flex_row()
3733                            .flex_1()
3734                            .h_full()
3735                            .child(div().flex().flex_1().child(self.left_dock.clone()))
3736                            .child(
3737                                div()
3738                                    .flex()
3739                                    .flex_col()
3740                                    .flex_1()
3741                                    .child(self.center.render(
3742                                        &self.project,
3743                                        &self.follower_states,
3744                                        self.active_call(),
3745                                        &self.active_pane,
3746                                        self.zoomed.as_ref(),
3747                                        &self.app_state,
3748                                        cx,
3749                                    ))
3750                                    .child(div().flex().flex_1().child(self.bottom_dock.clone())),
3751                            )
3752                            .child(div().flex().flex_1().child(self.right_dock.clone())),
3753                    ),
3754            )
3755            .child(self.status_bar.clone())
3756            // .when(self.debug.show_toast, |this| {
3757            //     this.child(Toast::new(ToastOrigin::Bottom).child(Label::new("A toast")))
3758            // })
3759            // .children(
3760            //     Some(
3761            //         div()
3762            //             .absolute()
3763            //             .top(px(50.))
3764            //             .left(px(640.))
3765            //             .z_index(8)
3766            //             .child(LanguageSelector::new("language-selector")),
3767            //     )
3768            //     .filter(|_| self.is_language_selector_open()),
3769            // )
3770            .z_index(8)
3771            // Debug
3772            .child(
3773                div()
3774                    .flex()
3775                    .flex_col()
3776                    .z_index(9)
3777                    .absolute()
3778                    .top_20()
3779                    .left_1_4()
3780                    .w_40()
3781                    .gap_2(), // .when(self.show_debug, |this| {
3782                              //     this.child(Button::<Workspace>::new("Toggle User Settings").on_click(
3783                              //         Arc::new(|workspace, cx| workspace.debug_toggle_user_settings(cx)),
3784                              //     ))
3785                              //     .child(
3786                              //         Button::<Workspace>::new("Toggle Toasts").on_click(Arc::new(
3787                              //             |workspace, cx| workspace.debug_toggle_toast(cx),
3788                              //         )),
3789                              //     )
3790                              //     .child(
3791                              //         Button::<Workspace>::new("Toggle Livestream").on_click(Arc::new(
3792                              //             |workspace, cx| workspace.debug_toggle_livestream(cx),
3793                              //         )),
3794                              //     )
3795                              // })
3796                              // .child(
3797                              //     Button::<Workspace>::new("Toggle Debug")
3798                              //         .on_click(Arc::new(|workspace, cx| workspace.toggle_debug(cx))),
3799                              // ),
3800            )
3801    }
3802}
3803// todo!()
3804// impl Entity for Workspace {
3805//     type Event = Event;
3806
3807//     fn release(&mut self, cx: &mut AppContext) {
3808//         self.app_state.workspace_store.update(cx, |store, _| {
3809//             store.workspaces.remove(&self.weak_self);
3810//         })
3811//     }
3812// }
3813
3814// impl View for Workspace {
3815//     fn ui_name() -> &'static str {
3816//         "Workspace"
3817//     }
3818
3819//     fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
3820//         let theme = theme::current(cx).clone();
3821//         Stack::new()
3822//             .with_child(
3823//                 Flex::column()
3824//                     .with_child(self.render_titlebar(&theme, cx))
3825//                     .with_child(
3826//                         Stack::new()
3827//                             .with_child({
3828//                                 let project = self.project.clone();
3829//                                 Flex::row()
3830//                                     .with_children(self.render_dock(DockPosition::Left, cx))
3831//                                     .with_child(
3832//                                         Flex::column()
3833//                                             .with_child(
3834//                                                 FlexItem::new(
3835//                                                     self.center.render(
3836//                                                         &project,
3837//                                                         &theme,
3838//                                                         &self.follower_states,
3839//                                                         self.active_call(),
3840//                                                         self.active_pane(),
3841//                                                         self.zoomed
3842//                                                             .as_ref()
3843//                                                             .and_then(|zoomed| zoomed.upgrade(cx))
3844//                                                             .as_ref(),
3845//                                                         &self.app_state,
3846//                                                         cx,
3847//                                                     ),
3848//                                                 )
3849//                                                 .flex(1., true),
3850//                                             )
3851//                                             .with_children(
3852//                                                 self.render_dock(DockPosition::Bottom, cx),
3853//                                             )
3854//                                             .flex(1., true),
3855//                                     )
3856//                                     .with_children(self.render_dock(DockPosition::Right, cx))
3857//                             })
3858//                             .with_child(Overlay::new(
3859//                                 Stack::new()
3860//                                     .with_children(self.zoomed.as_ref().and_then(|zoomed| {
3861//                                         enum ZoomBackground {}
3862//                                         let zoomed = zoomed.upgrade(cx)?;
3863
3864//                                         let mut foreground_style =
3865//                                             theme.workspace.zoomed_pane_foreground;
3866//                                         if let Some(zoomed_dock_position) = self.zoomed_position {
3867//                                             foreground_style =
3868//                                                 theme.workspace.zoomed_panel_foreground;
3869//                                             let margin = foreground_style.margin.top;
3870//                                             let border = foreground_style.border.top;
3871
3872//                                             // Only include a margin and border on the opposite side.
3873//                                             foreground_style.margin.top = 0.;
3874//                                             foreground_style.margin.left = 0.;
3875//                                             foreground_style.margin.bottom = 0.;
3876//                                             foreground_style.margin.right = 0.;
3877//                                             foreground_style.border.top = false;
3878//                                             foreground_style.border.left = false;
3879//                                             foreground_style.border.bottom = false;
3880//                                             foreground_style.border.right = false;
3881//                                             match zoomed_dock_position {
3882//                                                 DockPosition::Left => {
3883//                                                     foreground_style.margin.right = margin;
3884//                                                     foreground_style.border.right = border;
3885//                                                 }
3886//                                                 DockPosition::Right => {
3887//                                                     foreground_style.margin.left = margin;
3888//                                                     foreground_style.border.left = border;
3889//                                                 }
3890//                                                 DockPosition::Bottom => {
3891//                                                     foreground_style.margin.top = margin;
3892//                                                     foreground_style.border.top = border;
3893//                                                 }
3894//                                             }
3895//                                         }
3896
3897//                                         Some(
3898//                                             ChildView::new(&zoomed, cx)
3899//                                                 .contained()
3900//                                                 .with_style(foreground_style)
3901//                                                 .aligned()
3902//                                                 .contained()
3903//                                                 .with_style(theme.workspace.zoomed_background)
3904//                                                 .mouse::<ZoomBackground>(0)
3905//                                                 .capture_all()
3906//                                                 .on_down(
3907//                                                     MouseButton::Left,
3908//                                                     |_, this: &mut Self, cx| {
3909//                                                         this.zoom_out(cx);
3910//                                                     },
3911//                                                 ),
3912//                                         )
3913//                                     }))
3914//                                     .with_children(self.modal.as_ref().map(|modal| {
3915//                                         // Prevent clicks within the modal from falling
3916//                                         // through to the rest of the workspace.
3917//                                         enum ModalBackground {}
3918//                                         MouseEventHandler::new::<ModalBackground, _>(
3919//                                             0,
3920//                                             cx,
3921//                                             |_, cx| ChildView::new(modal.view.as_any(), cx),
3922//                                         )
3923//                                         .on_click(MouseButton::Left, |_, _, _| {})
3924//                                         .contained()
3925//                                         .with_style(theme.workspace.modal)
3926//                                         .aligned()
3927//                                         .top()
3928//                                     }))
3929//                                     .with_children(self.render_notifications(&theme.workspace, cx)),
3930//                             ))
3931//                             .provide_resize_bounds::<WorkspaceBounds>()
3932//                             .flex(1.0, true),
3933//                     )
3934//                     .with_child(ChildView::new(&self.status_bar, cx))
3935//                     .contained()
3936//                     .with_background_color(theme.workspace.background),
3937//             )
3938//             .with_children(DragAndDrop::render(cx))
3939//             .with_children(self.render_disconnected_overlay(cx))
3940//             .into_any_named("workspace")
3941//     }
3942
3943//     fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
3944//         if cx.is_self_focused() {
3945//             cx.focus(&self.active_pane);
3946//         }
3947//     }
3948
3949//     fn modifiers_changed(&mut self, e: &ModifiersChangedEvent, cx: &mut ViewContext<Self>) -> bool {
3950//         DragAndDrop::<Workspace>::update_modifiers(e.modifiers, cx)
3951//     }
3952// }
3953
3954impl WorkspaceStore {
3955    pub fn new(client: Arc<Client>, _cx: &mut ModelContext<Self>) -> Self {
3956        Self {
3957            workspaces: Default::default(),
3958            followers: Default::default(),
3959            _subscriptions: vec![],
3960            //     client.add_request_handler(cx.weak_model(), Self::handle_follow),
3961            //     client.add_message_handler(cx.weak_model(), Self::handle_unfollow),
3962            //     client.add_message_handler(cx.weak_model(), Self::handle_update_followers),
3963            // ],
3964            client,
3965        }
3966    }
3967
3968    pub fn update_followers(
3969        &self,
3970        project_id: Option<u64>,
3971        update: proto::update_followers::Variant,
3972        cx: &AppContext,
3973    ) -> Option<()> {
3974        if !cx.has_global::<Model<ActiveCall>>() {
3975            return None;
3976        }
3977
3978        let room_id = ActiveCall::global(cx).read(cx).room()?.read(cx).id();
3979        let follower_ids: Vec<_> = self
3980            .followers
3981            .iter()
3982            .filter_map(|follower| {
3983                if follower.project_id == project_id || project_id.is_none() {
3984                    Some(follower.peer_id.into())
3985                } else {
3986                    None
3987                }
3988            })
3989            .collect();
3990        if follower_ids.is_empty() {
3991            return None;
3992        }
3993        self.client
3994            .send(proto::UpdateFollowers {
3995                room_id,
3996                project_id,
3997                follower_ids,
3998                variant: Some(update),
3999            })
4000            .log_err()
4001    }
4002
4003    pub async fn handle_follow(
4004        this: Model<Self>,
4005        envelope: TypedEnvelope<proto::Follow>,
4006        _: Arc<Client>,
4007        mut cx: AsyncAppContext,
4008    ) -> Result<proto::FollowResponse> {
4009        this.update(&mut cx, |this, cx| {
4010            let follower = Follower {
4011                project_id: envelope.payload.project_id,
4012                peer_id: envelope.original_sender_id()?,
4013            };
4014            let active_project = ActiveCall::global(cx).read(cx).location().cloned();
4015
4016            let mut response = proto::FollowResponse::default();
4017            for workspace in &this.workspaces {
4018                workspace
4019                    .update(cx, |workspace, cx| {
4020                        let handler_response = workspace.handle_follow(follower.project_id, cx);
4021                        if response.views.is_empty() {
4022                            response.views = handler_response.views;
4023                        } else {
4024                            response.views.extend_from_slice(&handler_response.views);
4025                        }
4026
4027                        if let Some(active_view_id) = handler_response.active_view_id.clone() {
4028                            if response.active_view_id.is_none()
4029                                || Some(workspace.project.downgrade()) == active_project
4030                            {
4031                                response.active_view_id = Some(active_view_id);
4032                            }
4033                        }
4034                    })
4035                    .ok();
4036            }
4037
4038            if let Err(ix) = this.followers.binary_search(&follower) {
4039                this.followers.insert(ix, follower);
4040            }
4041
4042            Ok(response)
4043        })?
4044    }
4045
4046    async fn handle_unfollow(
4047        model: Model<Self>,
4048        envelope: TypedEnvelope<proto::Unfollow>,
4049        _: Arc<Client>,
4050        mut cx: AsyncAppContext,
4051    ) -> Result<()> {
4052        model.update(&mut cx, |this, _| {
4053            let follower = Follower {
4054                project_id: envelope.payload.project_id,
4055                peer_id: envelope.original_sender_id()?,
4056            };
4057            if let Ok(ix) = this.followers.binary_search(&follower) {
4058                this.followers.remove(ix);
4059            }
4060            Ok(())
4061        })?
4062    }
4063
4064    async fn handle_update_followers(
4065        this: Model<Self>,
4066        envelope: TypedEnvelope<proto::UpdateFollowers>,
4067        _: Arc<Client>,
4068        mut cx: AsyncWindowContext,
4069    ) -> Result<()> {
4070        let leader_id = envelope.original_sender_id()?;
4071        let update = envelope.payload;
4072
4073        this.update(&mut cx, |this, cx| {
4074            for workspace in &this.workspaces {
4075                workspace.update(cx, |workspace, cx| {
4076                    let project_id = workspace.project.read(cx).remote_id();
4077                    if update.project_id != project_id && update.project_id.is_some() {
4078                        return;
4079                    }
4080                    workspace.handle_update_followers(leader_id, update.clone(), cx);
4081                })?;
4082            }
4083            Ok(())
4084        })?
4085    }
4086}
4087
4088impl ViewId {
4089    pub(crate) fn from_proto(message: proto::ViewId) -> Result<Self> {
4090        Ok(Self {
4091            creator: message
4092                .creator
4093                .ok_or_else(|| anyhow!("creator is missing"))?,
4094            id: message.id,
4095        })
4096    }
4097
4098    pub(crate) fn to_proto(&self) -> proto::ViewId {
4099        proto::ViewId {
4100            creator: Some(self.creator),
4101            id: self.id,
4102        }
4103    }
4104}
4105
4106pub trait WorkspaceHandle {
4107    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath>;
4108}
4109
4110impl WorkspaceHandle for View<Workspace> {
4111    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath> {
4112        self.read(cx)
4113            .worktrees(cx)
4114            .flat_map(|worktree| {
4115                let worktree_id = worktree.read(cx).id();
4116                worktree.read(cx).files(true, 0).map(move |f| ProjectPath {
4117                    worktree_id,
4118                    path: f.path.clone(),
4119                })
4120            })
4121            .collect::<Vec<_>>()
4122    }
4123}
4124
4125// impl std::fmt::Debug for OpenPaths {
4126//     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
4127//         f.debug_struct("OpenPaths")
4128//             .field("paths", &self.paths)
4129//             .finish()
4130//     }
4131// }
4132
4133pub struct WorkspaceCreated(pub WeakView<Workspace>);
4134
4135pub fn activate_workspace_for_project(
4136    cx: &mut AppContext,
4137    predicate: impl Fn(&Project, &AppContext) -> bool + Send + 'static,
4138) -> Option<WindowHandle<Workspace>> {
4139    for window in cx.windows() {
4140        let Some(workspace) = window.downcast::<Workspace>() else {
4141            continue;
4142        };
4143
4144        let predicate = workspace
4145            .update(cx, |workspace, cx| {
4146                let project = workspace.project.read(cx);
4147                if predicate(project, cx) {
4148                    cx.activate_window();
4149                    true
4150                } else {
4151                    false
4152                }
4153            })
4154            .log_err()
4155            .unwrap_or(false);
4156
4157        if predicate {
4158            return Some(workspace);
4159        }
4160    }
4161
4162    None
4163}
4164
4165pub async fn last_opened_workspace_paths() -> Option<WorkspaceLocation> {
4166    DB.last_workspace().await.log_err().flatten()
4167}
4168
4169// async fn join_channel_internal(
4170//     channel_id: u64,
4171//     app_state: &Arc<AppState>,
4172//     requesting_window: Option<WindowHandle<Workspace>>,
4173//     active_call: &ModelHandle<ActiveCall>,
4174//     cx: &mut AsyncAppContext,
4175// ) -> Result<bool> {
4176//     let (should_prompt, open_room) = active_call.read_with(cx, |active_call, cx| {
4177//         let Some(room) = active_call.room().map(|room| room.read(cx)) else {
4178//             return (false, None);
4179//         };
4180
4181//         let already_in_channel = room.channel_id() == Some(channel_id);
4182//         let should_prompt = room.is_sharing_project()
4183//             && room.remote_participants().len() > 0
4184//             && !already_in_channel;
4185//         let open_room = if already_in_channel {
4186//             active_call.room().cloned()
4187//         } else {
4188//             None
4189//         };
4190//         (should_prompt, open_room)
4191//     });
4192
4193//     if let Some(room) = open_room {
4194//         let task = room.update(cx, |room, cx| {
4195//             if let Some((project, host)) = room.most_active_project(cx) {
4196//                 return Some(join_remote_project(project, host, app_state.clone(), cx));
4197//             }
4198
4199//             None
4200//         });
4201//         if let Some(task) = task {
4202//             task.await?;
4203//         }
4204//         return anyhow::Ok(true);
4205//     }
4206
4207//     if should_prompt {
4208//         if let Some(workspace) = requesting_window {
4209//             if let Some(window) = workspace.update(cx, |cx| cx.window()) {
4210//                 let answer = window.prompt(
4211//                     PromptLevel::Warning,
4212//                     "Leaving this call will unshare your current project.\nDo you want to switch channels?",
4213//                     &["Yes, Join Channel", "Cancel"],
4214//                     cx,
4215//                 );
4216
4217//                 if let Some(mut answer) = answer {
4218//                     if answer.next().await == Some(1) {
4219//                         return Ok(false);
4220//                     }
4221//                 }
4222//             } else {
4223//                 return Ok(false); // unreachable!() hopefully
4224//             }
4225//         } else {
4226//             return Ok(false); // unreachable!() hopefully
4227//         }
4228//     }
4229
4230//     let client = cx.read(|cx| active_call.read(cx).client());
4231
4232//     let mut client_status = client.status();
4233
4234//     // this loop will terminate within client::CONNECTION_TIMEOUT seconds.
4235//     'outer: loop {
4236//         let Some(status) = client_status.recv().await else {
4237//             return Err(anyhow!("error connecting"));
4238//         };
4239
4240//         match status {
4241//             Status::Connecting
4242//             | Status::Authenticating
4243//             | Status::Reconnecting
4244//             | Status::Reauthenticating => continue,
4245//             Status::Connected { .. } => break 'outer,
4246//             Status::SignedOut => return Err(anyhow!("not signed in")),
4247//             Status::UpgradeRequired => return Err(anyhow!("zed is out of date")),
4248//             Status::ConnectionError | Status::ConnectionLost | Status::ReconnectionError { .. } => {
4249//                 return Err(anyhow!("zed is offline"))
4250//             }
4251//         }
4252//     }
4253
4254//     let room = active_call
4255//         .update(cx, |active_call, cx| {
4256//             active_call.join_channel(channel_id, cx)
4257//         })
4258//         .await?;
4259
4260//     room.update(cx, |room, _| room.room_update_completed())
4261//         .await;
4262
4263//     let task = room.update(cx, |room, cx| {
4264//         if let Some((project, host)) = room.most_active_project(cx) {
4265//             return Some(join_remote_project(project, host, app_state.clone(), cx));
4266//         }
4267
4268//         None
4269//     });
4270//     if let Some(task) = task {
4271//         task.await?;
4272//         return anyhow::Ok(true);
4273//     }
4274//     anyhow::Ok(false)
4275// }
4276
4277// pub fn join_channel(
4278//     channel_id: u64,
4279//     app_state: Arc<AppState>,
4280//     requesting_window: Option<WindowHandle<Workspace>>,
4281//     cx: &mut AppContext,
4282// ) -> Task<Result<()>> {
4283//     let active_call = ActiveCall::global(cx);
4284//     cx.spawn(|mut cx| async move {
4285//         let result = join_channel_internal(
4286//             channel_id,
4287//             &app_state,
4288//             requesting_window,
4289//             &active_call,
4290//             &mut cx,
4291//         )
4292//         .await;
4293
4294//         // join channel succeeded, and opened a window
4295//         if matches!(result, Ok(true)) {
4296//             return anyhow::Ok(());
4297//         }
4298
4299//         if requesting_window.is_some() {
4300//             return anyhow::Ok(());
4301//         }
4302
4303//         // find an existing workspace to focus and show call controls
4304//         let mut active_window = activate_any_workspace_window(&mut cx);
4305//         if active_window.is_none() {
4306//             // no open workspaces, make one to show the error in (blergh)
4307//             cx.update(|cx| Workspace::new_local(vec![], app_state.clone(), requesting_window, cx))
4308//                 .await;
4309//         }
4310
4311//         active_window = activate_any_workspace_window(&mut cx);
4312//         if active_window.is_none() {
4313//             return result.map(|_| ()); // unreachable!() assuming new_local always opens a window
4314//         }
4315
4316//         if let Err(err) = result {
4317//             let prompt = active_window.unwrap().prompt(
4318//                 PromptLevel::Critical,
4319//                 &format!("Failed to join channel: {}", err),
4320//                 &["Ok"],
4321//                 &mut cx,
4322//             );
4323//             if let Some(mut prompt) = prompt {
4324//                 prompt.next().await;
4325//             } else {
4326//                 return Err(err);
4327//             }
4328//         }
4329
4330//         // return ok, we showed the error to the user.
4331//         return anyhow::Ok(());
4332//     })
4333// }
4334
4335// pub fn activate_any_workspace_window(cx: &mut AsyncAppContext) -> Option<AnyWindowHandle> {
4336//     for window in cx.windows() {
4337//         let found = window.update(cx, |cx| {
4338//             let is_workspace = cx.root_view().clone().downcast::<Workspace>().is_some();
4339//             if is_workspace {
4340//                 cx.activate_window();
4341//             }
4342//             is_workspace
4343//         });
4344//         if found == Some(true) {
4345//             return Some(window);
4346//         }
4347//     }
4348//     None
4349// }
4350
4351#[allow(clippy::type_complexity)]
4352pub fn open_paths(
4353    abs_paths: &[PathBuf],
4354    app_state: &Arc<AppState>,
4355    requesting_window: Option<WindowHandle<Workspace>>,
4356    cx: &mut AppContext,
4357) -> Task<
4358    anyhow::Result<(
4359        WindowHandle<Workspace>,
4360        Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
4361    )>,
4362> {
4363    let app_state = app_state.clone();
4364    let abs_paths = abs_paths.to_vec();
4365    // Open paths in existing workspace if possible
4366    let existing = activate_workspace_for_project(cx, {
4367        let abs_paths = abs_paths.clone();
4368        move |project, cx| project.contains_paths(&abs_paths, cx)
4369    });
4370    cx.spawn(move |mut cx| async move {
4371        if let Some(existing) = existing {
4372            // // Ok((
4373            //     existing.clone(),
4374            //     cx.update_window_root(&existing, |workspace, cx| {
4375            //         workspace.open_paths(abs_paths, true, cx)
4376            //     })?
4377            //     .await,
4378            // ))
4379            todo!()
4380        } else {
4381            cx.update(move |cx| {
4382                Workspace::new_local(abs_paths, app_state.clone(), requesting_window, cx)
4383            })?
4384            .await
4385        }
4386    })
4387}
4388
4389pub fn open_new(
4390    app_state: &Arc<AppState>,
4391    cx: &mut AppContext,
4392    init: impl FnOnce(&mut Workspace, &mut ViewContext<Workspace>) + 'static + Send,
4393) -> Task<()> {
4394    let task = Workspace::new_local(Vec::new(), app_state.clone(), None, cx);
4395    cx.spawn(|mut cx| async move {
4396        if let Some((workspace, opened_paths)) = task.await.log_err() {
4397            workspace
4398                .update(&mut cx, |workspace, cx| {
4399                    if opened_paths.is_empty() {
4400                        init(workspace, cx)
4401                    }
4402                })
4403                .log_err();
4404        }
4405    })
4406}
4407
4408pub fn create_and_open_local_file(
4409    path: &'static Path,
4410    cx: &mut ViewContext<Workspace>,
4411    default_content: impl 'static + Send + FnOnce() -> Rope,
4412) -> Task<Result<Box<dyn ItemHandle>>> {
4413    cx.spawn(|workspace, mut cx| async move {
4414        let fs = workspace.update(&mut cx, |workspace, _| workspace.app_state().fs.clone())?;
4415        if !fs.is_file(path).await {
4416            fs.create_file(path, Default::default()).await?;
4417            fs.save(path, &default_content(), Default::default())
4418                .await?;
4419        }
4420
4421        let mut items = workspace
4422            .update(&mut cx, |workspace, cx| {
4423                workspace.with_local_workspace(cx, |workspace, cx| {
4424                    workspace.open_paths(vec![path.to_path_buf()], false, cx)
4425                })
4426            })?
4427            .await?
4428            .await;
4429
4430        let item = items.pop().flatten();
4431        item.ok_or_else(|| anyhow!("path {path:?} is not a file"))?
4432    })
4433}
4434
4435// pub fn join_remote_project(
4436//     project_id: u64,
4437//     follow_user_id: u64,
4438//     app_state: Arc<AppState>,
4439//     cx: &mut AppContext,
4440// ) -> Task<Result<()>> {
4441//     cx.spawn(|mut cx| async move {
4442//         let windows = cx.windows();
4443//         let existing_workspace = windows.into_iter().find_map(|window| {
4444//             window.downcast::<Workspace>().and_then(|window| {
4445//                 window
4446//                     .read_root_with(&cx, |workspace, cx| {
4447//                         if workspace.project().read(cx).remote_id() == Some(project_id) {
4448//                             Some(cx.handle().downgrade())
4449//                         } else {
4450//                             None
4451//                         }
4452//                     })
4453//                     .unwrap_or(None)
4454//             })
4455//         });
4456
4457//         let workspace = if let Some(existing_workspace) = existing_workspace {
4458//             existing_workspace
4459//         } else {
4460//             let active_call = cx.read(ActiveCall::global);
4461//             let room = active_call
4462//                 .read_with(&cx, |call, _| call.room().cloned())
4463//                 .ok_or_else(|| anyhow!("not in a call"))?;
4464//             let project = room
4465//                 .update(&mut cx, |room, cx| {
4466//                     room.join_project(
4467//                         project_id,
4468//                         app_state.languages.clone(),
4469//                         app_state.fs.clone(),
4470//                         cx,
4471//                     )
4472//                 })
4473//                 .await?;
4474
4475//             let window_bounds_override = window_bounds_env_override(&cx);
4476//             let window = cx.add_window(
4477//                 (app_state.build_window_options)(
4478//                     window_bounds_override,
4479//                     None,
4480//                     cx.platform().as_ref(),
4481//                 ),
4482//                 |cx| Workspace::new(0, project, app_state.clone(), cx),
4483//             );
4484//             let workspace = window.root(&cx).unwrap();
4485//             (app_state.initialize_workspace)(
4486//                 workspace.downgrade(),
4487//                 false,
4488//                 app_state.clone(),
4489//                 cx.clone(),
4490//             )
4491//             .await
4492//             .log_err();
4493
4494//             workspace.downgrade()
4495//         };
4496
4497//         workspace.window().activate(&mut cx);
4498//         cx.platform().activate(true);
4499
4500//         workspace.update(&mut cx, |workspace, cx| {
4501//             if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
4502//                 let follow_peer_id = room
4503//                     .read(cx)
4504//                     .remote_participants()
4505//                     .iter()
4506//                     .find(|(_, participant)| participant.user.id == follow_user_id)
4507//                     .map(|(_, p)| p.peer_id)
4508//                     .or_else(|| {
4509//                         // If we couldn't follow the given user, follow the host instead.
4510//                         let collaborator = workspace
4511//                             .project()
4512//                             .read(cx)
4513//                             .collaborators()
4514//                             .values()
4515//                             .find(|collaborator| collaborator.replica_id == 0)?;
4516//                         Some(collaborator.peer_id)
4517//                     });
4518
4519//                 if let Some(follow_peer_id) = follow_peer_id {
4520//                     workspace
4521//                         .follow(follow_peer_id, cx)
4522//                         .map(|follow| follow.detach_and_log_err(cx));
4523//                 }
4524//             }
4525//         })?;
4526
4527//         anyhow::Ok(())
4528//     })
4529// }
4530
4531// pub fn restart(_: &Restart, cx: &mut AppContext) {
4532//     let should_confirm = settings::get::<WorkspaceSettings>(cx).confirm_quit;
4533//     cx.spawn(|mut cx| async move {
4534//         let mut workspace_windows = cx
4535//             .windows()
4536//             .into_iter()
4537//             .filter_map(|window| window.downcast::<Workspace>())
4538//             .collect::<Vec<_>>();
4539
4540//         // If multiple windows have unsaved changes, and need a save prompt,
4541//         // prompt in the active window before switching to a different window.
4542//         workspace_windows.sort_by_key(|window| window.is_active(&cx) == Some(false));
4543
4544//         if let (true, Some(window)) = (should_confirm, workspace_windows.first()) {
4545//             let answer = window.prompt(
4546//                 PromptLevel::Info,
4547//                 "Are you sure you want to restart?",
4548//                 &["Restart", "Cancel"],
4549//                 &mut cx,
4550//             );
4551
4552//             if let Some(mut answer) = answer {
4553//                 let answer = answer.next().await;
4554//                 if answer != Some(0) {
4555//                     return Ok(());
4556//                 }
4557//             }
4558//         }
4559
4560//         // If the user cancels any save prompt, then keep the app open.
4561//         for window in workspace_windows {
4562//             if let Some(should_close) = window.update_root(&mut cx, |workspace, cx| {
4563//                 workspace.prepare_to_close(true, cx)
4564//             }) {
4565//                 if !should_close.await? {
4566//                     return Ok(());
4567//                 }
4568//             }
4569//         }
4570//         cx.platform().restart();
4571//         anyhow::Ok(())
4572//     })
4573//     .detach_and_log_err(cx);
4574// }
4575
4576fn parse_pixel_position_env_var(value: &str) -> Option<Point<GlobalPixels>> {
4577    let mut parts = value.split(',');
4578    let x: usize = parts.next()?.parse().ok()?;
4579    let y: usize = parts.next()?.parse().ok()?;
4580    Some(point((x as f64).into(), (y as f64).into()))
4581}
4582
4583fn parse_pixel_size_env_var(value: &str) -> Option<Size<GlobalPixels>> {
4584    let mut parts = value.split(',');
4585    let width: usize = parts.next()?.parse().ok()?;
4586    let height: usize = parts.next()?.parse().ok()?;
4587    Some(size((width as f64).into(), (height as f64).into()))
4588}
4589
4590// #[cfg(test)]
4591// mod tests {
4592//     use super::*;
4593//     use crate::{
4594//         dock::test::{TestPanel, TestPanelEvent},
4595//         item::test::{TestItem, TestItemEvent, TestProjectItem},
4596//     };
4597//     use fs::FakeFs;
4598//     use gpui::{executor::Deterministic, test::EmptyView, TestAppContext};
4599//     use project::{Project, ProjectEntryId};
4600//     use serde_json::json;
4601//     use settings::SettingsStore;
4602//     use std::{cell::RefCell, rc::Rc};
4603
4604//     #[gpui::test]
4605//     async fn test_tab_disambiguation(cx: &mut TestAppContext) {
4606//         init_test(cx);
4607
4608//         let fs = FakeFs::new(cx.background());
4609//         let project = Project::test(fs, [], cx).await;
4610//         let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
4611//         let workspace = window.root(cx);
4612
4613//         // Adding an item with no ambiguity renders the tab without detail.
4614//         let item1 = window.build_view(cx, |_| {
4615//             let mut item = TestItem::new();
4616//             item.tab_descriptions = Some(vec!["c", "b1/c", "a/b1/c"]);
4617//             item
4618//         });
4619//         workspace.update(cx, |workspace, cx| {
4620//             workspace.add_item(Box::new(item1.clone()), cx);
4621//         });
4622//         item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), None));
4623
4624//         // Adding an item that creates ambiguity increases the level of detail on
4625//         // both tabs.
4626//         let item2 = window.build_view(cx, |_| {
4627//             let mut item = TestItem::new();
4628//             item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
4629//             item
4630//         });
4631//         workspace.update(cx, |workspace, cx| {
4632//             workspace.add_item(Box::new(item2.clone()), cx);
4633//         });
4634//         item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4635//         item2.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4636
4637//         // Adding an item that creates ambiguity increases the level of detail only
4638//         // on the ambiguous tabs. In this case, the ambiguity can't be resolved so
4639//         // we stop at the highest detail available.
4640//         let item3 = window.build_view(cx, |_| {
4641//             let mut item = TestItem::new();
4642//             item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
4643//             item
4644//         });
4645//         workspace.update(cx, |workspace, cx| {
4646//             workspace.add_item(Box::new(item3.clone()), cx);
4647//         });
4648//         item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4649//         item2.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
4650//         item3.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
4651//     }
4652
4653//     #[gpui::test]
4654//     async fn test_tracking_active_path(cx: &mut TestAppContext) {
4655//         init_test(cx);
4656
4657//         let fs = FakeFs::new(cx.background());
4658//         fs.insert_tree(
4659//             "/root1",
4660//             json!({
4661//                 "one.txt": "",
4662//                 "two.txt": "",
4663//             }),
4664//         )
4665//         .await;
4666//         fs.insert_tree(
4667//             "/root2",
4668//             json!({
4669//                 "three.txt": "",
4670//             }),
4671//         )
4672//         .await;
4673
4674//         let project = Project::test(fs, ["root1".as_ref()], cx).await;
4675//         let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
4676//         let workspace = window.root(cx);
4677//         let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
4678//         let worktree_id = project.read_with(cx, |project, cx| {
4679//             project.worktrees(cx).next().unwrap().read(cx).id()
4680//         });
4681
4682//         let item1 = window.build_view(cx, |cx| {
4683//             TestItem::new().with_project_items(&[TestProjectItem::new(1, "one.txt", cx)])
4684//         });
4685//         let item2 = window.build_view(cx, |cx| {
4686//             TestItem::new().with_project_items(&[TestProjectItem::new(2, "two.txt", cx)])
4687//         });
4688
4689//         // Add an item to an empty pane
4690//         workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item1), cx));
4691//         project.read_with(cx, |project, cx| {
4692//             assert_eq!(
4693//                 project.active_entry(),
4694//                 project
4695//                     .entry_for_path(&(worktree_id, "one.txt").into(), cx)
4696//                     .map(|e| e.id)
4697//             );
4698//         });
4699//         assert_eq!(window.current_title(cx).as_deref(), Some("one.txt β€” root1"));
4700
4701//         // Add a second item to a non-empty pane
4702//         workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item2), cx));
4703//         assert_eq!(window.current_title(cx).as_deref(), Some("two.txt β€” root1"));
4704//         project.read_with(cx, |project, cx| {
4705//             assert_eq!(
4706//                 project.active_entry(),
4707//                 project
4708//                     .entry_for_path(&(worktree_id, "two.txt").into(), cx)
4709//                     .map(|e| e.id)
4710//             );
4711//         });
4712
4713//         // Close the active item
4714//         pane.update(cx, |pane, cx| {
4715//             pane.close_active_item(&Default::default(), cx).unwrap()
4716//         })
4717//         .await
4718//         .unwrap();
4719//         assert_eq!(window.current_title(cx).as_deref(), Some("one.txt β€” root1"));
4720//         project.read_with(cx, |project, cx| {
4721//             assert_eq!(
4722//                 project.active_entry(),
4723//                 project
4724//                     .entry_for_path(&(worktree_id, "one.txt").into(), cx)
4725//                     .map(|e| e.id)
4726//             );
4727//         });
4728
4729//         // Add a project folder
4730//         project
4731//             .update(cx, |project, cx| {
4732//                 project.find_or_create_local_worktree("/root2", true, cx)
4733//             })
4734//             .await
4735//             .unwrap();
4736//         assert_eq!(
4737//             window.current_title(cx).as_deref(),
4738//             Some("one.txt β€” root1, root2")
4739//         );
4740
4741//         // Remove a project folder
4742//         project.update(cx, |project, cx| project.remove_worktree(worktree_id, cx));
4743//         assert_eq!(window.current_title(cx).as_deref(), Some("one.txt β€” root2"));
4744//     }
4745
4746//     #[gpui::test]
4747//     async fn test_close_window(cx: &mut TestAppContext) {
4748//         init_test(cx);
4749
4750//         let fs = FakeFs::new(cx.background());
4751//         fs.insert_tree("/root", json!({ "one": "" })).await;
4752
4753//         let project = Project::test(fs, ["root".as_ref()], cx).await;
4754//         let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
4755//         let workspace = window.root(cx);
4756
4757//         // When there are no dirty items, there's nothing to do.
4758//         let item1 = window.build_view(cx, |_| TestItem::new());
4759//         workspace.update(cx, |w, cx| w.add_item(Box::new(item1.clone()), cx));
4760//         let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
4761//         assert!(task.await.unwrap());
4762
4763//         // When there are dirty untitled items, prompt to save each one. If the user
4764//         // cancels any prompt, then abort.
4765//         let item2 = window.build_view(cx, |_| TestItem::new().with_dirty(true));
4766//         let item3 = window.build_view(cx, |cx| {
4767//             TestItem::new()
4768//                 .with_dirty(true)
4769//                 .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4770//         });
4771//         workspace.update(cx, |w, cx| {
4772//             w.add_item(Box::new(item2.clone()), cx);
4773//             w.add_item(Box::new(item3.clone()), cx);
4774//         });
4775//         let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
4776//         cx.foreground().run_until_parked();
4777//         window.simulate_prompt_answer(2, cx); // cancel save all
4778//         cx.foreground().run_until_parked();
4779//         window.simulate_prompt_answer(2, cx); // cancel save all
4780//         cx.foreground().run_until_parked();
4781//         assert!(!window.has_pending_prompt(cx));
4782//         assert!(!task.await.unwrap());
4783//     }
4784
4785//     #[gpui::test]
4786//     async fn test_close_pane_items(cx: &mut TestAppContext) {
4787//         init_test(cx);
4788
4789//         let fs = FakeFs::new(cx.background());
4790
4791//         let project = Project::test(fs, None, cx).await;
4792//         let window = cx.add_window(|cx| Workspace::test_new(project, cx));
4793//         let workspace = window.root(cx);
4794
4795//         let item1 = window.build_view(cx, |cx| {
4796//             TestItem::new()
4797//                 .with_dirty(true)
4798//                 .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4799//         });
4800//         let item2 = window.build_view(cx, |cx| {
4801//             TestItem::new()
4802//                 .with_dirty(true)
4803//                 .with_conflict(true)
4804//                 .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)])
4805//         });
4806//         let item3 = window.build_view(cx, |cx| {
4807//             TestItem::new()
4808//                 .with_dirty(true)
4809//                 .with_conflict(true)
4810//                 .with_project_items(&[TestProjectItem::new(3, "3.txt", cx)])
4811//         });
4812//         let item4 = window.build_view(cx, |cx| {
4813//             TestItem::new()
4814//                 .with_dirty(true)
4815//                 .with_project_items(&[TestProjectItem::new_untitled(cx)])
4816//         });
4817//         let pane = workspace.update(cx, |workspace, cx| {
4818//             workspace.add_item(Box::new(item1.clone()), cx);
4819//             workspace.add_item(Box::new(item2.clone()), cx);
4820//             workspace.add_item(Box::new(item3.clone()), cx);
4821//             workspace.add_item(Box::new(item4.clone()), cx);
4822//             workspace.active_pane().clone()
4823//         });
4824
4825//         let close_items = pane.update(cx, |pane, cx| {
4826//             pane.activate_item(1, true, true, cx);
4827//             assert_eq!(pane.active_item().unwrap().id(), item2.id());
4828//             let item1_id = item1.id();
4829//             let item3_id = item3.id();
4830//             let item4_id = item4.id();
4831//             pane.close_items(cx, SaveIntent::Close, move |id| {
4832//                 [item1_id, item3_id, item4_id].contains(&id)
4833//             })
4834//         });
4835//         cx.foreground().run_until_parked();
4836
4837//         assert!(window.has_pending_prompt(cx));
4838//         // Ignore "Save all" prompt
4839//         window.simulate_prompt_answer(2, cx);
4840//         cx.foreground().run_until_parked();
4841//         // There's a prompt to save item 1.
4842//         pane.read_with(cx, |pane, _| {
4843//             assert_eq!(pane.items_len(), 4);
4844//             assert_eq!(pane.active_item().unwrap().id(), item1.id());
4845//         });
4846//         // Confirm saving item 1.
4847//         window.simulate_prompt_answer(0, cx);
4848//         cx.foreground().run_until_parked();
4849
4850//         // Item 1 is saved. There's a prompt to save item 3.
4851//         pane.read_with(cx, |pane, cx| {
4852//             assert_eq!(item1.read(cx).save_count, 1);
4853//             assert_eq!(item1.read(cx).save_as_count, 0);
4854//             assert_eq!(item1.read(cx).reload_count, 0);
4855//             assert_eq!(pane.items_len(), 3);
4856//             assert_eq!(pane.active_item().unwrap().id(), item3.id());
4857//         });
4858//         assert!(window.has_pending_prompt(cx));
4859
4860//         // Cancel saving item 3.
4861//         window.simulate_prompt_answer(1, cx);
4862//         cx.foreground().run_until_parked();
4863
4864//         // Item 3 is reloaded. There's a prompt to save item 4.
4865//         pane.read_with(cx, |pane, cx| {
4866//             assert_eq!(item3.read(cx).save_count, 0);
4867//             assert_eq!(item3.read(cx).save_as_count, 0);
4868//             assert_eq!(item3.read(cx).reload_count, 1);
4869//             assert_eq!(pane.items_len(), 2);
4870//             assert_eq!(pane.active_item().unwrap().id(), item4.id());
4871//         });
4872//         assert!(window.has_pending_prompt(cx));
4873
4874//         // Confirm saving item 4.
4875//         window.simulate_prompt_answer(0, cx);
4876//         cx.foreground().run_until_parked();
4877
4878//         // There's a prompt for a path for item 4.
4879//         cx.simulate_new_path_selection(|_| Some(Default::default()));
4880//         close_items.await.unwrap();
4881
4882//         // The requested items are closed.
4883//         pane.read_with(cx, |pane, cx| {
4884//             assert_eq!(item4.read(cx).save_count, 0);
4885//             assert_eq!(item4.read(cx).save_as_count, 1);
4886//             assert_eq!(item4.read(cx).reload_count, 0);
4887//             assert_eq!(pane.items_len(), 1);
4888//             assert_eq!(pane.active_item().unwrap().id(), item2.id());
4889//         });
4890//     }
4891
4892//     #[gpui::test]
4893//     async fn test_prompting_to_save_only_on_last_item_for_entry(cx: &mut TestAppContext) {
4894//         init_test(cx);
4895
4896//         let fs = FakeFs::new(cx.background());
4897
4898//         let project = Project::test(fs, [], cx).await;
4899//         let window = cx.add_window(|cx| Workspace::test_new(project, cx));
4900//         let workspace = window.root(cx);
4901
4902//         // Create several workspace items with single project entries, and two
4903//         // workspace items with multiple project entries.
4904//         let single_entry_items = (0..=4)
4905//             .map(|project_entry_id| {
4906//                 window.build_view(cx, |cx| {
4907//                     TestItem::new()
4908//                         .with_dirty(true)
4909//                         .with_project_items(&[TestProjectItem::new(
4910//                             project_entry_id,
4911//                             &format!("{project_entry_id}.txt"),
4912//                             cx,
4913//                         )])
4914//                 })
4915//             })
4916//             .collect::<Vec<_>>();
4917//         let item_2_3 = window.build_view(cx, |cx| {
4918//             TestItem::new()
4919//                 .with_dirty(true)
4920//                 .with_singleton(false)
4921//                 .with_project_items(&[
4922//                     single_entry_items[2].read(cx).project_items[0].clone(),
4923//                     single_entry_items[3].read(cx).project_items[0].clone(),
4924//                 ])
4925//         });
4926//         let item_3_4 = window.build_view(cx, |cx| {
4927//             TestItem::new()
4928//                 .with_dirty(true)
4929//                 .with_singleton(false)
4930//                 .with_project_items(&[
4931//                     single_entry_items[3].read(cx).project_items[0].clone(),
4932//                     single_entry_items[4].read(cx).project_items[0].clone(),
4933//                 ])
4934//         });
4935
4936//         // Create two panes that contain the following project entries:
4937//         //   left pane:
4938//         //     multi-entry items:   (2, 3)
4939//         //     single-entry items:  0, 1, 2, 3, 4
4940//         //   right pane:
4941//         //     single-entry items:  1
4942//         //     multi-entry items:   (3, 4)
4943//         let left_pane = workspace.update(cx, |workspace, cx| {
4944//             let left_pane = workspace.active_pane().clone();
4945//             workspace.add_item(Box::new(item_2_3.clone()), cx);
4946//             for item in single_entry_items {
4947//                 workspace.add_item(Box::new(item), cx);
4948//             }
4949//             left_pane.update(cx, |pane, cx| {
4950//                 pane.activate_item(2, true, true, cx);
4951//             });
4952
4953//             workspace
4954//                 .split_and_clone(left_pane.clone(), SplitDirection::Right, cx)
4955//                 .unwrap();
4956
4957//             left_pane
4958//         });
4959
4960//         //Need to cause an effect flush in order to respect new focus
4961//         workspace.update(cx, |workspace, cx| {
4962//             workspace.add_item(Box::new(item_3_4.clone()), cx);
4963//             cx.focus(&left_pane);
4964//         });
4965
4966//         // When closing all of the items in the left pane, we should be prompted twice:
4967//         // once for project entry 0, and once for project entry 2. After those two
4968//         // prompts, the task should complete.
4969
4970//         let close = left_pane.update(cx, |pane, cx| {
4971//             pane.close_items(cx, SaveIntent::Close, move |_| true)
4972//         });
4973//         cx.foreground().run_until_parked();
4974//         // Discard "Save all" prompt
4975//         window.simulate_prompt_answer(2, cx);
4976
4977//         cx.foreground().run_until_parked();
4978//         left_pane.read_with(cx, |pane, cx| {
4979//             assert_eq!(
4980//                 pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
4981//                 &[ProjectEntryId::from_proto(0)]
4982//             );
4983//         });
4984//         window.simulate_prompt_answer(0, cx);
4985
4986//         cx.foreground().run_until_parked();
4987//         left_pane.read_with(cx, |pane, cx| {
4988//             assert_eq!(
4989//                 pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
4990//                 &[ProjectEntryId::from_proto(2)]
4991//             );
4992//         });
4993//         window.simulate_prompt_answer(0, cx);
4994
4995//         cx.foreground().run_until_parked();
4996//         close.await.unwrap();
4997//         left_pane.read_with(cx, |pane, _| {
4998//             assert_eq!(pane.items_len(), 0);
4999//         });
5000//     }
5001
5002//     #[gpui::test]
5003//     async fn test_autosave(deterministic: Arc<Deterministic>, cx: &mut gpui::TestAppContext) {
5004//         init_test(cx);
5005
5006//         let fs = FakeFs::new(cx.background());
5007
5008//         let project = Project::test(fs, [], cx).await;
5009//         let window = cx.add_window(|cx| Workspace::test_new(project, cx));
5010//         let workspace = window.root(cx);
5011//         let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
5012
5013//         let item = window.build_view(cx, |cx| {
5014//             TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
5015//         });
5016//         let item_id = item.id();
5017//         workspace.update(cx, |workspace, cx| {
5018//             workspace.add_item(Box::new(item.clone()), cx);
5019//         });
5020
5021//         // Autosave on window change.
5022//         item.update(cx, |item, cx| {
5023//             cx.update_global(|settings: &mut SettingsStore, cx| {
5024//                 settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
5025//                     settings.autosave = Some(AutosaveSetting::OnWindowChange);
5026//                 })
5027//             });
5028//             item.is_dirty = true;
5029//         });
5030
5031//         // Deactivating the window saves the file.
5032//         window.simulate_deactivation(cx);
5033//         deterministic.run_until_parked();
5034//         item.read_with(cx, |item, _| assert_eq!(item.save_count, 1));
5035
5036//         // Autosave on focus change.
5037//         item.update(cx, |item, cx| {
5038//             cx.focus_self();
5039//             cx.update_global(|settings: &mut SettingsStore, cx| {
5040//                 settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
5041//                     settings.autosave = Some(AutosaveSetting::OnFocusChange);
5042//                 })
5043//             });
5044//             item.is_dirty = true;
5045//         });
5046
5047//         // Blurring the item saves the file.
5048//         item.update(cx, |_, cx| cx.blur());
5049//         deterministic.run_until_parked();
5050//         item.read_with(cx, |item, _| assert_eq!(item.save_count, 2));
5051
5052//         // Deactivating the window still saves the file.
5053//         window.simulate_activation(cx);
5054//         item.update(cx, |item, cx| {
5055//             cx.focus_self();
5056//             item.is_dirty = true;
5057//         });
5058//         window.simulate_deactivation(cx);
5059
5060//         deterministic.run_until_parked();
5061//         item.read_with(cx, |item, _| assert_eq!(item.save_count, 3));
5062
5063//         // Autosave after delay.
5064//         item.update(cx, |item, cx| {
5065//             cx.update_global(|settings: &mut SettingsStore, cx| {
5066//                 settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
5067//                     settings.autosave = Some(AutosaveSetting::AfterDelay { milliseconds: 500 });
5068//                 })
5069//             });
5070//             item.is_dirty = true;
5071//             cx.emit(TestItemEvent::Edit);
5072//         });
5073
5074//         // Delay hasn't fully expired, so the file is still dirty and unsaved.
5075//         deterministic.advance_clock(Duration::from_millis(250));
5076//         item.read_with(cx, |item, _| assert_eq!(item.save_count, 3));
5077
5078//         // After delay expires, the file is saved.
5079//         deterministic.advance_clock(Duration::from_millis(250));
5080//         item.read_with(cx, |item, _| assert_eq!(item.save_count, 4));
5081
5082//         // Autosave on focus change, ensuring closing the tab counts as such.
5083//         item.update(cx, |item, cx| {
5084//             cx.update_global(|settings: &mut SettingsStore, cx| {
5085//                 settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
5086//                     settings.autosave = Some(AutosaveSetting::OnFocusChange);
5087//                 })
5088//             });
5089//             item.is_dirty = true;
5090//         });
5091
5092//         pane.update(cx, |pane, cx| {
5093//             pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
5094//         })
5095//         .await
5096//         .unwrap();
5097//         assert!(!window.has_pending_prompt(cx));
5098//         item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
5099
5100//         // Add the item again, ensuring autosave is prevented if the underlying file has been deleted.
5101//         workspace.update(cx, |workspace, cx| {
5102//             workspace.add_item(Box::new(item.clone()), cx);
5103//         });
5104//         item.update(cx, |item, cx| {
5105//             item.project_items[0].update(cx, |item, _| {
5106//                 item.entry_id = None;
5107//             });
5108//             item.is_dirty = true;
5109//             cx.blur();
5110//         });
5111//         deterministic.run_until_parked();
5112//         item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
5113
5114//         // Ensure autosave is prevented for deleted files also when closing the buffer.
5115//         let _close_items = pane.update(cx, |pane, cx| {
5116//             pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
5117//         });
5118//         deterministic.run_until_parked();
5119//         assert!(window.has_pending_prompt(cx));
5120//         item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
5121//     }
5122
5123//     #[gpui::test]
5124//     async fn test_pane_navigation(cx: &mut gpui::TestAppContext) {
5125//         init_test(cx);
5126
5127//         let fs = FakeFs::new(cx.background());
5128
5129//         let project = Project::test(fs, [], cx).await;
5130//         let window = cx.add_window(|cx| Workspace::test_new(project, cx));
5131//         let workspace = window.root(cx);
5132
5133//         let item = window.build_view(cx, |cx| {
5134//             TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
5135//         });
5136//         let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
5137//         let toolbar = pane.read_with(cx, |pane, _| pane.toolbar().clone());
5138//         let toolbar_notify_count = Rc::new(RefCell::new(0));
5139
5140//         workspace.update(cx, |workspace, cx| {
5141//             workspace.add_item(Box::new(item.clone()), cx);
5142//             let toolbar_notification_count = toolbar_notify_count.clone();
5143//             cx.observe(&toolbar, move |_, _, _| {
5144//                 *toolbar_notification_count.borrow_mut() += 1
5145//             })
5146//             .detach();
5147//         });
5148
5149//         pane.read_with(cx, |pane, _| {
5150//             assert!(!pane.can_navigate_backward());
5151//             assert!(!pane.can_navigate_forward());
5152//         });
5153
5154//         item.update(cx, |item, cx| {
5155//             item.set_state("one".to_string(), cx);
5156//         });
5157
5158//         // Toolbar must be notified to re-render the navigation buttons
5159//         assert_eq!(*toolbar_notify_count.borrow(), 1);
5160
5161//         pane.read_with(cx, |pane, _| {
5162//             assert!(pane.can_navigate_backward());
5163//             assert!(!pane.can_navigate_forward());
5164//         });
5165
5166//         workspace
5167//             .update(cx, |workspace, cx| workspace.go_back(pane.downgrade(), cx))
5168//             .await
5169//             .unwrap();
5170
5171//         assert_eq!(*toolbar_notify_count.borrow(), 3);
5172//         pane.read_with(cx, |pane, _| {
5173//             assert!(!pane.can_navigate_backward());
5174//             assert!(pane.can_navigate_forward());
5175//         });
5176//     }
5177
5178//     #[gpui::test]
5179//     async fn test_toggle_docks_and_panels(cx: &mut gpui::TestAppContext) {
5180//         init_test(cx);
5181//         let fs = FakeFs::new(cx.background());
5182
5183//         let project = Project::test(fs, [], cx).await;
5184//         let window = cx.add_window(|cx| Workspace::test_new(project, cx));
5185//         let workspace = window.root(cx);
5186
5187//         let panel = workspace.update(cx, |workspace, cx| {
5188//             let panel = cx.build_view(|_| TestPanel::new(DockPosition::Right));
5189//             workspace.add_panel(panel.clone(), cx);
5190
5191//             workspace
5192//                 .right_dock()
5193//                 .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
5194
5195//             panel
5196//         });
5197
5198//         let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
5199//         pane.update(cx, |pane, cx| {
5200//             let item = cx.build_view(|_| TestItem::new());
5201//             pane.add_item(Box::new(item), true, true, None, cx);
5202//         });
5203
5204//         // Transfer focus from center to panel
5205//         workspace.update(cx, |workspace, cx| {
5206//             workspace.toggle_panel_focus::<TestPanel>(cx);
5207//         });
5208
5209//         workspace.read_with(cx, |workspace, cx| {
5210//             assert!(workspace.right_dock().read(cx).is_open());
5211//             assert!(!panel.is_zoomed(cx));
5212//             assert!(panel.has_focus(cx));
5213//         });
5214
5215//         // Transfer focus from panel to center
5216//         workspace.update(cx, |workspace, cx| {
5217//             workspace.toggle_panel_focus::<TestPanel>(cx);
5218//         });
5219
5220//         workspace.read_with(cx, |workspace, cx| {
5221//             assert!(workspace.right_dock().read(cx).is_open());
5222//             assert!(!panel.is_zoomed(cx));
5223//             assert!(!panel.has_focus(cx));
5224//         });
5225
5226//         // Close the dock
5227//         workspace.update(cx, |workspace, cx| {
5228//             workspace.toggle_dock(DockPosition::Right, cx);
5229//         });
5230
5231//         workspace.read_with(cx, |workspace, cx| {
5232//             assert!(!workspace.right_dock().read(cx).is_open());
5233//             assert!(!panel.is_zoomed(cx));
5234//             assert!(!panel.has_focus(cx));
5235//         });
5236
5237//         // Open the dock
5238//         workspace.update(cx, |workspace, cx| {
5239//             workspace.toggle_dock(DockPosition::Right, cx);
5240//         });
5241
5242//         workspace.read_with(cx, |workspace, cx| {
5243//             assert!(workspace.right_dock().read(cx).is_open());
5244//             assert!(!panel.is_zoomed(cx));
5245//             assert!(panel.has_focus(cx));
5246//         });
5247
5248//         // Focus and zoom panel
5249//         panel.update(cx, |panel, cx| {
5250//             cx.focus_self();
5251//             panel.set_zoomed(true, cx)
5252//         });
5253
5254//         workspace.read_with(cx, |workspace, cx| {
5255//             assert!(workspace.right_dock().read(cx).is_open());
5256//             assert!(panel.is_zoomed(cx));
5257//             assert!(panel.has_focus(cx));
5258//         });
5259
5260//         // Transfer focus to the center closes the dock
5261//         workspace.update(cx, |workspace, cx| {
5262//             workspace.toggle_panel_focus::<TestPanel>(cx);
5263//         });
5264
5265//         workspace.read_with(cx, |workspace, cx| {
5266//             assert!(!workspace.right_dock().read(cx).is_open());
5267//             assert!(panel.is_zoomed(cx));
5268//             assert!(!panel.has_focus(cx));
5269//         });
5270
5271//         // Transferring focus back to the panel keeps it zoomed
5272//         workspace.update(cx, |workspace, cx| {
5273//             workspace.toggle_panel_focus::<TestPanel>(cx);
5274//         });
5275
5276//         workspace.read_with(cx, |workspace, cx| {
5277//             assert!(workspace.right_dock().read(cx).is_open());
5278//             assert!(panel.is_zoomed(cx));
5279//             assert!(panel.has_focus(cx));
5280//         });
5281
5282//         // Close the dock while it is zoomed
5283//         workspace.update(cx, |workspace, cx| {
5284//             workspace.toggle_dock(DockPosition::Right, cx)
5285//         });
5286
5287//         workspace.read_with(cx, |workspace, cx| {
5288//             assert!(!workspace.right_dock().read(cx).is_open());
5289//             assert!(panel.is_zoomed(cx));
5290//             assert!(workspace.zoomed.is_none());
5291//             assert!(!panel.has_focus(cx));
5292//         });
5293
5294//         // Opening the dock, when it's zoomed, retains focus
5295//         workspace.update(cx, |workspace, cx| {
5296//             workspace.toggle_dock(DockPosition::Right, cx)
5297//         });
5298
5299//         workspace.read_with(cx, |workspace, cx| {
5300//             assert!(workspace.right_dock().read(cx).is_open());
5301//             assert!(panel.is_zoomed(cx));
5302//             assert!(workspace.zoomed.is_some());
5303//             assert!(panel.has_focus(cx));
5304//         });
5305
5306//         // Unzoom and close the panel, zoom the active pane.
5307//         panel.update(cx, |panel, cx| panel.set_zoomed(false, cx));
5308//         workspace.update(cx, |workspace, cx| {
5309//             workspace.toggle_dock(DockPosition::Right, cx)
5310//         });
5311//         pane.update(cx, |pane, cx| pane.toggle_zoom(&Default::default(), cx));
5312
5313//         // Opening a dock unzooms the pane.
5314//         workspace.update(cx, |workspace, cx| {
5315//             workspace.toggle_dock(DockPosition::Right, cx)
5316//         });
5317//         workspace.read_with(cx, |workspace, cx| {
5318//             let pane = pane.read(cx);
5319//             assert!(!pane.is_zoomed());
5320//             assert!(!pane.has_focus());
5321//             assert!(workspace.right_dock().read(cx).is_open());
5322//             assert!(workspace.zoomed.is_none());
5323//         });
5324//     }
5325
5326//     #[gpui::test]
5327//     async fn test_panels(cx: &mut gpui::TestAppContext) {
5328//         init_test(cx);
5329//         let fs = FakeFs::new(cx.background());
5330
5331//         let project = Project::test(fs, [], cx).await;
5332//         let window = cx.add_window(|cx| Workspace::test_new(project, cx));
5333//         let workspace = window.root(cx);
5334
5335//         let (panel_1, panel_2) = workspace.update(cx, |workspace, cx| {
5336//             // Add panel_1 on the left, panel_2 on the right.
5337//             let panel_1 = cx.build_view(|_| TestPanel::new(DockPosition::Left));
5338//             workspace.add_panel(panel_1.clone(), cx);
5339//             workspace
5340//                 .left_dock()
5341//                 .update(cx, |left_dock, cx| left_dock.set_open(true, cx));
5342//             let panel_2 = cx.build_view(|_| TestPanel::new(DockPosition::Right));
5343//             workspace.add_panel(panel_2.clone(), cx);
5344//             workspace
5345//                 .right_dock()
5346//                 .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
5347
5348//             let left_dock = workspace.left_dock();
5349//             assert_eq!(
5350//                 left_dock.read(cx).visible_panel().unwrap().id(),
5351//                 panel_1.id()
5352//             );
5353//             assert_eq!(
5354//                 left_dock.read(cx).active_panel_size(cx).unwrap(),
5355//                 panel_1.size(cx)
5356//             );
5357
5358//             left_dock.update(cx, |left_dock, cx| {
5359//                 left_dock.resize_active_panel(Some(1337.), cx)
5360//             });
5361//             assert_eq!(
5362//                 workspace
5363//                     .right_dock()
5364//                     .read(cx)
5365//                     .visible_panel()
5366//                     .unwrap()
5367//                     .id(),
5368//                 panel_2.id()
5369//             );
5370
5371//             (panel_1, panel_2)
5372//         });
5373
5374//         // Move panel_1 to the right
5375//         panel_1.update(cx, |panel_1, cx| {
5376//             panel_1.set_position(DockPosition::Right, cx)
5377//         });
5378
5379//         workspace.update(cx, |workspace, cx| {
5380//             // Since panel_1 was visible on the left, it should now be visible now that it's been moved to the right.
5381//             // Since it was the only panel on the left, the left dock should now be closed.
5382//             assert!(!workspace.left_dock().read(cx).is_open());
5383//             assert!(workspace.left_dock().read(cx).visible_panel().is_none());
5384//             let right_dock = workspace.right_dock();
5385//             assert_eq!(
5386//                 right_dock.read(cx).visible_panel().unwrap().id(),
5387//                 panel_1.id()
5388//             );
5389//             assert_eq!(right_dock.read(cx).active_panel_size(cx).unwrap(), 1337.);
5390
5391//             // Now we move panel_2Β to the left
5392//             panel_2.set_position(DockPosition::Left, cx);
5393//         });
5394
5395//         workspace.update(cx, |workspace, cx| {
5396//             // Since panel_2 was not visible on the right, we don't open the left dock.
5397//             assert!(!workspace.left_dock().read(cx).is_open());
5398//             // And the right dock is unaffected in it's displaying of panel_1
5399//             assert!(workspace.right_dock().read(cx).is_open());
5400//             assert_eq!(
5401//                 workspace
5402//                     .right_dock()
5403//                     .read(cx)
5404//                     .visible_panel()
5405//                     .unwrap()
5406//                     .id(),
5407//                 panel_1.id()
5408//             );
5409//         });
5410
5411//         // Move panel_1 back to the left
5412//         panel_1.update(cx, |panel_1, cx| {
5413//             panel_1.set_position(DockPosition::Left, cx)
5414//         });
5415
5416//         workspace.update(cx, |workspace, cx| {
5417//             // Since panel_1 was visible on the right, we open the left dock and make panel_1 active.
5418//             let left_dock = workspace.left_dock();
5419//             assert!(left_dock.read(cx).is_open());
5420//             assert_eq!(
5421//                 left_dock.read(cx).visible_panel().unwrap().id(),
5422//                 panel_1.id()
5423//             );
5424//             assert_eq!(left_dock.read(cx).active_panel_size(cx).unwrap(), 1337.);
5425//             // And right the dock should be closed as it no longer has any panels.
5426//             assert!(!workspace.right_dock().read(cx).is_open());
5427
5428//             // Now we move panel_1 to the bottom
5429//             panel_1.set_position(DockPosition::Bottom, cx);
5430//         });
5431
5432//         workspace.update(cx, |workspace, cx| {
5433//             // Since panel_1 was visible on the left, we close the left dock.
5434//             assert!(!workspace.left_dock().read(cx).is_open());
5435//             // The bottom dock is sized based on the panel's default size,
5436//             // since the panel orientation changed from vertical to horizontal.
5437//             let bottom_dock = workspace.bottom_dock();
5438//             assert_eq!(
5439//                 bottom_dock.read(cx).active_panel_size(cx).unwrap(),
5440//                 panel_1.size(cx),
5441//             );
5442//             // Close bottom dock and move panel_1 back to the left.
5443//             bottom_dock.update(cx, |bottom_dock, cx| bottom_dock.set_open(false, cx));
5444//             panel_1.set_position(DockPosition::Left, cx);
5445//         });
5446
5447//         // Emit activated event on panel 1
5448//         panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::Activated));
5449
5450//         // Now the left dock is open and panel_1 is active and focused.
5451//         workspace.read_with(cx, |workspace, cx| {
5452//             let left_dock = workspace.left_dock();
5453//             assert!(left_dock.read(cx).is_open());
5454//             assert_eq!(
5455//                 left_dock.read(cx).visible_panel().unwrap().id(),
5456//                 panel_1.id()
5457//             );
5458//             assert!(panel_1.is_focused(cx));
5459//         });
5460
5461//         // Emit closed event on panel 2, which is not active
5462//         panel_2.update(cx, |_, cx| cx.emit(TestPanelEvent::Closed));
5463
5464//         // Wo don't close the left dock, because panel_2 wasn't the active panel
5465//         workspace.read_with(cx, |workspace, cx| {
5466//             let left_dock = workspace.left_dock();
5467//             assert!(left_dock.read(cx).is_open());
5468//             assert_eq!(
5469//                 left_dock.read(cx).visible_panel().unwrap().id(),
5470//                 panel_1.id()
5471//             );
5472//         });
5473
5474//         // Emitting a ZoomIn event shows the panel as zoomed.
5475//         panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::ZoomIn));
5476//         workspace.read_with(cx, |workspace, _| {
5477//             assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
5478//             assert_eq!(workspace.zoomed_position, Some(DockPosition::Left));
5479//         });
5480
5481//         // Move panel to another dock while it is zoomed
5482//         panel_1.update(cx, |panel, cx| panel.set_position(DockPosition::Right, cx));
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//         // If focus is transferred to another view that's not a panel or another pane, we still show
5489//         // the panel as zoomed.
5490//         let focus_receiver = window.build_view(cx, |_| EmptyView);
5491//         focus_receiver.update(cx, |_, cx| cx.focus_self());
5492//         workspace.read_with(cx, |workspace, _| {
5493//             assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
5494//             assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5495//         });
5496
5497//         // If focus is transferred elsewhere in the workspace, the panel is no longer zoomed.
5498//         workspace.update(cx, |_, cx| cx.focus_self());
5499//         workspace.read_with(cx, |workspace, _| {
5500//             assert_eq!(workspace.zoomed, None);
5501//             assert_eq!(workspace.zoomed_position, None);
5502//         });
5503
5504//         // If focus is transferred again to another view that's not a panel or a pane, we won't
5505//         // show the panel as zoomed because it wasn't zoomed before.
5506//         focus_receiver.update(cx, |_, cx| cx.focus_self());
5507//         workspace.read_with(cx, |workspace, _| {
5508//             assert_eq!(workspace.zoomed, None);
5509//             assert_eq!(workspace.zoomed_position, None);
5510//         });
5511
5512//         // When focus is transferred back to the panel, it is zoomed again.
5513//         panel_1.update(cx, |_, cx| cx.focus_self());
5514//         workspace.read_with(cx, |workspace, _| {
5515//             assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
5516//             assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5517//         });
5518
5519//         // Emitting a ZoomOut event unzooms the panel.
5520//         panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::ZoomOut));
5521//         workspace.read_with(cx, |workspace, _| {
5522//             assert_eq!(workspace.zoomed, None);
5523//             assert_eq!(workspace.zoomed_position, None);
5524//         });
5525
5526//         // Emit closed event on panel 1, which is active
5527//         panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::Closed));
5528
5529//         // Now the left dock is closed, because panel_1 was the active panel
5530//         workspace.read_with(cx, |workspace, cx| {
5531//             let right_dock = workspace.right_dock();
5532//             assert!(!right_dock.read(cx).is_open());
5533//         });
5534//     }
5535
5536//     pub fn init_test(cx: &mut TestAppContext) {
5537//         cx.foreground().forbid_parking();
5538//         cx.update(|cx| {
5539//             cx.set_global(SettingsStore::test(cx));
5540//             theme::init((), cx);
5541//             language::init(cx);
5542//             crate::init_settings(cx);
5543//             Project::init_settings(cx);
5544//         });
5545//     }
5546// }