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