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