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