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