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