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