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