workspace2.rs

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