workspace2.rs

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