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