workspace2.rs

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