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