workspace2.rs

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