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