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