workspace2.rs

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