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