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