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;
  13pub mod 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.on_action(Workspace::close_global);
 216    cx.on_action(restart);
 217
 218    cx.on_action({
 219        let app_state = Arc::downgrade(&app_state);
 220        move |_: &Open, cx: &mut AppContext| {
 221            let mut paths = cx.prompt_for_paths(PathPromptOptions {
 222                files: true,
 223                directories: true,
 224                multiple: true,
 225            });
 226
 227            if let Some(app_state) = app_state.upgrade() {
 228                cx.spawn(move |mut cx| async move {
 229                    if let Some(paths) = paths.await.log_err().flatten() {
 230                        cx.update(|cx| {
 231                            open_paths(&paths, &app_state, None, cx).detach_and_log_err(cx)
 232                        })
 233                        .ok();
 234                    }
 235                })
 236                .detach();
 237            }
 238        }
 239    });
 240}
 241
 242type ProjectItemBuilders =
 243    HashMap<TypeId, fn(Model<Project>, AnyModel, &mut ViewContext<Pane>) -> Box<dyn ItemHandle>>;
 244pub fn register_project_item<I: ProjectItem>(cx: &mut AppContext) {
 245    let builders = cx.default_global::<ProjectItemBuilders>();
 246    builders.insert(TypeId::of::<I::Item>(), |project, model, cx| {
 247        let item = model.downcast::<I::Item>().unwrap();
 248        Box::new(cx.build_view(|cx| I::for_project_item(project, item, cx)))
 249    });
 250}
 251
 252type FollowableItemBuilder = fn(
 253    View<Pane>,
 254    View<Workspace>,
 255    ViewId,
 256    &mut Option<proto::view::Variant>,
 257    &mut WindowContext,
 258) -> Option<Task<Result<Box<dyn FollowableItemHandle>>>>;
 259type FollowableItemBuilders = HashMap<
 260    TypeId,
 261    (
 262        FollowableItemBuilder,
 263        fn(&AnyView) -> Box<dyn FollowableItemHandle>,
 264    ),
 265>;
 266pub fn register_followable_item<I: FollowableItem>(cx: &mut AppContext) {
 267    let builders = cx.default_global::<FollowableItemBuilders>();
 268    builders.insert(
 269        TypeId::of::<I>(),
 270        (
 271            |pane, workspace, id, state, cx| {
 272                I::from_state_proto(pane, workspace, id, state, cx).map(|task| {
 273                    cx.foreground_executor()
 274                        .spawn(async move { Ok(Box::new(task.await?) as Box<_>) })
 275                })
 276            },
 277            |this| Box::new(this.clone().downcast::<I>().unwrap()),
 278        ),
 279    );
 280}
 281
 282type ItemDeserializers = HashMap<
 283    Arc<str>,
 284    fn(
 285        Model<Project>,
 286        WeakView<Workspace>,
 287        WorkspaceId,
 288        ItemId,
 289        &mut ViewContext<Pane>,
 290    ) -> Task<Result<Box<dyn ItemHandle>>>,
 291>;
 292pub fn register_deserializable_item<I: Item>(cx: &mut AppContext) {
 293    if let Some(serialized_item_kind) = I::serialized_item_kind() {
 294        let deserializers = cx.default_global::<ItemDeserializers>();
 295        deserializers.insert(
 296            Arc::from(serialized_item_kind),
 297            |project, workspace, workspace_id, item_id, cx| {
 298                let task = I::deserialize(project, workspace, workspace_id, item_id, cx);
 299                cx.foreground_executor()
 300                    .spawn(async { Ok(Box::new(task.await?) as Box<_>) })
 301            },
 302        );
 303    }
 304}
 305
 306pub struct AppState {
 307    pub languages: Arc<LanguageRegistry>,
 308    pub client: Arc<Client>,
 309    pub user_store: Model<UserStore>,
 310    pub workspace_store: Model<WorkspaceStore>,
 311    pub fs: Arc<dyn fs::Fs>,
 312    pub build_window_options:
 313        fn(Option<WindowBounds>, Option<Uuid>, &mut AppContext) -> WindowOptions,
 314    pub node_runtime: Arc<dyn NodeRuntime>,
 315}
 316
 317pub struct WorkspaceStore {
 318    workspaces: HashSet<WindowHandle<Workspace>>,
 319    followers: Vec<Follower>,
 320    client: Arc<Client>,
 321    _subscriptions: Vec<client::Subscription>,
 322}
 323
 324#[derive(PartialEq, Eq, PartialOrd, Ord, Debug)]
 325struct Follower {
 326    project_id: Option<u64>,
 327    peer_id: PeerId,
 328}
 329
 330impl AppState {
 331    #[cfg(any(test, feature = "test-support"))]
 332    pub fn test(cx: &mut AppContext) -> Arc<Self> {
 333        use node_runtime::FakeNodeRuntime;
 334        use settings::SettingsStore;
 335
 336        if !cx.has_global::<SettingsStore>() {
 337            let settings_store = SettingsStore::test(cx);
 338            cx.set_global(settings_store);
 339        }
 340
 341        let fs = fs::FakeFs::new(cx.background_executor().clone());
 342        let languages = Arc::new(LanguageRegistry::test());
 343        let http_client = util::http::FakeHttpClient::with_404_response();
 344        let client = Client::new(http_client.clone(), cx);
 345        let user_store = cx.build_model(|cx| UserStore::new(client.clone(), http_client, cx));
 346        let workspace_store = cx.build_model(|cx| WorkspaceStore::new(client.clone(), cx));
 347
 348        theme::init(theme::LoadThemes::JustBase, cx);
 349        client::init(&client, cx);
 350        crate::init_settings(cx);
 351
 352        Arc::new(Self {
 353            client,
 354            fs,
 355            languages,
 356            user_store,
 357            workspace_store,
 358            node_runtime: FakeNodeRuntime::new(),
 359            build_window_options: |_, _, _| Default::default(),
 360        })
 361    }
 362}
 363
 364struct DelayedDebouncedEditAction {
 365    task: Option<Task<()>>,
 366    cancel_channel: Option<oneshot::Sender<()>>,
 367}
 368
 369impl DelayedDebouncedEditAction {
 370    fn new() -> DelayedDebouncedEditAction {
 371        DelayedDebouncedEditAction {
 372            task: None,
 373            cancel_channel: None,
 374        }
 375    }
 376
 377    fn fire_new<F>(&mut self, delay: Duration, cx: &mut ViewContext<Workspace>, func: F)
 378    where
 379        F: 'static + Send + FnOnce(&mut Workspace, &mut ViewContext<Workspace>) -> Task<Result<()>>,
 380    {
 381        if let Some(channel) = self.cancel_channel.take() {
 382            _ = channel.send(());
 383        }
 384
 385        let (sender, mut receiver) = oneshot::channel::<()>();
 386        self.cancel_channel = Some(sender);
 387
 388        let previous_task = self.task.take();
 389        self.task = Some(cx.spawn(move |workspace, mut cx| async move {
 390            let mut timer = cx.background_executor().timer(delay).fuse();
 391            if let Some(previous_task) = previous_task {
 392                previous_task.await;
 393            }
 394
 395            futures::select_biased! {
 396                _ = receiver => return,
 397                    _ = timer => {}
 398            }
 399
 400            if let Some(result) = workspace
 401                .update(&mut cx, |workspace, cx| (func)(workspace, cx))
 402                .log_err()
 403            {
 404                result.await.log_err();
 405            }
 406        }));
 407    }
 408}
 409
 410pub enum Event {
 411    PaneAdded(View<Pane>),
 412    ContactRequestedJoin(u64),
 413    WorkspaceCreated(WeakView<Workspace>),
 414}
 415
 416pub struct Workspace {
 417    window_self: WindowHandle<Self>,
 418    weak_self: WeakView<Self>,
 419    workspace_actions: Vec<Box<dyn Fn(Div, &mut ViewContext<Self>) -> Div>>,
 420    zoomed: Option<AnyWeakView>,
 421    zoomed_position: Option<DockPosition>,
 422    center: PaneGroup,
 423    left_dock: View<Dock>,
 424    bottom_dock: View<Dock>,
 425    right_dock: View<Dock>,
 426    panes: Vec<View<Pane>>,
 427    panes_by_item: HashMap<EntityId, WeakView<Pane>>,
 428    active_pane: View<Pane>,
 429    last_active_center_pane: Option<WeakView<Pane>>,
 430    last_active_view_id: Option<proto::ViewId>,
 431    status_bar: View<StatusBar>,
 432    modal_layer: View<ModalLayer>,
 433    titlebar_item: Option<AnyView>,
 434    notifications: Vec<(TypeId, usize, Box<dyn NotificationHandle>)>,
 435    project: Model<Project>,
 436    follower_states: HashMap<View<Pane>, FollowerState>,
 437    last_leaders_by_pane: HashMap<WeakView<Pane>, PeerId>,
 438    window_edited: bool,
 439    active_call: Option<(Model<ActiveCall>, Vec<Subscription>)>,
 440    leader_updates_tx: mpsc::UnboundedSender<(PeerId, proto::UpdateFollowers)>,
 441    database_id: WorkspaceId,
 442    app_state: Arc<AppState>,
 443    subscriptions: Vec<Subscription>,
 444    _apply_leader_updates: Task<Result<()>>,
 445    _observe_current_user: Task<Result<()>>,
 446    _schedule_serialize: Option<Task<()>>,
 447    pane_history_timestamp: Arc<AtomicUsize>,
 448}
 449
 450impl EventEmitter<Event> for Workspace {}
 451
 452#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
 453pub struct ViewId {
 454    pub creator: PeerId,
 455    pub id: u64,
 456}
 457
 458#[derive(Default)]
 459struct FollowerState {
 460    leader_id: PeerId,
 461    active_view_id: Option<ViewId>,
 462    items_by_leader_view_id: HashMap<ViewId, Box<dyn FollowableItemHandle>>,
 463}
 464
 465enum WorkspaceBounds {}
 466
 467impl Workspace {
 468    pub fn new(
 469        workspace_id: WorkspaceId,
 470        project: Model<Project>,
 471        app_state: Arc<AppState>,
 472        cx: &mut ViewContext<Self>,
 473    ) -> Self {
 474        cx.observe(&project, |_, _, cx| cx.notify()).detach();
 475        cx.subscribe(&project, move |this, _, event, cx| {
 476            match event {
 477                project::Event::RemoteIdChanged(_) => {
 478                    this.update_window_title(cx);
 479                }
 480
 481                project::Event::CollaboratorLeft(peer_id) => {
 482                    this.collaborator_left(*peer_id, cx);
 483                }
 484
 485                project::Event::WorktreeRemoved(_) | project::Event::WorktreeAdded => {
 486                    this.update_window_title(cx);
 487                    this.serialize_workspace(cx);
 488                }
 489
 490                project::Event::DisconnectedFromHost => {
 491                    this.update_window_edited(cx);
 492                    cx.blur();
 493                }
 494
 495                project::Event::Closed => {
 496                    cx.remove_window();
 497                }
 498
 499                project::Event::DeletedEntry(entry_id) => {
 500                    for pane in this.panes.iter() {
 501                        pane.update(cx, |pane, cx| {
 502                            pane.handle_deleted_project_item(*entry_id, cx)
 503                        });
 504                    }
 505                }
 506
 507                project::Event::Notification(message) => this.show_notification(0, cx, |cx| {
 508                    cx.build_view(|_| MessageNotification::new(message.clone()))
 509                }),
 510
 511                _ => {}
 512            }
 513            cx.notify()
 514        })
 515        .detach();
 516
 517        let weak_handle = cx.view().downgrade();
 518        let pane_history_timestamp = Arc::new(AtomicUsize::new(0));
 519
 520        let center_pane = cx.build_view(|cx| {
 521            Pane::new(
 522                weak_handle.clone(),
 523                project.clone(),
 524                pane_history_timestamp.clone(),
 525                cx,
 526            )
 527        });
 528        cx.subscribe(&center_pane, Self::handle_pane_event).detach();
 529
 530        cx.focus_view(&center_pane);
 531        cx.emit(Event::PaneAdded(center_pane.clone()));
 532
 533        let window_handle = cx.window_handle().downcast::<Workspace>().unwrap();
 534        app_state.workspace_store.update(cx, |store, _| {
 535            store.workspaces.insert(window_handle);
 536        });
 537
 538        let mut current_user = app_state.user_store.read(cx).watch_current_user();
 539        let mut connection_status = app_state.client.status();
 540        let _observe_current_user = cx.spawn(|this, mut cx| async move {
 541            current_user.next().await;
 542            connection_status.next().await;
 543            let mut stream =
 544                Stream::map(current_user, drop).merge(Stream::map(connection_status, drop));
 545
 546            while stream.recv().await.is_some() {
 547                this.update(&mut cx, |_, cx| cx.notify())?;
 548            }
 549            anyhow::Ok(())
 550        });
 551
 552        // All leader updates are enqueued and then processed in a single task, so
 553        // that each asynchronous operation can be run in order.
 554        let (leader_updates_tx, mut leader_updates_rx) =
 555            mpsc::unbounded::<(PeerId, proto::UpdateFollowers)>();
 556        let _apply_leader_updates = cx.spawn(|this, mut cx| async move {
 557            while let Some((leader_id, update)) = leader_updates_rx.next().await {
 558                Self::process_leader_update(&this, leader_id, update, &mut cx)
 559                    .await
 560                    .log_err();
 561            }
 562
 563            Ok(())
 564        });
 565
 566        cx.emit(Event::WorkspaceCreated(weak_handle.clone()));
 567
 568        let left_dock = cx.build_view(|_| Dock::new(DockPosition::Left));
 569        let bottom_dock = cx.build_view(|_| Dock::new(DockPosition::Bottom));
 570        let right_dock = cx.build_view(|_| Dock::new(DockPosition::Right));
 571        let left_dock_buttons =
 572            cx.build_view(|cx| PanelButtons::new(left_dock.clone(), weak_handle.clone(), cx));
 573        let bottom_dock_buttons =
 574            cx.build_view(|cx| PanelButtons::new(bottom_dock.clone(), weak_handle.clone(), cx));
 575        let right_dock_buttons =
 576            cx.build_view(|cx| PanelButtons::new(right_dock.clone(), weak_handle.clone(), cx));
 577        let status_bar = cx.build_view(|cx| {
 578            let mut status_bar = StatusBar::new(&center_pane.clone(), cx);
 579            status_bar.add_left_item(left_dock_buttons, cx);
 580            status_bar.add_right_item(right_dock_buttons, cx);
 581            status_bar.add_right_item(bottom_dock_buttons, cx);
 582            status_bar
 583        });
 584
 585        let workspace_handle = cx.view().downgrade();
 586        let modal_layer = cx.build_view(|cx| ModalLayer::new());
 587
 588        // todo!()
 589        // cx.update_default_global::<DragAndDrop<Workspace>, _, _>(|drag_and_drop, _| {
 590        //     drag_and_drop.register_container(weak_handle.clone());
 591        // });
 592
 593        let mut active_call = None;
 594        if cx.has_global::<Model<ActiveCall>>() {
 595            let call = cx.global::<Model<ActiveCall>>().clone();
 596            let mut subscriptions = Vec::new();
 597            subscriptions.push(cx.subscribe(&call, Self::on_active_call_event));
 598            active_call = Some((call, subscriptions));
 599        }
 600
 601        let subscriptions = vec![
 602            cx.observe_window_activation(Self::on_window_activation_changed),
 603            cx.observe_window_bounds(move |_, cx| {
 604                if let Some(display) = cx.display() {
 605                    // Transform fixed bounds to be stored in terms of the containing display
 606                    let mut bounds = cx.window_bounds();
 607                    if let WindowBounds::Fixed(window_bounds) = &mut bounds {
 608                        let display_bounds = display.bounds();
 609                        window_bounds.origin.x -= display_bounds.origin.x;
 610                        window_bounds.origin.y -= display_bounds.origin.y;
 611                    }
 612
 613                    if let Some(display_uuid) = display.uuid().log_err() {
 614                        cx.background_executor()
 615                            .spawn(DB.set_window_bounds(workspace_id, bounds, display_uuid))
 616                            .detach_and_log_err(cx);
 617                    }
 618                }
 619                cx.notify();
 620            }),
 621            cx.observe(&left_dock, |this, _, cx| {
 622                this.serialize_workspace(cx);
 623                cx.notify();
 624            }),
 625            cx.observe(&bottom_dock, |this, _, cx| {
 626                this.serialize_workspace(cx);
 627                cx.notify();
 628            }),
 629            cx.observe(&right_dock, |this, _, cx| {
 630                this.serialize_workspace(cx);
 631                cx.notify();
 632            }),
 633            cx.on_release(|this, cx| {
 634                this.app_state.workspace_store.update(cx, |store, _| {
 635                    store.workspaces.remove(&this.window_self);
 636                })
 637            }),
 638        ];
 639
 640        cx.defer(|this, cx| {
 641            this.update_window_title(cx);
 642            // todo! @nate - these are useful for testing notifications
 643            // this.show_error(
 644            //     &anyhow::anyhow!("what happens if this message is very very very very very long"),
 645            //     cx,
 646            // );
 647
 648            // this.show_notification(1, cx, |cx| {
 649            //     cx.build_view(|_cx| {
 650            //         simple_message_notification::MessageNotification::new(format!("Error:"))
 651            //             .with_click_message("click here because!")
 652            //     })
 653            // });
 654        });
 655        Workspace {
 656            window_self: window_handle,
 657            weak_self: weak_handle.clone(),
 658            zoomed: None,
 659            zoomed_position: None,
 660            center: PaneGroup::new(center_pane.clone()),
 661            panes: vec![center_pane.clone()],
 662            panes_by_item: Default::default(),
 663            active_pane: center_pane.clone(),
 664            last_active_center_pane: Some(center_pane.downgrade()),
 665            last_active_view_id: None,
 666            status_bar,
 667            modal_layer,
 668            titlebar_item: None,
 669            notifications: Default::default(),
 670            left_dock,
 671            bottom_dock,
 672            right_dock,
 673            project: project.clone(),
 674            follower_states: Default::default(),
 675            last_leaders_by_pane: Default::default(),
 676            window_edited: false,
 677            active_call,
 678            database_id: workspace_id,
 679            app_state,
 680            _observe_current_user,
 681            _apply_leader_updates,
 682            _schedule_serialize: None,
 683            leader_updates_tx,
 684            subscriptions,
 685            pane_history_timestamp,
 686            workspace_actions: Default::default(),
 687        }
 688    }
 689
 690    fn new_local(
 691        abs_paths: Vec<PathBuf>,
 692        app_state: Arc<AppState>,
 693        requesting_window: Option<WindowHandle<Workspace>>,
 694        cx: &mut AppContext,
 695    ) -> Task<
 696        anyhow::Result<(
 697            WindowHandle<Workspace>,
 698            Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
 699        )>,
 700    > {
 701        let project_handle = Project::local(
 702            app_state.client.clone(),
 703            app_state.node_runtime.clone(),
 704            app_state.user_store.clone(),
 705            app_state.languages.clone(),
 706            app_state.fs.clone(),
 707            cx,
 708        );
 709
 710        cx.spawn(|mut cx| async move {
 711            let serialized_workspace: Option<SerializedWorkspace> =
 712                persistence::DB.workspace_for_roots(&abs_paths.as_slice());
 713
 714            let paths_to_open = Arc::new(abs_paths);
 715
 716            // Get project paths for all of the abs_paths
 717            let mut worktree_roots: HashSet<Arc<Path>> = Default::default();
 718            let mut project_paths: Vec<(PathBuf, Option<ProjectPath>)> =
 719                Vec::with_capacity(paths_to_open.len());
 720            for path in paths_to_open.iter().cloned() {
 721                if let Some((worktree, project_entry)) = cx
 722                    .update(|cx| {
 723                        Workspace::project_path_for_path(project_handle.clone(), &path, true, cx)
 724                    })?
 725                    .await
 726                    .log_err()
 727                {
 728                    worktree_roots.extend(worktree.update(&mut cx, |tree, _| tree.abs_path()).ok());
 729                    project_paths.push((path, Some(project_entry)));
 730                } else {
 731                    project_paths.push((path, None));
 732                }
 733            }
 734
 735            let workspace_id = if let Some(serialized_workspace) = serialized_workspace.as_ref() {
 736                serialized_workspace.id
 737            } else {
 738                DB.next_id().await.unwrap_or(0)
 739            };
 740
 741            let window = if let Some(window) = requesting_window {
 742                cx.update_window(window.into(), |old_workspace, cx| {
 743                    cx.replace_root_view(|cx| {
 744                        Workspace::new(workspace_id, project_handle.clone(), app_state.clone(), cx)
 745                    });
 746                })?;
 747                window
 748            } else {
 749                let window_bounds_override = window_bounds_env_override(&cx);
 750                let (bounds, display) = if let Some(bounds) = window_bounds_override {
 751                    (Some(bounds), None)
 752                } else {
 753                    serialized_workspace
 754                        .as_ref()
 755                        .and_then(|serialized_workspace| {
 756                            let serialized_display = serialized_workspace.display?;
 757                            let mut bounds = serialized_workspace.bounds?;
 758
 759                            // Stored bounds are relative to the containing display.
 760                            // So convert back to global coordinates if that screen still exists
 761                            if let WindowBounds::Fixed(mut window_bounds) = bounds {
 762                                let screen = cx
 763                                    .update(|cx| {
 764                                        cx.displays().into_iter().find(|display| {
 765                                            display.uuid().ok() == Some(serialized_display)
 766                                        })
 767                                    })
 768                                    .ok()??;
 769                                let screen_bounds = screen.bounds();
 770                                window_bounds.origin.x += screen_bounds.origin.x;
 771                                window_bounds.origin.y += screen_bounds.origin.y;
 772                                bounds = WindowBounds::Fixed(window_bounds);
 773                            }
 774
 775                            Some((bounds, serialized_display))
 776                        })
 777                        .unzip()
 778                };
 779
 780                // Use the serialized workspace to construct the new window
 781                let options =
 782                    cx.update(|cx| (app_state.build_window_options)(bounds, display, cx))?;
 783
 784                cx.open_window(options, {
 785                    let app_state = app_state.clone();
 786                    let workspace_id = workspace_id.clone();
 787                    let project_handle = project_handle.clone();
 788                    move |cx| {
 789                        cx.build_view(|cx| {
 790                            Workspace::new(workspace_id, project_handle, app_state, cx)
 791                        })
 792                    }
 793                })?
 794            };
 795
 796            window
 797                .update(&mut cx, |_, cx| cx.activate_window())
 798                .log_err();
 799
 800            notify_if_database_failed(window, &mut cx);
 801            let opened_items = window
 802                .update(&mut cx, |_workspace, cx| {
 803                    open_items(serialized_workspace, project_paths, app_state, cx)
 804                })?
 805                .await
 806                .unwrap_or_default();
 807
 808            Ok((window, opened_items))
 809        })
 810    }
 811
 812    pub fn weak_handle(&self) -> WeakView<Self> {
 813        self.weak_self.clone()
 814    }
 815
 816    pub fn left_dock(&self) -> &View<Dock> {
 817        &self.left_dock
 818    }
 819
 820    pub fn bottom_dock(&self) -> &View<Dock> {
 821        &self.bottom_dock
 822    }
 823
 824    pub fn right_dock(&self) -> &View<Dock> {
 825        &self.right_dock
 826    }
 827
 828    pub fn add_panel<T: Panel>(&mut self, panel: View<T>, cx: &mut ViewContext<Self>) {
 829        let dock = match panel.position(cx) {
 830            DockPosition::Left => &self.left_dock,
 831            DockPosition::Bottom => &self.bottom_dock,
 832            DockPosition::Right => &self.right_dock,
 833        };
 834
 835        dock.update(cx, |dock, cx| {
 836            dock.add_panel(panel, self.weak_self.clone(), cx)
 837        });
 838    }
 839
 840    pub fn status_bar(&self) -> &View<StatusBar> {
 841        &self.status_bar
 842    }
 843
 844    pub fn app_state(&self) -> &Arc<AppState> {
 845        &self.app_state
 846    }
 847
 848    pub fn user_store(&self) -> &Model<UserStore> {
 849        &self.app_state.user_store
 850    }
 851
 852    pub fn project(&self) -> &Model<Project> {
 853        &self.project
 854    }
 855
 856    pub fn recent_navigation_history(
 857        &self,
 858        limit: Option<usize>,
 859        cx: &AppContext,
 860    ) -> Vec<(ProjectPath, Option<PathBuf>)> {
 861        let mut abs_paths_opened: HashMap<PathBuf, HashSet<ProjectPath>> = HashMap::default();
 862        let mut history: HashMap<ProjectPath, (Option<PathBuf>, usize)> = HashMap::default();
 863        for pane in &self.panes {
 864            let pane = pane.read(cx);
 865            pane.nav_history()
 866                .for_each_entry(cx, |entry, (project_path, fs_path)| {
 867                    if let Some(fs_path) = &fs_path {
 868                        abs_paths_opened
 869                            .entry(fs_path.clone())
 870                            .or_default()
 871                            .insert(project_path.clone());
 872                    }
 873                    let timestamp = entry.timestamp;
 874                    match history.entry(project_path) {
 875                        hash_map::Entry::Occupied(mut entry) => {
 876                            let (_, old_timestamp) = entry.get();
 877                            if &timestamp > old_timestamp {
 878                                entry.insert((fs_path, timestamp));
 879                            }
 880                        }
 881                        hash_map::Entry::Vacant(entry) => {
 882                            entry.insert((fs_path, timestamp));
 883                        }
 884                    }
 885                });
 886        }
 887
 888        history
 889            .into_iter()
 890            .sorted_by_key(|(_, (_, timestamp))| *timestamp)
 891            .map(|(project_path, (fs_path, _))| (project_path, fs_path))
 892            .rev()
 893            .filter(|(history_path, abs_path)| {
 894                let latest_project_path_opened = abs_path
 895                    .as_ref()
 896                    .and_then(|abs_path| abs_paths_opened.get(abs_path))
 897                    .and_then(|project_paths| {
 898                        project_paths
 899                            .iter()
 900                            .max_by(|b1, b2| b1.worktree_id.cmp(&b2.worktree_id))
 901                    });
 902
 903                match latest_project_path_opened {
 904                    Some(latest_project_path_opened) => latest_project_path_opened == history_path,
 905                    None => true,
 906                }
 907            })
 908            .take(limit.unwrap_or(usize::MAX))
 909            .collect()
 910    }
 911
 912    fn navigate_history(
 913        &mut self,
 914        pane: WeakView<Pane>,
 915        mode: NavigationMode,
 916        cx: &mut ViewContext<Workspace>,
 917    ) -> Task<Result<()>> {
 918        let to_load = if let Some(pane) = pane.upgrade() {
 919            // todo!("focus")
 920            // cx.focus(&pane);
 921
 922            pane.update(cx, |pane, cx| {
 923                loop {
 924                    // Retrieve the weak item handle from the history.
 925                    let entry = pane.nav_history_mut().pop(mode, cx)?;
 926
 927                    // If the item is still present in this pane, then activate it.
 928                    if let Some(index) = entry
 929                        .item
 930                        .upgrade()
 931                        .and_then(|v| pane.index_for_item(v.as_ref()))
 932                    {
 933                        let prev_active_item_index = pane.active_item_index();
 934                        pane.nav_history_mut().set_mode(mode);
 935                        pane.activate_item(index, true, true, cx);
 936                        pane.nav_history_mut().set_mode(NavigationMode::Normal);
 937
 938                        let mut navigated = prev_active_item_index != pane.active_item_index();
 939                        if let Some(data) = entry.data {
 940                            navigated |= pane.active_item()?.navigate(data, cx);
 941                        }
 942
 943                        if navigated {
 944                            break None;
 945                        }
 946                    }
 947                    // If the item is no longer present in this pane, then retrieve its
 948                    // project path in order to reopen it.
 949                    else {
 950                        break pane
 951                            .nav_history()
 952                            .path_for_item(entry.item.id())
 953                            .map(|(project_path, _)| (project_path, entry));
 954                    }
 955                }
 956            })
 957        } else {
 958            None
 959        };
 960
 961        if let Some((project_path, entry)) = to_load {
 962            // If the item was no longer present, then load it again from its previous path.
 963            let task = self.load_path(project_path, cx);
 964            cx.spawn(|workspace, mut cx| async move {
 965                let task = task.await;
 966                let mut navigated = false;
 967                if let Some((project_entry_id, build_item)) = task.log_err() {
 968                    let prev_active_item_id = pane.update(&mut cx, |pane, _| {
 969                        pane.nav_history_mut().set_mode(mode);
 970                        pane.active_item().map(|p| p.item_id())
 971                    })?;
 972
 973                    pane.update(&mut cx, |pane, cx| {
 974                        let item = pane.open_item(project_entry_id, true, cx, build_item);
 975                        navigated |= Some(item.item_id()) != prev_active_item_id;
 976                        pane.nav_history_mut().set_mode(NavigationMode::Normal);
 977                        if let Some(data) = entry.data {
 978                            navigated |= item.navigate(data, cx);
 979                        }
 980                    })?;
 981                }
 982
 983                if !navigated {
 984                    workspace
 985                        .update(&mut cx, |workspace, cx| {
 986                            Self::navigate_history(workspace, pane, mode, cx)
 987                        })?
 988                        .await?;
 989                }
 990
 991                Ok(())
 992            })
 993        } else {
 994            Task::ready(Ok(()))
 995        }
 996    }
 997
 998    pub fn go_back(
 999        &mut self,
1000        pane: WeakView<Pane>,
1001        cx: &mut ViewContext<Workspace>,
1002    ) -> Task<Result<()>> {
1003        self.navigate_history(pane, NavigationMode::GoingBack, cx)
1004    }
1005
1006    pub fn go_forward(
1007        &mut self,
1008        pane: WeakView<Pane>,
1009        cx: &mut ViewContext<Workspace>,
1010    ) -> Task<Result<()>> {
1011        self.navigate_history(pane, NavigationMode::GoingForward, cx)
1012    }
1013
1014    pub fn reopen_closed_item(&mut self, cx: &mut ViewContext<Workspace>) -> Task<Result<()>> {
1015        self.navigate_history(
1016            self.active_pane().downgrade(),
1017            NavigationMode::ReopeningClosedItem,
1018            cx,
1019        )
1020    }
1021
1022    pub fn client(&self) -> &Client {
1023        &self.app_state.client
1024    }
1025
1026    pub fn set_titlebar_item(&mut self, item: AnyView, cx: &mut ViewContext<Self>) {
1027        self.titlebar_item = Some(item);
1028        cx.notify();
1029    }
1030
1031    pub fn titlebar_item(&self) -> Option<AnyView> {
1032        self.titlebar_item.clone()
1033    }
1034
1035    /// Call the given callback with a workspace whose project is local.
1036    ///
1037    /// If the given workspace has a local project, then it will be passed
1038    /// to the callback. Otherwise, a new empty window will be created.
1039    pub fn with_local_workspace<T, F>(
1040        &mut self,
1041        cx: &mut ViewContext<Self>,
1042        callback: F,
1043    ) -> Task<Result<T>>
1044    where
1045        T: 'static,
1046        F: 'static + FnOnce(&mut Workspace, &mut ViewContext<Workspace>) -> T,
1047    {
1048        if self.project.read(cx).is_local() {
1049            Task::Ready(Some(Ok(callback(self, cx))))
1050        } else {
1051            let task = Self::new_local(Vec::new(), self.app_state.clone(), None, cx);
1052            cx.spawn(|_vh, mut cx| async move {
1053                let (workspace, _) = task.await?;
1054                workspace.update(&mut cx, callback)
1055            })
1056        }
1057    }
1058
1059    pub fn worktrees<'a>(&self, cx: &'a AppContext) -> impl 'a + Iterator<Item = Model<Worktree>> {
1060        self.project.read(cx).worktrees()
1061    }
1062
1063    pub fn visible_worktrees<'a>(
1064        &self,
1065        cx: &'a AppContext,
1066    ) -> impl 'a + Iterator<Item = Model<Worktree>> {
1067        self.project.read(cx).visible_worktrees(cx)
1068    }
1069
1070    pub fn worktree_scans_complete(&self, cx: &AppContext) -> impl Future<Output = ()> + 'static {
1071        let futures = self
1072            .worktrees(cx)
1073            .filter_map(|worktree| worktree.read(cx).as_local())
1074            .map(|worktree| worktree.scan_complete())
1075            .collect::<Vec<_>>();
1076        async move {
1077            for future in futures {
1078                future.await;
1079            }
1080        }
1081    }
1082
1083    pub fn close_global(_: &CloseWindow, cx: &mut AppContext) {
1084        cx.windows().iter().find(|window| {
1085            window
1086                .update(cx, |_, window| {
1087                    if window.is_window_active() {
1088                        //This can only get called when the window's project connection has been lost
1089                        //so we don't need to prompt the user for anything and instead just close the window
1090                        window.remove_window();
1091                        true
1092                    } else {
1093                        false
1094                    }
1095                })
1096                .unwrap_or(false)
1097        });
1098    }
1099
1100    pub fn close_window(&mut self, _: &CloseWindow, cx: &mut ViewContext<Self>) {
1101        let window = cx.window_handle();
1102        let prepare = self.prepare_to_close(false, cx);
1103        cx.spawn(|_, mut cx| async move {
1104            if prepare.await? {
1105                window.update(&mut cx, |_, cx| {
1106                    cx.remove_window();
1107                })?;
1108            }
1109            anyhow::Ok(())
1110        })
1111        .detach_and_log_err(cx)
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    fn load_path(
1857        &mut self,
1858        path: ProjectPath,
1859        cx: &mut ViewContext<Self>,
1860    ) -> Task<
1861        Result<(
1862            Option<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    // ) {
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;
2358    //     };
2359    //     if Some(leader_id) == self.unfollow(&pane, cx) {
2360    //         return;
2361    //     }
2362    //     if let Some(task) = self.follow(leader_id, cx) {
2363    //         task.detach();
2364    //     }
2365    // }
2366
2367    pub fn follow(
2368        &mut self,
2369        leader_id: PeerId,
2370        cx: &mut ViewContext<Self>,
2371    ) -> Option<Task<Result<()>>> {
2372        let room = ActiveCall::global(cx).read(cx).room()?.read(cx);
2373        let project = self.project.read(cx);
2374
2375        let Some(remote_participant) = room.remote_participant_for_peer_id(leader_id) else {
2376            return None;
2377        };
2378
2379        let other_project_id = match remote_participant.location {
2380            call::ParticipantLocation::External => None,
2381            call::ParticipantLocation::UnsharedProject => None,
2382            call::ParticipantLocation::SharedProject { project_id } => {
2383                if Some(project_id) == project.remote_id() {
2384                    None
2385                } else {
2386                    Some(project_id)
2387                }
2388            }
2389        };
2390
2391        // if they are active in another project, follow there.
2392        if let Some(project_id) = other_project_id {
2393            let app_state = self.app_state.clone();
2394            return Some(crate::join_remote_project(
2395                project_id,
2396                remote_participant.user.id,
2397                app_state,
2398                cx,
2399            ));
2400        }
2401
2402        // if you're already following, find the right pane and focus it.
2403        for (pane, state) in &self.follower_states {
2404            if leader_id == state.leader_id {
2405                cx.focus_view(pane);
2406                return None;
2407            }
2408        }
2409
2410        // Otherwise, follow.
2411        self.start_following(leader_id, cx)
2412    }
2413
2414    //     // if you're already following, find the right pane and focus it.
2415    //     for (pane, state) in &self.follower_states {
2416    //         if leader_id == state.leader_id {
2417    //             cx.focus(pane);
2418    //             return None;
2419    //         }
2420    //     }
2421
2422    //     // Otherwise, follow.
2423    //     self.start_following(leader_id, cx)
2424    // }
2425
2426    pub fn unfollow(&mut self, pane: &View<Pane>, cx: &mut ViewContext<Self>) -> Option<PeerId> {
2427        let state = self.follower_states.remove(pane)?;
2428        let leader_id = state.leader_id;
2429        for (_, item) in state.items_by_leader_view_id {
2430            item.set_leader_peer_id(None, cx);
2431        }
2432
2433        if self
2434            .follower_states
2435            .values()
2436            .all(|state| state.leader_id != state.leader_id)
2437        {
2438            let project_id = self.project.read(cx).remote_id();
2439            let room_id = self.active_call()?.read(cx).room()?.read(cx).id();
2440            self.app_state
2441                .client
2442                .send(proto::Unfollow {
2443                    room_id,
2444                    project_id,
2445                    leader_id: Some(leader_id),
2446                })
2447                .log_err();
2448        }
2449
2450        cx.notify();
2451        Some(leader_id)
2452    }
2453
2454    //     pub fn is_being_followed(&self, peer_id: PeerId) -> bool {
2455    //         self.follower_states
2456    //             .values()
2457    //             .any(|state| state.leader_id == peer_id)
2458    //     }
2459
2460    fn active_item_path_changed(&mut self, cx: &mut ViewContext<Self>) {
2461        let active_entry = self.active_project_path(cx);
2462        self.project
2463            .update(cx, |project, cx| project.set_active_path(active_entry, cx));
2464        self.update_window_title(cx);
2465    }
2466
2467    fn update_window_title(&mut self, cx: &mut ViewContext<Self>) {
2468        let project = self.project().read(cx);
2469        let mut title = String::new();
2470
2471        if let Some(path) = self.active_item(cx).and_then(|item| item.project_path(cx)) {
2472            let filename = path
2473                .path
2474                .file_name()
2475                .map(|s| s.to_string_lossy())
2476                .or_else(|| {
2477                    Some(Cow::Borrowed(
2478                        project
2479                            .worktree_for_id(path.worktree_id, cx)?
2480                            .read(cx)
2481                            .root_name(),
2482                    ))
2483                });
2484
2485            if let Some(filename) = filename {
2486                title.push_str(filename.as_ref());
2487                title.push_str(" β€” ");
2488            }
2489        }
2490
2491        for (i, name) in project.worktree_root_names(cx).enumerate() {
2492            if i > 0 {
2493                title.push_str(", ");
2494            }
2495            title.push_str(name);
2496        }
2497
2498        if title.is_empty() {
2499            title = "empty project".to_string();
2500        }
2501
2502        if project.is_remote() {
2503            title.push_str(" ↙");
2504        } else if project.is_shared() {
2505            title.push_str(" β†—");
2506        }
2507
2508        cx.set_window_title(&title);
2509    }
2510
2511    fn update_window_edited(&mut self, cx: &mut ViewContext<Self>) {
2512        let is_edited = !self.project.read(cx).is_read_only()
2513            && self
2514                .items(cx)
2515                .any(|item| item.has_conflict(cx) || item.is_dirty(cx));
2516        if is_edited != self.window_edited {
2517            self.window_edited = is_edited;
2518            // todo!()
2519            // cx.set_window_edited(self.window_edited)
2520        }
2521    }
2522
2523    //     fn render_disconnected_overlay(
2524    //         &self,
2525    //         cx: &mut ViewContext<Workspace>,
2526    //     ) -> Option<AnyElement<Workspace>> {
2527    //         if self.project.read(cx).is_read_only() {
2528    //             enum DisconnectedOverlay {}
2529    //             Some(
2530    //                 MouseEventHandler::new::<DisconnectedOverlay, _>(0, cx, |_, cx| {
2531    //                     let theme = &theme::current(cx);
2532    //                     Label::new(
2533    //                         "Your connection to the remote project has been lost.",
2534    //                         theme.workspace.disconnected_overlay.text.clone(),
2535    //                     )
2536    //                     .aligned()
2537    //                     .contained()
2538    //                     .with_style(theme.workspace.disconnected_overlay.container)
2539    //                 })
2540    //                 .with_cursor_style(CursorStyle::Arrow)
2541    //                 .capture_all()
2542    //                 .into_any_named("disconnected overlay"),
2543    //             )
2544    //         } else {
2545    //             None
2546    //         }
2547    //     }
2548
2549    fn render_notifications(&self, cx: &ViewContext<Self>) -> Option<Div> {
2550        if self.notifications.is_empty() {
2551            None
2552        } else {
2553            Some(
2554                div()
2555                    .absolute()
2556                    .z_index(100)
2557                    .right_3()
2558                    .bottom_3()
2559                    .w_96()
2560                    .h_full()
2561                    .flex()
2562                    .flex_col()
2563                    .justify_end()
2564                    .gap_2()
2565                    .children(
2566                        self.notifications
2567                            .iter()
2568                            .map(|(_, _, notification)| notification.to_any()),
2569                    ),
2570            )
2571        }
2572    }
2573
2574    // RPC handlers
2575
2576    fn handle_follow(
2577        &mut self,
2578        follower_project_id: Option<u64>,
2579        cx: &mut ViewContext<Self>,
2580    ) -> proto::FollowResponse {
2581        let client = &self.app_state.client;
2582        let project_id = self.project.read(cx).remote_id();
2583
2584        let active_view_id = self.active_item(cx).and_then(|i| {
2585            Some(
2586                i.to_followable_item_handle(cx)?
2587                    .remote_id(client, cx)?
2588                    .to_proto(),
2589            )
2590        });
2591
2592        cx.notify();
2593
2594        self.last_active_view_id = active_view_id.clone();
2595        proto::FollowResponse {
2596            active_view_id,
2597            views: self
2598                .panes()
2599                .iter()
2600                .flat_map(|pane| {
2601                    let leader_id = self.leader_for_pane(pane);
2602                    pane.read(cx).items().filter_map({
2603                        let cx = &cx;
2604                        move |item| {
2605                            let item = item.to_followable_item_handle(cx)?;
2606                            if (project_id.is_none() || project_id != follower_project_id)
2607                                && item.is_project_item(cx)
2608                            {
2609                                return None;
2610                            }
2611                            let id = item.remote_id(client, cx)?.to_proto();
2612                            let variant = item.to_state_proto(cx)?;
2613                            Some(proto::View {
2614                                id: Some(id),
2615                                leader_id,
2616                                variant: Some(variant),
2617                            })
2618                        }
2619                    })
2620                })
2621                .collect(),
2622        }
2623    }
2624
2625    fn handle_update_followers(
2626        &mut self,
2627        leader_id: PeerId,
2628        message: proto::UpdateFollowers,
2629        _cx: &mut ViewContext<Self>,
2630    ) {
2631        self.leader_updates_tx
2632            .unbounded_send((leader_id, message))
2633            .ok();
2634    }
2635
2636    async fn process_leader_update(
2637        this: &WeakView<Self>,
2638        leader_id: PeerId,
2639        update: proto::UpdateFollowers,
2640        cx: &mut AsyncWindowContext,
2641    ) -> Result<()> {
2642        match update.variant.ok_or_else(|| anyhow!("invalid update"))? {
2643            proto::update_followers::Variant::UpdateActiveView(update_active_view) => {
2644                this.update(cx, |this, _| {
2645                    for (_, state) in &mut this.follower_states {
2646                        if state.leader_id == leader_id {
2647                            state.active_view_id =
2648                                if let Some(active_view_id) = update_active_view.id.clone() {
2649                                    Some(ViewId::from_proto(active_view_id)?)
2650                                } else {
2651                                    None
2652                                };
2653                        }
2654                    }
2655                    anyhow::Ok(())
2656                })??;
2657            }
2658            proto::update_followers::Variant::UpdateView(update_view) => {
2659                let variant = update_view
2660                    .variant
2661                    .ok_or_else(|| anyhow!("missing update view variant"))?;
2662                let id = update_view
2663                    .id
2664                    .ok_or_else(|| anyhow!("missing update view id"))?;
2665                let mut tasks = Vec::new();
2666                this.update(cx, |this, cx| {
2667                    let project = this.project.clone();
2668                    for (_, state) in &mut this.follower_states {
2669                        if state.leader_id == leader_id {
2670                            let view_id = ViewId::from_proto(id.clone())?;
2671                            if let Some(item) = state.items_by_leader_view_id.get(&view_id) {
2672                                tasks.push(item.apply_update_proto(&project, variant.clone(), cx));
2673                            }
2674                        }
2675                    }
2676                    anyhow::Ok(())
2677                })??;
2678                try_join_all(tasks).await.log_err();
2679            }
2680            proto::update_followers::Variant::CreateView(view) => {
2681                let panes = this.update(cx, |this, _| {
2682                    this.follower_states
2683                        .iter()
2684                        .filter_map(|(pane, state)| (state.leader_id == leader_id).then_some(pane))
2685                        .cloned()
2686                        .collect()
2687                })?;
2688                Self::add_views_from_leader(this.clone(), leader_id, panes, vec![view], cx).await?;
2689            }
2690        }
2691        this.update(cx, |this, cx| this.leader_updated(leader_id, cx))?;
2692        Ok(())
2693    }
2694
2695    async fn add_views_from_leader(
2696        this: WeakView<Self>,
2697        leader_id: PeerId,
2698        panes: Vec<View<Pane>>,
2699        views: Vec<proto::View>,
2700        cx: &mut AsyncWindowContext,
2701    ) -> Result<()> {
2702        let this = this.upgrade().context("workspace dropped")?;
2703
2704        let item_builders = cx.update(|_, cx| {
2705            cx.default_global::<FollowableItemBuilders>()
2706                .values()
2707                .map(|b| b.0)
2708                .collect::<Vec<_>>()
2709        })?;
2710
2711        let mut item_tasks_by_pane = HashMap::default();
2712        for pane in panes {
2713            let mut item_tasks = Vec::new();
2714            let mut leader_view_ids = Vec::new();
2715            for view in &views {
2716                let Some(id) = &view.id else { continue };
2717                let id = ViewId::from_proto(id.clone())?;
2718                let mut variant = view.variant.clone();
2719                if variant.is_none() {
2720                    Err(anyhow!("missing view variant"))?;
2721                }
2722                for build_item in &item_builders {
2723                    let task = cx.update(|_, cx| {
2724                        build_item(pane.clone(), this.clone(), id, &mut variant, cx)
2725                    })?;
2726                    if let Some(task) = task {
2727                        item_tasks.push(task);
2728                        leader_view_ids.push(id);
2729                        break;
2730                    } else {
2731                        assert!(variant.is_some());
2732                    }
2733                }
2734            }
2735
2736            item_tasks_by_pane.insert(pane, (item_tasks, leader_view_ids));
2737        }
2738
2739        for (pane, (item_tasks, leader_view_ids)) in item_tasks_by_pane {
2740            let items = futures::future::try_join_all(item_tasks).await?;
2741            this.update(cx, |this, cx| {
2742                let state = this.follower_states.get_mut(&pane)?;
2743                for (id, item) in leader_view_ids.into_iter().zip(items) {
2744                    item.set_leader_peer_id(Some(leader_id), cx);
2745                    state.items_by_leader_view_id.insert(id, item);
2746                }
2747
2748                Some(())
2749            })?;
2750        }
2751        Ok(())
2752    }
2753
2754    fn update_active_view_for_followers(&mut self, cx: &mut ViewContext<Self>) {
2755        let mut is_project_item = true;
2756        let mut update = proto::UpdateActiveView::default();
2757        if self.active_pane.read(cx).has_focus(cx) {
2758            let item = self
2759                .active_item(cx)
2760                .and_then(|item| item.to_followable_item_handle(cx));
2761            if let Some(item) = item {
2762                is_project_item = item.is_project_item(cx);
2763                update = proto::UpdateActiveView {
2764                    id: item
2765                        .remote_id(&self.app_state.client, cx)
2766                        .map(|id| id.to_proto()),
2767                    leader_id: self.leader_for_pane(&self.active_pane),
2768                };
2769            }
2770        }
2771
2772        if update.id != self.last_active_view_id {
2773            self.last_active_view_id = update.id.clone();
2774            self.update_followers(
2775                is_project_item,
2776                proto::update_followers::Variant::UpdateActiveView(update),
2777                cx,
2778            );
2779        }
2780    }
2781
2782    fn update_followers(
2783        &self,
2784        project_only: bool,
2785        update: proto::update_followers::Variant,
2786        cx: &mut WindowContext,
2787    ) -> Option<()> {
2788        let project_id = if project_only {
2789            self.project.read(cx).remote_id()
2790        } else {
2791            None
2792        };
2793        self.app_state().workspace_store.update(cx, |store, cx| {
2794            store.update_followers(project_id, update, cx)
2795        })
2796    }
2797
2798    pub fn leader_for_pane(&self, pane: &View<Pane>) -> Option<PeerId> {
2799        self.follower_states.get(pane).map(|state| state.leader_id)
2800    }
2801
2802    fn leader_updated(&mut self, leader_id: PeerId, cx: &mut ViewContext<Self>) -> Option<()> {
2803        cx.notify();
2804
2805        let call = self.active_call()?;
2806        let room = call.read(cx).room()?.read(cx);
2807        let participant = room.remote_participant_for_peer_id(leader_id)?;
2808        let mut items_to_activate = Vec::new();
2809
2810        let leader_in_this_app;
2811        let leader_in_this_project;
2812        match participant.location {
2813            call::ParticipantLocation::SharedProject { project_id } => {
2814                leader_in_this_app = true;
2815                leader_in_this_project = Some(project_id) == self.project.read(cx).remote_id();
2816            }
2817            call::ParticipantLocation::UnsharedProject => {
2818                leader_in_this_app = true;
2819                leader_in_this_project = false;
2820            }
2821            call::ParticipantLocation::External => {
2822                leader_in_this_app = false;
2823                leader_in_this_project = false;
2824            }
2825        };
2826
2827        for (pane, state) in &self.follower_states {
2828            if state.leader_id != leader_id {
2829                continue;
2830            }
2831            if let (Some(active_view_id), true) = (state.active_view_id, leader_in_this_app) {
2832                if let Some(item) = state.items_by_leader_view_id.get(&active_view_id) {
2833                    if leader_in_this_project || !item.is_project_item(cx) {
2834                        items_to_activate.push((pane.clone(), item.boxed_clone()));
2835                    }
2836                } else {
2837                    log::warn!(
2838                        "unknown view id {:?} for leader {:?}",
2839                        active_view_id,
2840                        leader_id
2841                    );
2842                }
2843                continue;
2844            }
2845
2846            if let Some(shared_screen) = self.shared_screen_for_peer(leader_id, pane, cx) {
2847                items_to_activate.push((pane.clone(), Box::new(shared_screen)));
2848            }
2849        }
2850
2851        for (pane, item) in items_to_activate {
2852            let pane_was_focused = pane.read(cx).has_focus(cx);
2853            if let Some(index) = pane.update(cx, |pane, _| pane.index_for_item(item.as_ref())) {
2854                pane.update(cx, |pane, cx| pane.activate_item(index, false, false, cx));
2855            } else {
2856                pane.update(cx, |pane, cx| {
2857                    pane.add_item(item.boxed_clone(), false, false, None, cx)
2858                });
2859            }
2860
2861            if pane_was_focused {
2862                pane.update(cx, |pane, cx| pane.focus_active_item(cx));
2863            }
2864        }
2865
2866        None
2867    }
2868
2869    fn shared_screen_for_peer(
2870        &self,
2871        peer_id: PeerId,
2872        pane: &View<Pane>,
2873        cx: &mut ViewContext<Self>,
2874    ) -> Option<View<SharedScreen>> {
2875        let call = self.active_call()?;
2876        let room = call.read(cx).room()?.read(cx);
2877        let participant = room.remote_participant_for_peer_id(peer_id)?;
2878        let track = participant.video_tracks.values().next()?.clone();
2879        let user = participant.user.clone();
2880
2881        for item in pane.read(cx).items_of_type::<SharedScreen>() {
2882            if item.read(cx).peer_id == peer_id {
2883                return Some(item);
2884            }
2885        }
2886
2887        Some(cx.build_view(|cx| SharedScreen::new(&track, peer_id, user.clone(), cx)))
2888    }
2889
2890    pub fn on_window_activation_changed(&mut self, cx: &mut ViewContext<Self>) {
2891        if cx.is_window_active() {
2892            self.update_active_view_for_followers(cx);
2893            cx.background_executor()
2894                .spawn(persistence::DB.update_timestamp(self.database_id()))
2895                .detach();
2896        } else {
2897            for pane in &self.panes {
2898                pane.update(cx, |pane, cx| {
2899                    if let Some(item) = pane.active_item() {
2900                        item.workspace_deactivated(cx);
2901                    }
2902                    if matches!(
2903                        WorkspaceSettings::get_global(cx).autosave,
2904                        AutosaveSetting::OnWindowChange | AutosaveSetting::OnFocusChange
2905                    ) {
2906                        for item in pane.items() {
2907                            Pane::autosave_item(item.as_ref(), self.project.clone(), cx)
2908                                .detach_and_log_err(cx);
2909                        }
2910                    }
2911                });
2912            }
2913        }
2914    }
2915
2916    fn active_call(&self) -> Option<&Model<ActiveCall>> {
2917        self.active_call.as_ref().map(|(call, _)| call)
2918    }
2919
2920    fn on_active_call_event(
2921        &mut self,
2922        _: Model<ActiveCall>,
2923        event: &call::room::Event,
2924        cx: &mut ViewContext<Self>,
2925    ) {
2926        match event {
2927            call::room::Event::ParticipantLocationChanged { participant_id }
2928            | call::room::Event::RemoteVideoTracksChanged { participant_id } => {
2929                self.leader_updated(*participant_id, cx);
2930            }
2931            _ => {}
2932        }
2933    }
2934
2935    pub fn database_id(&self) -> WorkspaceId {
2936        self.database_id
2937    }
2938
2939    fn location(&self, cx: &AppContext) -> Option<WorkspaceLocation> {
2940        let project = self.project().read(cx);
2941
2942        if project.is_local() {
2943            Some(
2944                project
2945                    .visible_worktrees(cx)
2946                    .map(|worktree| worktree.read(cx).abs_path())
2947                    .collect::<Vec<_>>()
2948                    .into(),
2949            )
2950        } else {
2951            None
2952        }
2953    }
2954
2955    fn remove_panes(&mut self, member: Member, cx: &mut ViewContext<Workspace>) {
2956        match member {
2957            Member::Axis(PaneAxis { members, .. }) => {
2958                for child in members.iter() {
2959                    self.remove_panes(child.clone(), cx)
2960                }
2961            }
2962            Member::Pane(pane) => {
2963                self.force_remove_pane(&pane, cx);
2964            }
2965        }
2966    }
2967
2968    fn force_remove_pane(&mut self, pane: &View<Pane>, cx: &mut ViewContext<Workspace>) {
2969        self.panes.retain(|p| p != pane);
2970        self.panes
2971            .last()
2972            .unwrap()
2973            .update(cx, |pane, cx| pane.focus(cx));
2974        if self.last_active_center_pane == Some(pane.downgrade()) {
2975            self.last_active_center_pane = None;
2976        }
2977        cx.notify();
2978    }
2979
2980    fn schedule_serialize(&mut self, cx: &mut ViewContext<Self>) {
2981        self._schedule_serialize = Some(cx.spawn(|this, mut cx| async move {
2982            cx.background_executor()
2983                .timer(Duration::from_millis(100))
2984                .await;
2985            this.update(&mut cx, |this, cx| this.serialize_workspace(cx))
2986                .log_err();
2987        }));
2988    }
2989
2990    fn serialize_workspace(&self, cx: &mut ViewContext<Self>) {
2991        fn serialize_pane_handle(pane_handle: &View<Pane>, cx: &WindowContext) -> SerializedPane {
2992            let (items, active) = {
2993                let pane = pane_handle.read(cx);
2994                let active_item_id = pane.active_item().map(|item| item.item_id());
2995                (
2996                    pane.items()
2997                        .filter_map(|item_handle| {
2998                            Some(SerializedItem {
2999                                kind: Arc::from(item_handle.serialized_item_kind()?),
3000                                item_id: item_handle.item_id().as_u64(),
3001                                active: Some(item_handle.item_id()) == active_item_id,
3002                            })
3003                        })
3004                        .collect::<Vec<_>>(),
3005                    pane.has_focus(cx),
3006                )
3007            };
3008
3009            SerializedPane::new(items, active)
3010        }
3011
3012        fn build_serialized_pane_group(
3013            pane_group: &Member,
3014            cx: &WindowContext,
3015        ) -> SerializedPaneGroup {
3016            match pane_group {
3017                Member::Axis(PaneAxis {
3018                    axis,
3019                    members,
3020                    flexes,
3021                    bounding_boxes: _,
3022                }) => SerializedPaneGroup::Group {
3023                    axis: *axis,
3024                    children: members
3025                        .iter()
3026                        .map(|member| build_serialized_pane_group(member, cx))
3027                        .collect::<Vec<_>>(),
3028                    flexes: Some(flexes.lock().clone()),
3029                },
3030                Member::Pane(pane_handle) => {
3031                    SerializedPaneGroup::Pane(serialize_pane_handle(&pane_handle, cx))
3032                }
3033            }
3034        }
3035
3036        fn build_serialized_docks(
3037            this: &Workspace,
3038            cx: &mut ViewContext<Workspace>,
3039        ) -> DockStructure {
3040            let left_dock = this.left_dock.read(cx);
3041            let left_visible = left_dock.is_open();
3042            let left_active_panel = left_dock
3043                .visible_panel()
3044                .and_then(|panel| Some(panel.persistent_name().to_string()));
3045            let left_dock_zoom = left_dock
3046                .visible_panel()
3047                .map(|panel| panel.is_zoomed(cx))
3048                .unwrap_or(false);
3049
3050            let right_dock = this.right_dock.read(cx);
3051            let right_visible = right_dock.is_open();
3052            let right_active_panel = right_dock
3053                .visible_panel()
3054                .and_then(|panel| Some(panel.persistent_name().to_string()));
3055            let right_dock_zoom = right_dock
3056                .visible_panel()
3057                .map(|panel| panel.is_zoomed(cx))
3058                .unwrap_or(false);
3059
3060            let bottom_dock = this.bottom_dock.read(cx);
3061            let bottom_visible = bottom_dock.is_open();
3062            let bottom_active_panel = bottom_dock
3063                .visible_panel()
3064                .and_then(|panel| Some(panel.persistent_name().to_string()));
3065            let bottom_dock_zoom = bottom_dock
3066                .visible_panel()
3067                .map(|panel| panel.is_zoomed(cx))
3068                .unwrap_or(false);
3069
3070            DockStructure {
3071                left: DockData {
3072                    visible: left_visible,
3073                    active_panel: left_active_panel,
3074                    zoom: left_dock_zoom,
3075                },
3076                right: DockData {
3077                    visible: right_visible,
3078                    active_panel: right_active_panel,
3079                    zoom: right_dock_zoom,
3080                },
3081                bottom: DockData {
3082                    visible: bottom_visible,
3083                    active_panel: bottom_active_panel,
3084                    zoom: bottom_dock_zoom,
3085                },
3086            }
3087        }
3088
3089        if let Some(location) = self.location(cx) {
3090            // Load bearing special case:
3091            //  - with_local_workspace() relies on this to not have other stuff open
3092            //    when you open your log
3093            if !location.paths().is_empty() {
3094                let center_group = build_serialized_pane_group(&self.center.root, cx);
3095                let docks = build_serialized_docks(self, cx);
3096
3097                let serialized_workspace = SerializedWorkspace {
3098                    id: self.database_id,
3099                    location,
3100                    center_group,
3101                    bounds: Default::default(),
3102                    display: Default::default(),
3103                    docks,
3104                };
3105
3106                cx.spawn(|_, _| persistence::DB.save_workspace(serialized_workspace))
3107                    .detach();
3108            }
3109        }
3110    }
3111
3112    pub(crate) fn load_workspace(
3113        serialized_workspace: SerializedWorkspace,
3114        paths_to_open: Vec<Option<ProjectPath>>,
3115        cx: &mut ViewContext<Workspace>,
3116    ) -> Task<Result<Vec<Option<Box<dyn ItemHandle>>>>> {
3117        cx.spawn(|workspace, mut cx| async move {
3118            let (project, old_center_pane) = workspace.update(&mut cx, |workspace, _| {
3119                (
3120                    workspace.project().clone(),
3121                    workspace.last_active_center_pane.clone(),
3122                )
3123            })?;
3124
3125            let mut center_group = None;
3126            let mut center_items = None;
3127
3128            // Traverse the splits tree and add to things
3129            if let Some((group, active_pane, items)) = serialized_workspace
3130                .center_group
3131                .deserialize(
3132                    &project,
3133                    serialized_workspace.id,
3134                    workspace.clone(),
3135                    &mut cx,
3136                )
3137                .await
3138            {
3139                center_items = Some(items);
3140                center_group = Some((group, active_pane))
3141            }
3142
3143            let mut items_by_project_path = cx.update(|_, cx| {
3144                center_items
3145                    .unwrap_or_default()
3146                    .into_iter()
3147                    .filter_map(|item| {
3148                        let item = item?;
3149                        let project_path = item.project_path(cx)?;
3150                        Some((project_path, item))
3151                    })
3152                    .collect::<HashMap<_, _>>()
3153            })?;
3154
3155            let opened_items = paths_to_open
3156                .into_iter()
3157                .map(|path_to_open| {
3158                    path_to_open
3159                        .and_then(|path_to_open| items_by_project_path.remove(&path_to_open))
3160                })
3161                .collect::<Vec<_>>();
3162
3163            // Remove old panes from workspace panes list
3164            workspace.update(&mut cx, |workspace, cx| {
3165                if let Some((center_group, active_pane)) = center_group {
3166                    workspace.remove_panes(workspace.center.root.clone(), cx);
3167
3168                    // Swap workspace center group
3169                    workspace.center = PaneGroup::with_root(center_group);
3170                    workspace.last_active_center_pane = active_pane.as_ref().map(|p| p.downgrade());
3171                    if let Some(active_pane) = active_pane {
3172                        workspace.active_pane = active_pane;
3173                        cx.focus_self();
3174                    } else {
3175                        workspace.active_pane = workspace.center.first_pane().clone();
3176                    }
3177                }
3178
3179                let docks = serialized_workspace.docks;
3180                workspace.left_dock.update(cx, |dock, cx| {
3181                    dock.set_open(docks.left.visible, cx);
3182                    if let Some(active_panel) = docks.left.active_panel {
3183                        if let Some(ix) = dock.panel_index_for_persistent_name(&active_panel, cx) {
3184                            dock.activate_panel(ix, cx);
3185                        }
3186                    }
3187                    dock.active_panel()
3188                        .map(|panel| panel.set_zoomed(docks.left.zoom, cx));
3189                    if docks.left.visible && docks.left.zoom {
3190                        cx.focus_self()
3191                    }
3192                });
3193                // TODO: I think the bug is that setting zoom or active undoes the bottom zoom or something
3194                workspace.right_dock.update(cx, |dock, cx| {
3195                    dock.set_open(docks.right.visible, cx);
3196                    if let Some(active_panel) = docks.right.active_panel {
3197                        if let Some(ix) = dock.panel_index_for_persistent_name(&active_panel, cx) {
3198                            dock.activate_panel(ix, cx);
3199                        }
3200                    }
3201                    dock.active_panel()
3202                        .map(|panel| panel.set_zoomed(docks.right.zoom, cx));
3203
3204                    if docks.right.visible && docks.right.zoom {
3205                        cx.focus_self()
3206                    }
3207                });
3208                workspace.bottom_dock.update(cx, |dock, cx| {
3209                    dock.set_open(docks.bottom.visible, cx);
3210                    if let Some(active_panel) = docks.bottom.active_panel {
3211                        if let Some(ix) = dock.panel_index_for_persistent_name(&active_panel, cx) {
3212                            dock.activate_panel(ix, cx);
3213                        }
3214                    }
3215
3216                    dock.active_panel()
3217                        .map(|panel| panel.set_zoomed(docks.bottom.zoom, cx));
3218
3219                    if docks.bottom.visible && docks.bottom.zoom {
3220                        cx.focus_self()
3221                    }
3222                });
3223
3224                cx.notify();
3225            })?;
3226
3227            // Serialize ourself to make sure our timestamps and any pane / item changes are replicated
3228            workspace.update(&mut cx, |workspace, cx| workspace.serialize_workspace(cx))?;
3229
3230            Ok(opened_items)
3231        })
3232    }
3233
3234    fn actions(&self, div: Div, cx: &mut ViewContext<Self>) -> Div {
3235        self.add_workspace_actions_listeners(div, cx)
3236            .on_action(cx.listener(Self::close_inactive_items_and_panes))
3237            .on_action(cx.listener(Self::close_all_items_and_panes))
3238            .on_action(cx.listener(Self::save_all))
3239            .on_action(cx.listener(Self::add_folder_to_project))
3240            .on_action(cx.listener(|workspace, _: &Unfollow, cx| {
3241                let pane = workspace.active_pane().clone();
3242                workspace.unfollow(&pane, cx);
3243            }))
3244            .on_action(cx.listener(|workspace, action: &Save, cx| {
3245                workspace
3246                    .save_active_item(action.save_intent.unwrap_or(SaveIntent::Save), cx)
3247                    .detach_and_log_err(cx);
3248            }))
3249            .on_action(cx.listener(|workspace, _: &SaveAs, cx| {
3250                workspace
3251                    .save_active_item(SaveIntent::SaveAs, cx)
3252                    .detach_and_log_err(cx);
3253            }))
3254            .on_action(cx.listener(|workspace, _: &ActivatePreviousPane, cx| {
3255                workspace.activate_previous_pane(cx)
3256            }))
3257            .on_action(
3258                cx.listener(|workspace, _: &ActivateNextPane, cx| workspace.activate_next_pane(cx)),
3259            )
3260            .on_action(
3261                cx.listener(|workspace, action: &ActivatePaneInDirection, cx| {
3262                    workspace.activate_pane_in_direction(action.0, cx)
3263                }),
3264            )
3265            .on_action(cx.listener(|workspace, action: &SwapPaneInDirection, cx| {
3266                workspace.swap_pane_in_direction(action.0, cx)
3267            }))
3268            .on_action(cx.listener(|this, e: &ToggleLeftDock, cx| {
3269                this.toggle_dock(DockPosition::Left, cx);
3270            }))
3271            .on_action(
3272                cx.listener(|workspace: &mut Workspace, _: &ToggleRightDock, cx| {
3273                    workspace.toggle_dock(DockPosition::Right, cx);
3274                }),
3275            )
3276            .on_action(
3277                cx.listener(|workspace: &mut Workspace, _: &ToggleBottomDock, cx| {
3278                    workspace.toggle_dock(DockPosition::Bottom, cx);
3279                }),
3280            )
3281            .on_action(
3282                cx.listener(|workspace: &mut Workspace, _: &CloseAllDocks, cx| {
3283                    workspace.close_all_docks(cx);
3284                }),
3285            )
3286            .on_action(cx.listener(Workspace::open))
3287            .on_action(cx.listener(Workspace::close_window))
3288
3289        //     cx.add_action(Workspace::activate_pane_at_index);
3290        //     cx.add_action(|workspace: &mut Workspace, _: &ReopenClosedItem, cx| {
3291        //         workspace.reopen_closed_item(cx).detach();
3292        //     });
3293        //     cx.add_action(|workspace: &mut Workspace, _: &GoBack, cx| {
3294        //         workspace
3295        //             .go_back(workspace.active_pane().downgrade(), cx)
3296        //             .detach();
3297        //     });
3298        //     cx.add_action(|workspace: &mut Workspace, _: &GoForward, cx| {
3299        //         workspace
3300        //             .go_forward(workspace.active_pane().downgrade(), cx)
3301        //             .detach();
3302        //     });
3303
3304        //     cx.add_action(|_: &mut Workspace, _: &install_cli::Install, cx| {
3305        //         cx.spawn(|workspace, mut cx| async move {
3306        //             let err = install_cli::install_cli(&cx)
3307        //                 .await
3308        //                 .context("Failed to create CLI symlink");
3309
3310        //             workspace.update(&mut cx, |workspace, cx| {
3311        //                 if matches!(err, Err(_)) {
3312        //                     err.notify_err(workspace, cx);
3313        //                 } else {
3314        //                     workspace.show_notification(1, cx, |cx| {
3315        //                         cx.build_view(|_| {
3316        //                             MessageNotification::new("Successfully installed the `zed` binary")
3317        //                         })
3318        //                     });
3319        //                 }
3320        //             })
3321        //         })
3322        //         .detach();
3323        //     });
3324    }
3325
3326    #[cfg(any(test, feature = "test-support"))]
3327    pub fn test_new(project: Model<Project>, cx: &mut ViewContext<Self>) -> Self {
3328        use node_runtime::FakeNodeRuntime;
3329
3330        let client = project.read(cx).client();
3331        let user_store = project.read(cx).user_store();
3332
3333        let workspace_store = cx.build_model(|cx| WorkspaceStore::new(client.clone(), cx));
3334        let app_state = Arc::new(AppState {
3335            languages: project.read(cx).languages().clone(),
3336            workspace_store,
3337            client,
3338            user_store,
3339            fs: project.read(cx).fs().clone(),
3340            build_window_options: |_, _, _| Default::default(),
3341            node_runtime: FakeNodeRuntime::new(),
3342        });
3343        let workspace = Self::new(0, project, app_state, cx);
3344        workspace.active_pane.update(cx, |pane, cx| pane.focus(cx));
3345        workspace
3346    }
3347
3348    //     fn render_dock(&self, position: DockPosition, cx: &WindowContext) -> Option<AnyElement<Self>> {
3349    //         let dock = match position {
3350    //             DockPosition::Left => &self.left_dock,
3351    //             DockPosition::Right => &self.right_dock,
3352    //             DockPosition::Bottom => &self.bottom_dock,
3353    //         };
3354    //         let active_panel = dock.read(cx).visible_panel()?;
3355    //         let element = if Some(active_panel.id()) == self.zoomed.as_ref().map(|zoomed| zoomed.id()) {
3356    //             dock.read(cx).render_placeholder(cx)
3357    //         } else {
3358    //             ChildView::new(dock, cx).into_any()
3359    //         };
3360
3361    //         Some(
3362    //             element
3363    //                 .constrained()
3364    //                 .dynamically(move |constraint, _, cx| match position {
3365    //                     DockPosition::Left | DockPosition::Right => SizeConstraint::new(
3366    //                         Vector2F::new(20., constraint.min.y()),
3367    //                         Vector2F::new(cx.window_size().x() * 0.8, constraint.max.y()),
3368    //                     ),
3369    //                     DockPosition::Bottom => SizeConstraint::new(
3370    //                         Vector2F::new(constraint.min.x(), 20.),
3371    //                         Vector2F::new(constraint.max.x(), cx.window_size().y() * 0.8),
3372    //                     ),
3373    //                 })
3374    //                 .into_any(),
3375    //         )
3376    //     }
3377    // }
3378    pub fn register_action<A: Action>(
3379        &mut self,
3380        callback: impl Fn(&mut Self, &A, &mut ViewContext<Self>) + 'static,
3381    ) -> &mut Self {
3382        let callback = Arc::new(callback);
3383
3384        self.workspace_actions.push(Box::new(move |div, cx| {
3385            let callback = callback.clone();
3386            div.on_action(
3387                cx.listener(move |workspace, event, cx| (callback.clone())(workspace, event, cx)),
3388            )
3389        }));
3390        self
3391    }
3392
3393    fn add_workspace_actions_listeners(&self, mut div: Div, cx: &mut ViewContext<Self>) -> Div {
3394        let mut div = div
3395            .on_action(cx.listener(Self::close_inactive_items_and_panes))
3396            .on_action(cx.listener(Self::close_all_items_and_panes))
3397            .on_action(cx.listener(Self::add_folder_to_project))
3398            .on_action(cx.listener(Self::save_all))
3399            .on_action(cx.listener(Self::open));
3400        for action in self.workspace_actions.iter() {
3401            div = (action)(div, cx)
3402        }
3403        div
3404    }
3405
3406    pub fn active_modal<V: ManagedView + 'static>(
3407        &mut self,
3408        cx: &ViewContext<Self>,
3409    ) -> Option<View<V>> {
3410        self.modal_layer.read(cx).active_modal()
3411    }
3412
3413    pub fn toggle_modal<V: ManagedView, B>(&mut self, cx: &mut ViewContext<Self>, build: B)
3414    where
3415        B: FnOnce(&mut ViewContext<V>) -> V,
3416    {
3417        self.modal_layer
3418            .update(cx, |modal_layer, cx| modal_layer.toggle_modal(cx, build))
3419    }
3420}
3421
3422fn window_bounds_env_override(cx: &AsyncAppContext) -> Option<WindowBounds> {
3423    let display_origin = cx
3424        .update(|cx| Some(cx.displays().first()?.bounds().origin))
3425        .ok()??;
3426    ZED_WINDOW_POSITION
3427        .zip(*ZED_WINDOW_SIZE)
3428        .map(|(position, size)| {
3429            WindowBounds::Fixed(Bounds {
3430                origin: display_origin + position,
3431                size,
3432            })
3433        })
3434}
3435
3436fn open_items(
3437    serialized_workspace: Option<SerializedWorkspace>,
3438    mut project_paths_to_open: Vec<(PathBuf, Option<ProjectPath>)>,
3439    app_state: Arc<AppState>,
3440    cx: &mut ViewContext<Workspace>,
3441) -> impl 'static + Future<Output = Result<Vec<Option<Result<Box<dyn ItemHandle>>>>>> {
3442    let restored_items = serialized_workspace.map(|serialized_workspace| {
3443        Workspace::load_workspace(
3444            serialized_workspace,
3445            project_paths_to_open
3446                .iter()
3447                .map(|(_, project_path)| project_path)
3448                .cloned()
3449                .collect(),
3450            cx,
3451        )
3452    });
3453
3454    cx.spawn(|workspace, mut cx| async move {
3455        let mut opened_items = Vec::with_capacity(project_paths_to_open.len());
3456
3457        if let Some(restored_items) = restored_items {
3458            let restored_items = restored_items.await?;
3459
3460            let restored_project_paths = restored_items
3461                .iter()
3462                .filter_map(|item| {
3463                    cx.update(|_, cx| item.as_ref()?.project_path(cx))
3464                        .ok()
3465                        .flatten()
3466                })
3467                .collect::<HashSet<_>>();
3468
3469            for restored_item in restored_items {
3470                opened_items.push(restored_item.map(Ok));
3471            }
3472
3473            project_paths_to_open
3474                .iter_mut()
3475                .for_each(|(_, project_path)| {
3476                    if let Some(project_path_to_open) = project_path {
3477                        if restored_project_paths.contains(project_path_to_open) {
3478                            *project_path = None;
3479                        }
3480                    }
3481                });
3482        } else {
3483            for _ in 0..project_paths_to_open.len() {
3484                opened_items.push(None);
3485            }
3486        }
3487        assert!(opened_items.len() == project_paths_to_open.len());
3488
3489        let tasks =
3490            project_paths_to_open
3491                .into_iter()
3492                .enumerate()
3493                .map(|(i, (abs_path, project_path))| {
3494                    let workspace = workspace.clone();
3495                    cx.spawn(|mut cx| {
3496                        let fs = app_state.fs.clone();
3497                        async move {
3498                            let file_project_path = project_path?;
3499                            if fs.is_file(&abs_path).await {
3500                                Some((
3501                                    i,
3502                                    workspace
3503                                        .update(&mut cx, |workspace, cx| {
3504                                            workspace.open_path(file_project_path, None, true, cx)
3505                                        })
3506                                        .log_err()?
3507                                        .await,
3508                                ))
3509                            } else {
3510                                None
3511                            }
3512                        }
3513                    })
3514                });
3515
3516        let tasks = tasks.collect::<Vec<_>>();
3517
3518        let tasks = futures::future::join_all(tasks.into_iter());
3519        for maybe_opened_path in tasks.await.into_iter() {
3520            if let Some((i, path_open_result)) = maybe_opened_path {
3521                opened_items[i] = Some(path_open_result);
3522            }
3523        }
3524
3525        Ok(opened_items)
3526    })
3527}
3528
3529fn notify_if_database_failed(workspace: WindowHandle<Workspace>, cx: &mut AsyncAppContext) {
3530    const REPORT_ISSUE_URL: &str ="https://github.com/zed-industries/community/issues/new?assignees=&labels=defect%2Ctriage&template=2_bug_report.yml";
3531
3532    workspace
3533        .update(cx, |workspace, cx| {
3534            if (*db::ALL_FILE_DB_FAILED).load(std::sync::atomic::Ordering::Acquire) {
3535                workspace.show_notification_once(0, cx, |cx| {
3536                    cx.build_view(|_| {
3537                        MessageNotification::new("Failed to load the database file.")
3538                            .with_click_message("Click to let us know about this error")
3539                            .on_click(|cx| cx.open_url(REPORT_ISSUE_URL))
3540                    })
3541                });
3542            }
3543        })
3544        .log_err();
3545}
3546
3547impl FocusableView for Workspace {
3548    fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
3549        self.active_pane.focus_handle(cx)
3550    }
3551}
3552
3553impl Render for Workspace {
3554    type Element = Div;
3555
3556    fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
3557        let mut context = KeyContext::default();
3558        context.add("Workspace");
3559
3560        let (ui_font, ui_font_size) = {
3561            let theme_settings = ThemeSettings::get_global(cx);
3562            (
3563                theme_settings.ui_font.family.clone(),
3564                theme_settings.ui_font_size.clone(),
3565            )
3566        };
3567
3568        cx.set_rem_size(ui_font_size);
3569
3570        self.actions(div(), cx)
3571            .key_context(context)
3572            .relative()
3573            .size_full()
3574            .flex()
3575            .flex_col()
3576            .font(ui_font)
3577            .gap_0()
3578            .justify_start()
3579            .items_start()
3580            .text_color(cx.theme().colors().text)
3581            .bg(cx.theme().colors().background)
3582            .border()
3583            .border_color(cx.theme().colors().border)
3584            .children(self.titlebar_item.clone())
3585            .child(
3586                div()
3587                    .id("workspace")
3588                    .relative()
3589                    .flex_1()
3590                    .w_full()
3591                    .flex()
3592                    .overflow_hidden()
3593                    .border_t()
3594                    .border_b()
3595                    .border_color(cx.theme().colors().border)
3596                    .child(self.modal_layer.clone())
3597                    .child(
3598                        div()
3599                            .flex()
3600                            .flex_row()
3601                            .flex_1()
3602                            .h_full()
3603                            // Left Dock
3604                            .child(
3605                                div()
3606                                    .flex()
3607                                    .flex_none()
3608                                    .overflow_hidden()
3609                                    .child(self.left_dock.clone()),
3610                            )
3611                            // Panes
3612                            .child(
3613                                div()
3614                                    .flex()
3615                                    .flex_col()
3616                                    .flex_1()
3617                                    .child(self.center.render(
3618                                        &self.project,
3619                                        &self.follower_states,
3620                                        self.active_call(),
3621                                        &self.active_pane,
3622                                        self.zoomed.as_ref(),
3623                                        &self.app_state,
3624                                        cx,
3625                                    ))
3626                                    .child(self.bottom_dock.clone()),
3627                            )
3628                            // Right Dock
3629                            .child(
3630                                div()
3631                                    .flex()
3632                                    .flex_none()
3633                                    .overflow_hidden()
3634                                    .child(self.right_dock.clone()),
3635                            ),
3636                    )
3637                    .children(self.render_notifications(cx)),
3638            )
3639            .child(self.status_bar.clone())
3640    }
3641}
3642
3643// impl View for Workspace {
3644
3645//     fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
3646//         let theme = theme::current(cx).clone();
3647//         Stack::new()
3648//             .with_child(
3649//                 Flex::column()
3650//                     .with_child(self.render_titlebar(&theme, cx))
3651//                     .with_child(
3652//                         Stack::new()
3653//                             .with_child({
3654//                                 let project = self.project.clone();
3655//                                 Flex::row()
3656//                                     .with_children(self.render_dock(DockPosition::Left, cx))
3657//                                     .with_child(
3658//                                         Flex::column()
3659//                                             .with_child(
3660//                                                 FlexItem::new(
3661//                                                     self.center.render(
3662//                                                         &project,
3663//                                                         &theme,
3664//                                                         &self.follower_states,
3665//                                                         self.active_call(),
3666//                                                         self.active_pane(),
3667//                                                         self.zoomed
3668//                                                             .as_ref()
3669//                                                             .and_then(|zoomed| zoomed.upgrade(cx))
3670//                                                             .as_ref(),
3671//                                                         &self.app_state,
3672//                                                         cx,
3673//                                                     ),
3674//                                                 )
3675//                                                 .flex(1., true),
3676//                                             )
3677//                                             .with_children(
3678//                                                 self.render_dock(DockPosition::Bottom, cx),
3679//                                             )
3680//                                             .flex(1., true),
3681//                                     )
3682//                                     .with_children(self.render_dock(DockPosition::Right, cx))
3683//                             })
3684//                             .with_child(Overlay::new(
3685//                                 Stack::new()
3686//                                     .with_children(self.zoomed.as_ref().and_then(|zoomed| {
3687//                                         enum ZoomBackground {}
3688//                                         let zoomed = zoomed.upgrade(cx)?;
3689
3690//                                         let mut foreground_style =
3691//                                             theme.workspace.zoomed_pane_foreground;
3692//                                         if let Some(zoomed_dock_position) = self.zoomed_position {
3693//                                             foreground_style =
3694//                                                 theme.workspace.zoomed_panel_foreground;
3695//                                             let margin = foreground_style.margin.top;
3696//                                             let border = foreground_style.border.top;
3697
3698//                                             // Only include a margin and border on the opposite side.
3699//                                             foreground_style.margin.top = 0.;
3700//                                             foreground_style.margin.left = 0.;
3701//                                             foreground_style.margin.bottom = 0.;
3702//                                             foreground_style.margin.right = 0.;
3703//                                             foreground_style.border.top = false;
3704//                                             foreground_style.border.left = false;
3705//                                             foreground_style.border.bottom = false;
3706//                                             foreground_style.border.right = false;
3707//                                             match zoomed_dock_position {
3708//                                                 DockPosition::Left => {
3709//                                                     foreground_style.margin.right = margin;
3710//                                                     foreground_style.border.right = border;
3711//                                                 }
3712//                                                 DockPosition::Right => {
3713//                                                     foreground_style.margin.left = margin;
3714//                                                     foreground_style.border.left = border;
3715//                                                 }
3716//                                                 DockPosition::Bottom => {
3717//                                                     foreground_style.margin.top = margin;
3718//                                                     foreground_style.border.top = border;
3719//                                                 }
3720//                                             }
3721//                                         }
3722
3723//                                         Some(
3724//                                             ChildView::new(&zoomed, cx)
3725//                                                 .contained()
3726//                                                 .with_style(foreground_style)
3727//                                                 .aligned()
3728//                                                 .contained()
3729//                                                 .with_style(theme.workspace.zoomed_background)
3730//                                                 .mouse::<ZoomBackground>(0)
3731//                                                 .capture_all()
3732//                                                 .on_down(
3733//                                                     MouseButton::Left,
3734//                                                     |_, this: &mut Self, cx| {
3735//                                                         this.zoom_out(cx);
3736//                                                     },
3737//                                                 ),
3738//                                         )
3739//                                     }))
3740//                                     .with_children(self.modal.as_ref().map(|modal| {
3741//                                         // Prevent clicks within the modal from falling
3742//                                         // through to the rest of the workspace.
3743//                                         enum ModalBackground {}
3744//                                         MouseEventHandler::new::<ModalBackground, _>(
3745//                                             0,
3746//                                             cx,
3747//                                             |_, cx| ChildView::new(modal.view.as_any(), cx),
3748//                                         )
3749//                                         .on_click(MouseButton::Left, |_, _, _| {})
3750//                                         .contained()
3751//                                         .with_style(theme.workspace.modal)
3752//                                         .aligned()
3753//                                         .top()
3754//                                     }))
3755//                                     .with_children(self.render_notifications(&theme.workspace, cx)),
3756//                             ))
3757//                             .provide_resize_bounds::<WorkspaceBounds>()
3758//                             .flex(1.0, true),
3759//                     )
3760//                     .with_child(ChildView::new(&self.status_bar, cx))
3761//                     .contained()
3762//                     .with_background_color(theme.workspace.background),
3763//             )
3764//             .with_children(DragAndDrop::render(cx))
3765//             .with_children(self.render_disconnected_overlay(cx))
3766//             .into_any_named("workspace")
3767//     }
3768
3769//     fn modifiers_changed(&mut self, e: &ModifiersChangedEvent, cx: &mut ViewContext<Self>) -> bool {
3770//         DragAndDrop::<Workspace>::update_modifiers(e.modifiers, cx)
3771//     }
3772// }
3773
3774impl WorkspaceStore {
3775    pub fn new(client: Arc<Client>, cx: &mut ModelContext<Self>) -> Self {
3776        Self {
3777            workspaces: Default::default(),
3778            followers: Default::default(),
3779            _subscriptions: vec![
3780                client.add_request_handler(cx.weak_model(), Self::handle_follow),
3781                client.add_message_handler(cx.weak_model(), Self::handle_unfollow),
3782                client.add_message_handler(cx.weak_model(), Self::handle_update_followers),
3783            ],
3784            client,
3785        }
3786    }
3787
3788    pub fn update_followers(
3789        &self,
3790        project_id: Option<u64>,
3791        update: proto::update_followers::Variant,
3792        cx: &AppContext,
3793    ) -> Option<()> {
3794        if !cx.has_global::<Model<ActiveCall>>() {
3795            return None;
3796        }
3797
3798        let room_id = ActiveCall::global(cx).read(cx).room()?.read(cx).id();
3799        let follower_ids: Vec<_> = self
3800            .followers
3801            .iter()
3802            .filter_map(|follower| {
3803                if follower.project_id == project_id || project_id.is_none() {
3804                    Some(follower.peer_id.into())
3805                } else {
3806                    None
3807                }
3808            })
3809            .collect();
3810        if follower_ids.is_empty() {
3811            return None;
3812        }
3813        self.client
3814            .send(proto::UpdateFollowers {
3815                room_id,
3816                project_id,
3817                follower_ids,
3818                variant: Some(update),
3819            })
3820            .log_err()
3821    }
3822
3823    pub async fn handle_follow(
3824        this: Model<Self>,
3825        envelope: TypedEnvelope<proto::Follow>,
3826        _: Arc<Client>,
3827        mut cx: AsyncAppContext,
3828    ) -> Result<proto::FollowResponse> {
3829        this.update(&mut cx, |this, cx| {
3830            let follower = Follower {
3831                project_id: envelope.payload.project_id,
3832                peer_id: envelope.original_sender_id()?,
3833            };
3834            let active_project = ActiveCall::global(cx).read(cx).location().cloned();
3835
3836            let mut response = proto::FollowResponse::default();
3837            for workspace in &this.workspaces {
3838                workspace
3839                    .update(cx, |workspace, cx| {
3840                        let handler_response = workspace.handle_follow(follower.project_id, cx);
3841                        if response.views.is_empty() {
3842                            response.views = handler_response.views;
3843                        } else {
3844                            response.views.extend_from_slice(&handler_response.views);
3845                        }
3846
3847                        if let Some(active_view_id) = handler_response.active_view_id.clone() {
3848                            if response.active_view_id.is_none()
3849                                || Some(workspace.project.downgrade()) == active_project
3850                            {
3851                                response.active_view_id = Some(active_view_id);
3852                            }
3853                        }
3854                    })
3855                    .ok();
3856            }
3857
3858            if let Err(ix) = this.followers.binary_search(&follower) {
3859                this.followers.insert(ix, follower);
3860            }
3861
3862            Ok(response)
3863        })?
3864    }
3865
3866    async fn handle_unfollow(
3867        model: Model<Self>,
3868        envelope: TypedEnvelope<proto::Unfollow>,
3869        _: Arc<Client>,
3870        mut cx: AsyncAppContext,
3871    ) -> Result<()> {
3872        model.update(&mut cx, |this, _| {
3873            let follower = Follower {
3874                project_id: envelope.payload.project_id,
3875                peer_id: envelope.original_sender_id()?,
3876            };
3877            if let Ok(ix) = this.followers.binary_search(&follower) {
3878                this.followers.remove(ix);
3879            }
3880            Ok(())
3881        })?
3882    }
3883
3884    async fn handle_update_followers(
3885        this: Model<Self>,
3886        envelope: TypedEnvelope<proto::UpdateFollowers>,
3887        _: Arc<Client>,
3888        mut cx: AsyncAppContext,
3889    ) -> Result<()> {
3890        let leader_id = envelope.original_sender_id()?;
3891        let update = envelope.payload;
3892
3893        this.update(&mut cx, |this, cx| {
3894            for workspace in &this.workspaces {
3895                workspace.update(cx, |workspace, cx| {
3896                    let project_id = workspace.project.read(cx).remote_id();
3897                    if update.project_id != project_id && update.project_id.is_some() {
3898                        return;
3899                    }
3900                    workspace.handle_update_followers(leader_id, update.clone(), cx);
3901                })?;
3902            }
3903            Ok(())
3904        })?
3905    }
3906}
3907
3908impl ViewId {
3909    pub(crate) fn from_proto(message: proto::ViewId) -> Result<Self> {
3910        Ok(Self {
3911            creator: message
3912                .creator
3913                .ok_or_else(|| anyhow!("creator is missing"))?,
3914            id: message.id,
3915        })
3916    }
3917
3918    pub(crate) fn to_proto(&self) -> proto::ViewId {
3919        proto::ViewId {
3920            creator: Some(self.creator),
3921            id: self.id,
3922        }
3923    }
3924}
3925
3926pub trait WorkspaceHandle {
3927    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath>;
3928}
3929
3930impl WorkspaceHandle for View<Workspace> {
3931    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath> {
3932        self.read(cx)
3933            .worktrees(cx)
3934            .flat_map(|worktree| {
3935                let worktree_id = worktree.read(cx).id();
3936                worktree.read(cx).files(true, 0).map(move |f| ProjectPath {
3937                    worktree_id,
3938                    path: f.path.clone(),
3939                })
3940            })
3941            .collect::<Vec<_>>()
3942    }
3943}
3944
3945impl std::fmt::Debug for OpenPaths {
3946    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3947        f.debug_struct("OpenPaths")
3948            .field("paths", &self.paths)
3949            .finish()
3950    }
3951}
3952
3953pub fn activate_workspace_for_project(
3954    cx: &mut AppContext,
3955    predicate: impl Fn(&Project, &AppContext) -> bool + Send + 'static,
3956) -> Option<WindowHandle<Workspace>> {
3957    for window in cx.windows() {
3958        let Some(workspace) = window.downcast::<Workspace>() else {
3959            continue;
3960        };
3961
3962        let predicate = workspace
3963            .update(cx, |workspace, cx| {
3964                let project = workspace.project.read(cx);
3965                if predicate(project, cx) {
3966                    cx.activate_window();
3967                    true
3968                } else {
3969                    false
3970                }
3971            })
3972            .log_err()
3973            .unwrap_or(false);
3974
3975        if predicate {
3976            return Some(workspace);
3977        }
3978    }
3979
3980    None
3981}
3982
3983pub async fn last_opened_workspace_paths() -> Option<WorkspaceLocation> {
3984    DB.last_workspace().await.log_err().flatten()
3985}
3986
3987async fn join_channel_internal(
3988    channel_id: u64,
3989    app_state: &Arc<AppState>,
3990    requesting_window: Option<WindowHandle<Workspace>>,
3991    active_call: &Model<ActiveCall>,
3992    cx: &mut AsyncAppContext,
3993) -> Result<bool> {
3994    let (should_prompt, open_room) = active_call.read_with(cx, |active_call, cx| {
3995        let Some(room) = active_call.room().map(|room| room.read(cx)) else {
3996            return (false, None);
3997        };
3998
3999        let already_in_channel = room.channel_id() == Some(channel_id);
4000        let should_prompt = room.is_sharing_project()
4001            && room.remote_participants().len() > 0
4002            && !already_in_channel;
4003        let open_room = if already_in_channel {
4004            active_call.room().cloned()
4005        } else {
4006            None
4007        };
4008        (should_prompt, open_room)
4009    })?;
4010
4011    if let Some(room) = open_room {
4012        let task = room.update(cx, |room, cx| {
4013            if let Some((project, host)) = room.most_active_project(cx) {
4014                return Some(join_remote_project(project, host, app_state.clone(), cx));
4015            }
4016
4017            None
4018        })?;
4019        if let Some(task) = task {
4020            task.await?;
4021        }
4022        return anyhow::Ok(true);
4023    }
4024
4025    if should_prompt {
4026        if let Some(workspace) = requesting_window {
4027            let answer  = workspace.update(cx, |_, cx| {
4028                cx.prompt(
4029                    PromptLevel::Warning,
4030                    "Leaving this call will unshare your current project.\nDo you want to switch channels?",
4031                    &["Yes, Join Channel", "Cancel"],
4032                )
4033            })?.await;
4034
4035            if answer == Ok(1) {
4036                return Ok(false);
4037            }
4038        } else {
4039            return Ok(false); // unreachable!() hopefully
4040        }
4041    }
4042
4043    let client = cx.update(|cx| active_call.read(cx).client())?;
4044
4045    let mut client_status = client.status();
4046
4047    // this loop will terminate within client::CONNECTION_TIMEOUT seconds.
4048    'outer: loop {
4049        let Some(status) = client_status.recv().await else {
4050            return Err(anyhow!("error connecting"));
4051        };
4052
4053        match status {
4054            Status::Connecting
4055            | Status::Authenticating
4056            | Status::Reconnecting
4057            | Status::Reauthenticating => continue,
4058            Status::Connected { .. } => break 'outer,
4059            Status::SignedOut => return Err(anyhow!("not signed in")),
4060            Status::UpgradeRequired => return Err(anyhow!("zed is out of date")),
4061            Status::ConnectionError | Status::ConnectionLost | Status::ReconnectionError { .. } => {
4062                return Err(anyhow!("zed is offline"))
4063            }
4064        }
4065    }
4066
4067    let room = active_call
4068        .update(cx, |active_call, cx| {
4069            active_call.join_channel(channel_id, cx)
4070        })?
4071        .await?;
4072
4073    let Some(room) = room else {
4074        return anyhow::Ok(true);
4075    };
4076
4077    room.update(cx, |room, _| room.room_update_completed())?
4078        .await;
4079
4080    let task = room.update(cx, |room, cx| {
4081        if let Some((project, host)) = room.most_active_project(cx) {
4082            return Some(join_remote_project(project, host, app_state.clone(), cx));
4083        }
4084
4085        None
4086    })?;
4087    if let Some(task) = task {
4088        task.await?;
4089        return anyhow::Ok(true);
4090    }
4091    anyhow::Ok(false)
4092}
4093
4094pub fn join_channel(
4095    channel_id: u64,
4096    app_state: Arc<AppState>,
4097    requesting_window: Option<WindowHandle<Workspace>>,
4098    cx: &mut AppContext,
4099) -> Task<Result<()>> {
4100    let active_call = ActiveCall::global(cx);
4101    cx.spawn(|mut cx| async move {
4102        let result = join_channel_internal(
4103            channel_id,
4104            &app_state,
4105            requesting_window,
4106            &active_call,
4107            &mut cx,
4108        )
4109        .await;
4110
4111        // join channel succeeded, and opened a window
4112        if matches!(result, Ok(true)) {
4113            return anyhow::Ok(());
4114        }
4115
4116        if requesting_window.is_some() {
4117            return anyhow::Ok(());
4118        }
4119
4120        // find an existing workspace to focus and show call controls
4121        let mut active_window = activate_any_workspace_window(&mut cx);
4122        if active_window.is_none() {
4123            // no open workspaces, make one to show the error in (blergh)
4124            cx.update(|cx| Workspace::new_local(vec![], app_state.clone(), requesting_window, cx))?
4125                .await?;
4126        }
4127
4128        active_window = activate_any_workspace_window(&mut cx);
4129        let Some(active_window) = active_window else {
4130            return anyhow::Ok(());
4131        };
4132
4133        if let Err(err) = result {
4134            active_window
4135                .update(&mut cx, |_, cx| {
4136                    cx.prompt(
4137                        PromptLevel::Critical,
4138                        &format!("Failed to join channel: {}", err),
4139                        &["Ok"],
4140                    )
4141                })?
4142                .await
4143                .ok();
4144        }
4145
4146        // return ok, we showed the error to the user.
4147        return anyhow::Ok(());
4148    })
4149}
4150
4151pub fn activate_any_workspace_window(cx: &mut AsyncAppContext) -> Option<AnyWindowHandle> {
4152    cx.update(|cx| {
4153        for window in cx.windows() {
4154            let is_workspace = window.downcast::<Workspace>().is_some();
4155            if is_workspace {
4156                window.update(cx, |_, cx| cx.activate_window()).ok();
4157                return Some(window);
4158            }
4159        }
4160        None
4161    })
4162    .ok()
4163    .flatten()
4164}
4165
4166#[allow(clippy::type_complexity)]
4167pub fn open_paths(
4168    abs_paths: &[PathBuf],
4169    app_state: &Arc<AppState>,
4170    requesting_window: Option<WindowHandle<Workspace>>,
4171    cx: &mut AppContext,
4172) -> Task<
4173    anyhow::Result<(
4174        WindowHandle<Workspace>,
4175        Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
4176    )>,
4177> {
4178    let app_state = app_state.clone();
4179    let abs_paths = abs_paths.to_vec();
4180    // Open paths in existing workspace if possible
4181    let existing = activate_workspace_for_project(cx, {
4182        let abs_paths = abs_paths.clone();
4183        move |project, cx| project.contains_paths(&abs_paths, cx)
4184    });
4185    cx.spawn(move |mut cx| async move {
4186        if let Some(existing) = existing {
4187            // // Ok((
4188            //     existing.clone(),
4189            //     cx.update_window_root(&existing, |workspace, cx| {
4190            //         workspace.open_paths(abs_paths, true, cx)
4191            //     })?
4192            //     .await,
4193            // ))
4194            todo!()
4195        } else {
4196            cx.update(move |cx| {
4197                Workspace::new_local(abs_paths, app_state.clone(), requesting_window, cx)
4198            })?
4199            .await
4200        }
4201    })
4202}
4203
4204pub fn open_new(
4205    app_state: &Arc<AppState>,
4206    cx: &mut AppContext,
4207    init: impl FnOnce(&mut Workspace, &mut ViewContext<Workspace>) + 'static + Send,
4208) -> Task<()> {
4209    let task = Workspace::new_local(Vec::new(), app_state.clone(), None, cx);
4210    cx.spawn(|mut cx| async move {
4211        if let Some((workspace, opened_paths)) = task.await.log_err() {
4212            workspace
4213                .update(&mut cx, |workspace, cx| {
4214                    if opened_paths.is_empty() {
4215                        init(workspace, cx)
4216                    }
4217                })
4218                .log_err();
4219        }
4220    })
4221}
4222
4223pub fn create_and_open_local_file(
4224    path: &'static Path,
4225    cx: &mut ViewContext<Workspace>,
4226    default_content: impl 'static + Send + FnOnce() -> Rope,
4227) -> Task<Result<Box<dyn ItemHandle>>> {
4228    cx.spawn(|workspace, mut cx| async move {
4229        let fs = workspace.update(&mut cx, |workspace, _| workspace.app_state().fs.clone())?;
4230        if !fs.is_file(path).await {
4231            fs.create_file(path, Default::default()).await?;
4232            fs.save(path, &default_content(), Default::default())
4233                .await?;
4234        }
4235
4236        let mut items = workspace
4237            .update(&mut cx, |workspace, cx| {
4238                workspace.with_local_workspace(cx, |workspace, cx| {
4239                    workspace.open_paths(vec![path.to_path_buf()], false, cx)
4240                })
4241            })?
4242            .await?
4243            .await;
4244
4245        let item = items.pop().flatten();
4246        item.ok_or_else(|| anyhow!("path {path:?} is not a file"))?
4247    })
4248}
4249
4250pub fn join_remote_project(
4251    project_id: u64,
4252    follow_user_id: u64,
4253    app_state: Arc<AppState>,
4254    cx: &mut AppContext,
4255) -> Task<Result<()>> {
4256    let windows = cx.windows();
4257    cx.spawn(|mut cx| async move {
4258        let existing_workspace = windows.into_iter().find_map(|window| {
4259            window.downcast::<Workspace>().and_then(|window| {
4260                window
4261                    .update(&mut cx, |workspace, cx| {
4262                        if workspace.project().read(cx).remote_id() == Some(project_id) {
4263                            Some(window)
4264                        } else {
4265                            None
4266                        }
4267                    })
4268                    .unwrap_or(None)
4269            })
4270        });
4271
4272        let workspace = if let Some(existing_workspace) = existing_workspace {
4273            existing_workspace
4274        } else {
4275            let active_call = cx.update(|cx| ActiveCall::global(cx))?;
4276            let room = active_call
4277                .read_with(&cx, |call, _| call.room().cloned())?
4278                .ok_or_else(|| anyhow!("not in a call"))?;
4279            let project = room
4280                .update(&mut cx, |room, cx| {
4281                    room.join_project(
4282                        project_id,
4283                        app_state.languages.clone(),
4284                        app_state.fs.clone(),
4285                        cx,
4286                    )
4287                })?
4288                .await?;
4289
4290            let window_bounds_override = window_bounds_env_override(&cx);
4291            cx.update(|cx| {
4292                let options = (app_state.build_window_options)(window_bounds_override, None, cx);
4293                cx.open_window(options, |cx| {
4294                    cx.build_view(|cx| Workspace::new(0, project, app_state.clone(), cx))
4295                })
4296            })?
4297        };
4298
4299        workspace.update(&mut cx, |workspace, cx| {
4300            cx.activate(true);
4301            cx.activate_window();
4302
4303            if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
4304                let follow_peer_id = room
4305                    .read(cx)
4306                    .remote_participants()
4307                    .iter()
4308                    .find(|(_, participant)| participant.user.id == follow_user_id)
4309                    .map(|(_, p)| p.peer_id)
4310                    .or_else(|| {
4311                        // If we couldn't follow the given user, follow the host instead.
4312                        let collaborator = workspace
4313                            .project()
4314                            .read(cx)
4315                            .collaborators()
4316                            .values()
4317                            .find(|collaborator| collaborator.replica_id == 0)?;
4318                        Some(collaborator.peer_id)
4319                    });
4320
4321                if let Some(follow_peer_id) = follow_peer_id {
4322                    workspace
4323                        .follow(follow_peer_id, cx)
4324                        .map(|follow| follow.detach_and_log_err(cx));
4325                }
4326            }
4327        })?;
4328
4329        anyhow::Ok(())
4330    })
4331}
4332
4333pub fn restart(_: &Restart, cx: &mut AppContext) {
4334    let should_confirm = WorkspaceSettings::get_global(cx).confirm_quit;
4335    let mut workspace_windows = cx
4336        .windows()
4337        .into_iter()
4338        .filter_map(|window| window.downcast::<Workspace>())
4339        .collect::<Vec<_>>();
4340
4341    // If multiple windows have unsaved changes, and need a save prompt,
4342    // prompt in the active window before switching to a different window.
4343    workspace_windows.sort_by_key(|window| window.is_active(&cx) == Some(false));
4344
4345    let mut prompt = None;
4346    if let (true, Some(window)) = (should_confirm, workspace_windows.first()) {
4347        prompt = window
4348            .update(cx, |_, cx| {
4349                cx.prompt(
4350                    PromptLevel::Info,
4351                    "Are you sure you want to restart?",
4352                    &["Restart", "Cancel"],
4353                )
4354            })
4355            .ok();
4356    }
4357
4358    cx.spawn(|mut cx| async move {
4359        if let Some(mut prompt) = prompt {
4360            let answer = prompt.await?;
4361            if answer != 0 {
4362                return Ok(());
4363            }
4364        }
4365
4366        // If the user cancels any save prompt, then keep the app open.
4367        for window in workspace_windows {
4368            if let Ok(should_close) = window.update(&mut cx, |workspace, cx| {
4369                workspace.prepare_to_close(true, cx)
4370            }) {
4371                if !should_close.await? {
4372                    return Ok(());
4373                }
4374            }
4375        }
4376
4377        cx.update(|cx| cx.restart())
4378    })
4379    .detach_and_log_err(cx);
4380}
4381
4382fn parse_pixel_position_env_var(value: &str) -> Option<Point<GlobalPixels>> {
4383    let mut parts = value.split(',');
4384    let x: usize = parts.next()?.parse().ok()?;
4385    let y: usize = parts.next()?.parse().ok()?;
4386    Some(point((x as f64).into(), (y as f64).into()))
4387}
4388
4389fn parse_pixel_size_env_var(value: &str) -> Option<Size<GlobalPixels>> {
4390    let mut parts = value.split(',');
4391    let width: usize = parts.next()?.parse().ok()?;
4392    let height: usize = parts.next()?.parse().ok()?;
4393    Some(size((width as f64).into(), (height as f64).into()))
4394}
4395
4396#[cfg(test)]
4397mod tests {
4398    use super::*;
4399    use crate::item::{
4400        test::{TestItem, TestProjectItem},
4401        ItemEvent,
4402    };
4403    use fs::FakeFs;
4404    use gpui::TestAppContext;
4405    use project::{Project, ProjectEntryId};
4406    use serde_json::json;
4407    use settings::SettingsStore;
4408    use std::{cell::RefCell, rc::Rc};
4409
4410    #[gpui::test]
4411    async fn test_tab_disambiguation(cx: &mut TestAppContext) {
4412        init_test(cx);
4413
4414        let fs = FakeFs::new(cx.executor());
4415        let project = Project::test(fs, [], cx).await;
4416        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
4417
4418        // Adding an item with no ambiguity renders the tab without detail.
4419        let item1 = cx.build_view(|cx| {
4420            let mut item = TestItem::new(cx);
4421            item.tab_descriptions = Some(vec!["c", "b1/c", "a/b1/c"]);
4422            item
4423        });
4424        workspace.update(cx, |workspace, cx| {
4425            workspace.add_item(Box::new(item1.clone()), cx);
4426        });
4427        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(0)));
4428
4429        // Adding an item that creates ambiguity increases the level of detail on
4430        // both tabs.
4431        let item2 = cx.build_view(|cx| {
4432            let mut item = TestItem::new(cx);
4433            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
4434            item
4435        });
4436        workspace.update(cx, |workspace, cx| {
4437            workspace.add_item(Box::new(item2.clone()), cx);
4438        });
4439        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4440        item2.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4441
4442        // Adding an item that creates ambiguity increases the level of detail only
4443        // on the ambiguous tabs. In this case, the ambiguity can't be resolved so
4444        // we stop at the highest detail available.
4445        let item3 = cx.build_view(|cx| {
4446            let mut item = TestItem::new(cx);
4447            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
4448            item
4449        });
4450        workspace.update(cx, |workspace, cx| {
4451            workspace.add_item(Box::new(item3.clone()), cx);
4452        });
4453        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4454        item2.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
4455        item3.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
4456    }
4457
4458    #[gpui::test]
4459    async fn test_tracking_active_path(cx: &mut TestAppContext) {
4460        init_test(cx);
4461
4462        let fs = FakeFs::new(cx.executor());
4463        fs.insert_tree(
4464            "/root1",
4465            json!({
4466                "one.txt": "",
4467                "two.txt": "",
4468            }),
4469        )
4470        .await;
4471        fs.insert_tree(
4472            "/root2",
4473            json!({
4474                "three.txt": "",
4475            }),
4476        )
4477        .await;
4478
4479        let project = Project::test(fs, ["root1".as_ref()], cx).await;
4480        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
4481        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
4482        let worktree_id = project.read_with(cx, |project, cx| {
4483            project.worktrees().next().unwrap().read(cx).id()
4484        });
4485
4486        let item1 = cx.build_view(|cx| {
4487            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "one.txt", cx)])
4488        });
4489        let item2 = cx.build_view(|cx| {
4490            TestItem::new(cx).with_project_items(&[TestProjectItem::new(2, "two.txt", cx)])
4491        });
4492
4493        // Add an item to an empty pane
4494        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item1), cx));
4495        project.read_with(cx, |project, cx| {
4496            assert_eq!(
4497                project.active_entry(),
4498                project
4499                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
4500                    .map(|e| e.id)
4501            );
4502        });
4503        assert_eq!(cx.window_title().as_deref(), Some("one.txt β€” root1"));
4504
4505        // Add a second item to a non-empty pane
4506        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item2), cx));
4507        assert_eq!(cx.window_title().as_deref(), Some("two.txt β€” root1"));
4508        project.read_with(cx, |project, cx| {
4509            assert_eq!(
4510                project.active_entry(),
4511                project
4512                    .entry_for_path(&(worktree_id, "two.txt").into(), cx)
4513                    .map(|e| e.id)
4514            );
4515        });
4516
4517        // Close the active item
4518        pane.update(cx, |pane, cx| {
4519            pane.close_active_item(&Default::default(), cx).unwrap()
4520        })
4521        .await
4522        .unwrap();
4523        assert_eq!(cx.window_title().as_deref(), Some("one.txt β€” root1"));
4524        project.read_with(cx, |project, cx| {
4525            assert_eq!(
4526                project.active_entry(),
4527                project
4528                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
4529                    .map(|e| e.id)
4530            );
4531        });
4532
4533        // Add a project folder
4534        project
4535            .update(cx, |project, cx| {
4536                project.find_or_create_local_worktree("/root2", true, cx)
4537            })
4538            .await
4539            .unwrap();
4540        assert_eq!(cx.window_title().as_deref(), Some("one.txt β€” root1, root2"));
4541
4542        // Remove a project folder
4543        project.update(cx, |project, cx| project.remove_worktree(worktree_id, cx));
4544        assert_eq!(cx.window_title().as_deref(), Some("one.txt β€” root2"));
4545    }
4546
4547    #[gpui::test]
4548    async fn test_close_window(cx: &mut TestAppContext) {
4549        init_test(cx);
4550
4551        let fs = FakeFs::new(cx.executor());
4552        fs.insert_tree("/root", json!({ "one": "" })).await;
4553
4554        let project = Project::test(fs, ["root".as_ref()], cx).await;
4555        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
4556
4557        // When there are no dirty items, there's nothing to do.
4558        let item1 = cx.build_view(|cx| TestItem::new(cx));
4559        workspace.update(cx, |w, cx| w.add_item(Box::new(item1.clone()), cx));
4560        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
4561        assert!(task.await.unwrap());
4562
4563        // When there are dirty untitled items, prompt to save each one. If the user
4564        // cancels any prompt, then abort.
4565        let item2 = cx.build_view(|cx| TestItem::new(cx).with_dirty(true));
4566        let item3 = cx.build_view(|cx| {
4567            TestItem::new(cx)
4568                .with_dirty(true)
4569                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4570        });
4571        workspace.update(cx, |w, cx| {
4572            w.add_item(Box::new(item2.clone()), cx);
4573            w.add_item(Box::new(item3.clone()), cx);
4574        });
4575        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
4576        cx.executor().run_until_parked();
4577        cx.simulate_prompt_answer(2); // cancel save all
4578        cx.executor().run_until_parked();
4579        cx.simulate_prompt_answer(2); // cancel save all
4580        cx.executor().run_until_parked();
4581        assert!(!cx.has_pending_prompt());
4582        assert!(!task.await.unwrap());
4583    }
4584
4585    #[gpui::test]
4586    async fn test_close_pane_items(cx: &mut TestAppContext) {
4587        init_test(cx);
4588
4589        let fs = FakeFs::new(cx.executor());
4590
4591        let project = Project::test(fs, None, cx).await;
4592        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
4593
4594        let item1 = cx.build_view(|cx| {
4595            TestItem::new(cx)
4596                .with_dirty(true)
4597                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4598        });
4599        let item2 = cx.build_view(|cx| {
4600            TestItem::new(cx)
4601                .with_dirty(true)
4602                .with_conflict(true)
4603                .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)])
4604        });
4605        let item3 = cx.build_view(|cx| {
4606            TestItem::new(cx)
4607                .with_dirty(true)
4608                .with_conflict(true)
4609                .with_project_items(&[TestProjectItem::new(3, "3.txt", cx)])
4610        });
4611        let item4 = cx.build_view(|cx| {
4612            TestItem::new(cx)
4613                .with_dirty(true)
4614                .with_project_items(&[TestProjectItem::new_untitled(cx)])
4615        });
4616        let pane = workspace.update(cx, |workspace, cx| {
4617            workspace.add_item(Box::new(item1.clone()), cx);
4618            workspace.add_item(Box::new(item2.clone()), cx);
4619            workspace.add_item(Box::new(item3.clone()), cx);
4620            workspace.add_item(Box::new(item4.clone()), cx);
4621            workspace.active_pane().clone()
4622        });
4623
4624        let close_items = pane.update(cx, |pane, cx| {
4625            pane.activate_item(1, true, true, cx);
4626            assert_eq!(pane.active_item().unwrap().item_id(), item2.item_id());
4627            let item1_id = item1.item_id();
4628            let item3_id = item3.item_id();
4629            let item4_id = item4.item_id();
4630            pane.close_items(cx, SaveIntent::Close, move |id| {
4631                [item1_id, item3_id, item4_id].contains(&id)
4632            })
4633        });
4634        cx.executor().run_until_parked();
4635
4636        assert!(cx.has_pending_prompt());
4637        // Ignore "Save all" prompt
4638        cx.simulate_prompt_answer(2);
4639        cx.executor().run_until_parked();
4640        // There's a prompt to save item 1.
4641        pane.update(cx, |pane, _| {
4642            assert_eq!(pane.items_len(), 4);
4643            assert_eq!(pane.active_item().unwrap().item_id(), item1.item_id());
4644        });
4645        // Confirm saving item 1.
4646        cx.simulate_prompt_answer(0);
4647        cx.executor().run_until_parked();
4648
4649        // Item 1 is saved. There's a prompt to save item 3.
4650        pane.update(cx, |pane, cx| {
4651            assert_eq!(item1.read(cx).save_count, 1);
4652            assert_eq!(item1.read(cx).save_as_count, 0);
4653            assert_eq!(item1.read(cx).reload_count, 0);
4654            assert_eq!(pane.items_len(), 3);
4655            assert_eq!(pane.active_item().unwrap().item_id(), item3.item_id());
4656        });
4657        assert!(cx.has_pending_prompt());
4658
4659        // Cancel saving item 3.
4660        cx.simulate_prompt_answer(1);
4661        cx.executor().run_until_parked();
4662
4663        // Item 3 is reloaded. There's a prompt to save item 4.
4664        pane.update(cx, |pane, cx| {
4665            assert_eq!(item3.read(cx).save_count, 0);
4666            assert_eq!(item3.read(cx).save_as_count, 0);
4667            assert_eq!(item3.read(cx).reload_count, 1);
4668            assert_eq!(pane.items_len(), 2);
4669            assert_eq!(pane.active_item().unwrap().item_id(), item4.item_id());
4670        });
4671        assert!(cx.has_pending_prompt());
4672
4673        // Confirm saving item 4.
4674        cx.simulate_prompt_answer(0);
4675        cx.executor().run_until_parked();
4676
4677        // There's a prompt for a path for item 4.
4678        cx.simulate_new_path_selection(|_| Some(Default::default()));
4679        close_items.await.unwrap();
4680
4681        // The requested items are closed.
4682        pane.update(cx, |pane, cx| {
4683            assert_eq!(item4.read(cx).save_count, 0);
4684            assert_eq!(item4.read(cx).save_as_count, 1);
4685            assert_eq!(item4.read(cx).reload_count, 0);
4686            assert_eq!(pane.items_len(), 1);
4687            assert_eq!(pane.active_item().unwrap().item_id(), item2.item_id());
4688        });
4689    }
4690
4691    #[gpui::test]
4692    async fn test_prompting_to_save_only_on_last_item_for_entry(cx: &mut TestAppContext) {
4693        init_test(cx);
4694
4695        let fs = FakeFs::new(cx.executor());
4696        let project = Project::test(fs, [], cx).await;
4697        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
4698
4699        // Create several workspace items with single project entries, and two
4700        // workspace items with multiple project entries.
4701        let single_entry_items = (0..=4)
4702            .map(|project_entry_id| {
4703                cx.build_view(|cx| {
4704                    TestItem::new(cx)
4705                        .with_dirty(true)
4706                        .with_project_items(&[TestProjectItem::new(
4707                            project_entry_id,
4708                            &format!("{project_entry_id}.txt"),
4709                            cx,
4710                        )])
4711                })
4712            })
4713            .collect::<Vec<_>>();
4714        let item_2_3 = cx.build_view(|cx| {
4715            TestItem::new(cx)
4716                .with_dirty(true)
4717                .with_singleton(false)
4718                .with_project_items(&[
4719                    single_entry_items[2].read(cx).project_items[0].clone(),
4720                    single_entry_items[3].read(cx).project_items[0].clone(),
4721                ])
4722        });
4723        let item_3_4 = cx.build_view(|cx| {
4724            TestItem::new(cx)
4725                .with_dirty(true)
4726                .with_singleton(false)
4727                .with_project_items(&[
4728                    single_entry_items[3].read(cx).project_items[0].clone(),
4729                    single_entry_items[4].read(cx).project_items[0].clone(),
4730                ])
4731        });
4732
4733        // Create two panes that contain the following project entries:
4734        //   left pane:
4735        //     multi-entry items:   (2, 3)
4736        //     single-entry items:  0, 1, 2, 3, 4
4737        //   right pane:
4738        //     single-entry items:  1
4739        //     multi-entry items:   (3, 4)
4740        let left_pane = workspace.update(cx, |workspace, cx| {
4741            let left_pane = workspace.active_pane().clone();
4742            workspace.add_item(Box::new(item_2_3.clone()), cx);
4743            for item in single_entry_items {
4744                workspace.add_item(Box::new(item), cx);
4745            }
4746            left_pane.update(cx, |pane, cx| {
4747                pane.activate_item(2, true, true, cx);
4748            });
4749
4750            let right_pane = workspace
4751                .split_and_clone(left_pane.clone(), SplitDirection::Right, cx)
4752                .unwrap();
4753
4754            right_pane.update(cx, |pane, cx| {
4755                pane.add_item(Box::new(item_3_4.clone()), true, true, None, cx);
4756            });
4757
4758            left_pane
4759        });
4760
4761        cx.focus_view(&left_pane);
4762
4763        // When closing all of the items in the left pane, we should be prompted twice:
4764        // once for project entry 0, and once for project entry 2. Project entries 1,
4765        // 3, and 4 are all still open in the other paten. After those two
4766        // prompts, the task should complete.
4767
4768        let close = left_pane.update(cx, |pane, cx| {
4769            pane.close_all_items(&CloseAllItems::default(), cx).unwrap()
4770        });
4771        cx.executor().run_until_parked();
4772
4773        // Discard "Save all" prompt
4774        cx.simulate_prompt_answer(2);
4775
4776        cx.executor().run_until_parked();
4777        left_pane.update(cx, |pane, cx| {
4778            assert_eq!(
4779                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
4780                &[ProjectEntryId::from_proto(0)]
4781            );
4782        });
4783        cx.simulate_prompt_answer(0);
4784
4785        cx.executor().run_until_parked();
4786        left_pane.update(cx, |pane, cx| {
4787            assert_eq!(
4788                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
4789                &[ProjectEntryId::from_proto(2)]
4790            );
4791        });
4792        cx.simulate_prompt_answer(0);
4793
4794        cx.executor().run_until_parked();
4795        close.await.unwrap();
4796        left_pane.update(cx, |pane, _| {
4797            assert_eq!(pane.items_len(), 0);
4798        });
4799    }
4800
4801    #[gpui::test]
4802    async fn test_autosave(cx: &mut gpui::TestAppContext) {
4803        init_test(cx);
4804
4805        let fs = FakeFs::new(cx.executor());
4806        let project = Project::test(fs, [], cx).await;
4807        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
4808        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
4809
4810        let item = cx.build_view(|cx| {
4811            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4812        });
4813        let item_id = item.entity_id();
4814        workspace.update(cx, |workspace, cx| {
4815            workspace.add_item(Box::new(item.clone()), cx);
4816        });
4817
4818        // Autosave on window change.
4819        item.update(cx, |item, cx| {
4820            cx.update_global(|settings: &mut SettingsStore, cx| {
4821                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4822                    settings.autosave = Some(AutosaveSetting::OnWindowChange);
4823                })
4824            });
4825            item.is_dirty = true;
4826        });
4827
4828        // Deactivating the window saves the file.
4829        cx.simulate_deactivation();
4830        cx.executor().run_until_parked();
4831        item.update(cx, |item, _| assert_eq!(item.save_count, 1));
4832
4833        // Autosave on focus change.
4834        item.update(cx, |item, cx| {
4835            cx.focus_self();
4836            cx.update_global(|settings: &mut SettingsStore, cx| {
4837                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4838                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
4839                })
4840            });
4841            item.is_dirty = true;
4842        });
4843
4844        // Blurring the item saves the file.
4845        item.update(cx, |_, cx| cx.blur());
4846        cx.executor().run_until_parked();
4847        item.update(cx, |item, _| assert_eq!(item.save_count, 2));
4848
4849        // Deactivating the window still saves the file.
4850        cx.simulate_activation();
4851        item.update(cx, |item, cx| {
4852            cx.focus_self();
4853            item.is_dirty = true;
4854        });
4855        cx.simulate_deactivation();
4856
4857        cx.executor().run_until_parked();
4858        item.update(cx, |item, _| assert_eq!(item.save_count, 3));
4859
4860        // Autosave after delay.
4861        item.update(cx, |item, cx| {
4862            cx.update_global(|settings: &mut SettingsStore, cx| {
4863                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4864                    settings.autosave = Some(AutosaveSetting::AfterDelay { milliseconds: 500 });
4865                })
4866            });
4867            item.is_dirty = true;
4868            cx.emit(ItemEvent::Edit);
4869        });
4870
4871        // Delay hasn't fully expired, so the file is still dirty and unsaved.
4872        cx.executor().advance_clock(Duration::from_millis(250));
4873        item.update(cx, |item, _| assert_eq!(item.save_count, 3));
4874
4875        // After delay expires, the file is saved.
4876        cx.executor().advance_clock(Duration::from_millis(250));
4877        item.update(cx, |item, _| assert_eq!(item.save_count, 4));
4878
4879        // Autosave on focus change, ensuring closing the tab counts as such.
4880        item.update(cx, |item, cx| {
4881            cx.update_global(|settings: &mut SettingsStore, cx| {
4882                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4883                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
4884                })
4885            });
4886            item.is_dirty = true;
4887        });
4888
4889        pane.update(cx, |pane, cx| {
4890            pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
4891        })
4892        .await
4893        .unwrap();
4894        assert!(!cx.has_pending_prompt());
4895        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
4896
4897        // Add the item again, ensuring autosave is prevented if the underlying file has been deleted.
4898        workspace.update(cx, |workspace, cx| {
4899            workspace.add_item(Box::new(item.clone()), cx);
4900        });
4901        item.update(cx, |item, cx| {
4902            item.project_items[0].update(cx, |item, _| {
4903                item.entry_id = None;
4904            });
4905            item.is_dirty = true;
4906            cx.blur();
4907        });
4908        cx.executor().run_until_parked();
4909        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
4910
4911        // Ensure autosave is prevented for deleted files also when closing the buffer.
4912        let _close_items = pane.update(cx, |pane, cx| {
4913            pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
4914        });
4915        cx.executor().run_until_parked();
4916        assert!(cx.has_pending_prompt());
4917        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
4918    }
4919
4920    #[gpui::test]
4921    async fn test_pane_navigation(cx: &mut gpui::TestAppContext) {
4922        init_test(cx);
4923
4924        let fs = FakeFs::new(cx.executor());
4925
4926        let project = Project::test(fs, [], cx).await;
4927        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
4928
4929        let item = cx.build_view(|cx| {
4930            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4931        });
4932        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
4933        let toolbar = pane.update(cx, |pane, _| pane.toolbar().clone());
4934        let toolbar_notify_count = Rc::new(RefCell::new(0));
4935
4936        workspace.update(cx, |workspace, cx| {
4937            workspace.add_item(Box::new(item.clone()), cx);
4938            let toolbar_notification_count = toolbar_notify_count.clone();
4939            cx.observe(&toolbar, move |_, _, _| {
4940                *toolbar_notification_count.borrow_mut() += 1
4941            })
4942            .detach();
4943        });
4944
4945        pane.update(cx, |pane, _| {
4946            assert!(!pane.can_navigate_backward());
4947            assert!(!pane.can_navigate_forward());
4948        });
4949
4950        item.update(cx, |item, cx| {
4951            item.set_state("one".to_string(), cx);
4952        });
4953
4954        // Toolbar must be notified to re-render the navigation buttons
4955        assert_eq!(*toolbar_notify_count.borrow(), 1);
4956
4957        pane.update(cx, |pane, _| {
4958            assert!(pane.can_navigate_backward());
4959            assert!(!pane.can_navigate_forward());
4960        });
4961
4962        workspace
4963            .update(cx, |workspace, cx| workspace.go_back(pane.downgrade(), cx))
4964            .await
4965            .unwrap();
4966
4967        assert_eq!(*toolbar_notify_count.borrow(), 2);
4968        pane.update(cx, |pane, _| {
4969            assert!(!pane.can_navigate_backward());
4970            assert!(pane.can_navigate_forward());
4971        });
4972    }
4973
4974    //     #[gpui::test]
4975    //     async fn test_toggle_docks_and_panels(cx: &mut gpui::TestAppContext) {
4976    //         init_test(cx);
4977    //         let fs = FakeFs::new(cx.executor());
4978
4979    //         let project = Project::test(fs, [], cx).await;
4980    //         let window = cx.add_window(|cx| Workspace::test_new(project, cx));
4981    //         let workspace = window.root(cx);
4982
4983    //         let panel = workspace.update(cx, |workspace, cx| {
4984    //             let panel = cx.build_view(|_| TestPanel::new(DockPosition::Right));
4985    //             workspace.add_panel(panel.clone(), cx);
4986
4987    //             workspace
4988    //                 .right_dock()
4989    //                 .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
4990
4991    //             panel
4992    //         });
4993
4994    //         let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
4995    //         pane.update(cx, |pane, cx| {
4996    //             let item = cx.build_view(|_| TestItem::new(cx));
4997    //             pane.add_item(Box::new(item), true, true, None, cx);
4998    //         });
4999
5000    //         // Transfer focus from center to panel
5001    //         workspace.update(cx, |workspace, cx| {
5002    //             workspace.toggle_panel_focus::<TestPanel>(cx);
5003    //         });
5004
5005    //         workspace.update(cx, |workspace, cx| {
5006    //             assert!(workspace.right_dock().read(cx).is_open());
5007    //             assert!(!panel.is_zoomed(cx));
5008    //             assert!(panel.has_focus(cx));
5009    //         });
5010
5011    //         // Transfer focus from panel to center
5012    //         workspace.update(cx, |workspace, cx| {
5013    //             workspace.toggle_panel_focus::<TestPanel>(cx);
5014    //         });
5015
5016    //         workspace.update(cx, |workspace, cx| {
5017    //             assert!(workspace.right_dock().read(cx).is_open());
5018    //             assert!(!panel.is_zoomed(cx));
5019    //             assert!(!panel.has_focus(cx));
5020    //         });
5021
5022    //         // Close the dock
5023    //         workspace.update(cx, |workspace, cx| {
5024    //             workspace.toggle_dock(DockPosition::Right, cx);
5025    //         });
5026
5027    //         workspace.update(cx, |workspace, cx| {
5028    //             assert!(!workspace.right_dock().read(cx).is_open());
5029    //             assert!(!panel.is_zoomed(cx));
5030    //             assert!(!panel.has_focus(cx));
5031    //         });
5032
5033    //         // Open the dock
5034    //         workspace.update(cx, |workspace, cx| {
5035    //             workspace.toggle_dock(DockPosition::Right, cx);
5036    //         });
5037
5038    //         workspace.update(cx, |workspace, cx| {
5039    //             assert!(workspace.right_dock().read(cx).is_open());
5040    //             assert!(!panel.is_zoomed(cx));
5041    //             assert!(panel.has_focus(cx));
5042    //         });
5043
5044    //         // Focus and zoom panel
5045    //         panel.update(cx, |panel, cx| {
5046    //             cx.focus_self();
5047    //             panel.set_zoomed(true, cx)
5048    //         });
5049
5050    //         workspace.update(cx, |workspace, cx| {
5051    //             assert!(workspace.right_dock().read(cx).is_open());
5052    //             assert!(panel.is_zoomed(cx));
5053    //             assert!(panel.has_focus(cx));
5054    //         });
5055
5056    //         // Transfer focus to the center closes the dock
5057    //         workspace.update(cx, |workspace, cx| {
5058    //             workspace.toggle_panel_focus::<TestPanel>(cx);
5059    //         });
5060
5061    //         workspace.update(cx, |workspace, cx| {
5062    //             assert!(!workspace.right_dock().read(cx).is_open());
5063    //             assert!(panel.is_zoomed(cx));
5064    //             assert!(!panel.has_focus(cx));
5065    //         });
5066
5067    //         // Transferring focus back to the panel keeps it zoomed
5068    //         workspace.update(cx, |workspace, cx| {
5069    //             workspace.toggle_panel_focus::<TestPanel>(cx);
5070    //         });
5071
5072    //         workspace.update(cx, |workspace, cx| {
5073    //             assert!(workspace.right_dock().read(cx).is_open());
5074    //             assert!(panel.is_zoomed(cx));
5075    //             assert!(panel.has_focus(cx));
5076    //         });
5077
5078    //         // Close the dock while it is zoomed
5079    //         workspace.update(cx, |workspace, cx| {
5080    //             workspace.toggle_dock(DockPosition::Right, cx)
5081    //         });
5082
5083    //         workspace.update(cx, |workspace, cx| {
5084    //             assert!(!workspace.right_dock().read(cx).is_open());
5085    //             assert!(panel.is_zoomed(cx));
5086    //             assert!(workspace.zoomed.is_none());
5087    //             assert!(!panel.has_focus(cx));
5088    //         });
5089
5090    //         // Opening the dock, when it's zoomed, retains focus
5091    //         workspace.update(cx, |workspace, cx| {
5092    //             workspace.toggle_dock(DockPosition::Right, cx)
5093    //         });
5094
5095    //         workspace.update(cx, |workspace, cx| {
5096    //             assert!(workspace.right_dock().read(cx).is_open());
5097    //             assert!(panel.is_zoomed(cx));
5098    //             assert!(workspace.zoomed.is_some());
5099    //             assert!(panel.has_focus(cx));
5100    //         });
5101
5102    //         // Unzoom and close the panel, zoom the active pane.
5103    //         panel.update(cx, |panel, cx| panel.set_zoomed(false, cx));
5104    //         workspace.update(cx, |workspace, cx| {
5105    //             workspace.toggle_dock(DockPosition::Right, cx)
5106    //         });
5107    //         pane.update(cx, |pane, cx| pane.toggle_zoom(&Default::default(), cx));
5108
5109    //         // Opening a dock unzooms the pane.
5110    //         workspace.update(cx, |workspace, cx| {
5111    //             workspace.toggle_dock(DockPosition::Right, cx)
5112    //         });
5113    //         workspace.update(cx, |workspace, cx| {
5114    //             let pane = pane.read(cx);
5115    //             assert!(!pane.is_zoomed());
5116    //             assert!(!pane.has_focus());
5117    //             assert!(workspace.right_dock().read(cx).is_open());
5118    //             assert!(workspace.zoomed.is_none());
5119    //         });
5120    //     }
5121
5122    //     #[gpui::test]
5123    //     async fn test_panels(cx: &mut gpui::TestAppContext) {
5124    //         init_test(cx);
5125    //         let fs = FakeFs::new(cx.executor());
5126
5127    //         let project = Project::test(fs, [], cx).await;
5128    //         let window = cx.add_window(|cx| Workspace::test_new(project, cx));
5129    //         let workspace = window.root(cx);
5130
5131    //         let (panel_1, panel_2) = workspace.update(cx, |workspace, cx| {
5132    //             // Add panel_1 on the left, panel_2 on the right.
5133    //             let panel_1 = cx.build_view(|_| TestPanel::new(DockPosition::Left));
5134    //             workspace.add_panel(panel_1.clone(), cx);
5135    //             workspace
5136    //                 .left_dock()
5137    //                 .update(cx, |left_dock, cx| left_dock.set_open(true, cx));
5138    //             let panel_2 = cx.build_view(|_| TestPanel::new(DockPosition::Right));
5139    //             workspace.add_panel(panel_2.clone(), cx);
5140    //             workspace
5141    //                 .right_dock()
5142    //                 .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
5143
5144    //             let left_dock = workspace.left_dock();
5145    //             assert_eq!(
5146    //                 left_dock.read(cx).visible_panel().unwrap().id(),
5147    //                 panel_1.id()
5148    //             );
5149    //             assert_eq!(
5150    //                 left_dock.read(cx).active_panel_size(cx).unwrap(),
5151    //                 panel_1.size(cx)
5152    //             );
5153
5154    //             left_dock.update(cx, |left_dock, cx| {
5155    //                 left_dock.resize_active_panel(Some(1337.), cx)
5156    //             });
5157    //             assert_eq!(
5158    //                 workspace
5159    //                     .right_dock()
5160    //                     .read(cx)
5161    //                     .visible_panel()
5162    //                     .unwrap()
5163    //                     .id(),
5164    //                 panel_2.id()
5165    //             );
5166
5167    //             (panel_1, panel_2)
5168    //         });
5169
5170    //         // Move panel_1 to the right
5171    //         panel_1.update(cx, |panel_1, cx| {
5172    //             panel_1.set_position(DockPosition::Right, cx)
5173    //         });
5174
5175    //         workspace.update(cx, |workspace, cx| {
5176    //             // Since panel_1 was visible on the left, it should now be visible now that it's been moved to the right.
5177    //             // Since it was the only panel on the left, the left dock should now be closed.
5178    //             assert!(!workspace.left_dock().read(cx).is_open());
5179    //             assert!(workspace.left_dock().read(cx).visible_panel().is_none());
5180    //             let right_dock = workspace.right_dock();
5181    //             assert_eq!(
5182    //                 right_dock.read(cx).visible_panel().unwrap().id(),
5183    //                 panel_1.id()
5184    //             );
5185    //             assert_eq!(right_dock.read(cx).active_panel_size(cx).unwrap(), 1337.);
5186
5187    //             // Now we move panel_2Β to the left
5188    //             panel_2.set_position(DockPosition::Left, cx);
5189    //         });
5190
5191    //         workspace.update(cx, |workspace, cx| {
5192    //             // Since panel_2 was not visible on the right, we don't open the left dock.
5193    //             assert!(!workspace.left_dock().read(cx).is_open());
5194    //             // And the right dock is unaffected in it's displaying of panel_1
5195    //             assert!(workspace.right_dock().read(cx).is_open());
5196    //             assert_eq!(
5197    //                 workspace
5198    //                     .right_dock()
5199    //                     .read(cx)
5200    //                     .visible_panel()
5201    //                     .unwrap()
5202    //                     .id(),
5203    //                 panel_1.id()
5204    //             );
5205    //         });
5206
5207    //         // Move panel_1 back to the left
5208    //         panel_1.update(cx, |panel_1, cx| {
5209    //             panel_1.set_position(DockPosition::Left, cx)
5210    //         });
5211
5212    //         workspace.update(cx, |workspace, cx| {
5213    //             // Since panel_1 was visible on the right, we open the left dock and make panel_1 active.
5214    //             let left_dock = workspace.left_dock();
5215    //             assert!(left_dock.read(cx).is_open());
5216    //             assert_eq!(
5217    //                 left_dock.read(cx).visible_panel().unwrap().id(),
5218    //                 panel_1.id()
5219    //             );
5220    //             assert_eq!(left_dock.read(cx).active_panel_size(cx).unwrap(), 1337.);
5221    //             // And right the dock should be closed as it no longer has any panels.
5222    //             assert!(!workspace.right_dock().read(cx).is_open());
5223
5224    //             // Now we move panel_1 to the bottom
5225    //             panel_1.set_position(DockPosition::Bottom, cx);
5226    //         });
5227
5228    //         workspace.update(cx, |workspace, cx| {
5229    //             // Since panel_1 was visible on the left, we close the left dock.
5230    //             assert!(!workspace.left_dock().read(cx).is_open());
5231    //             // The bottom dock is sized based on the panel's default size,
5232    //             // since the panel orientation changed from vertical to horizontal.
5233    //             let bottom_dock = workspace.bottom_dock();
5234    //             assert_eq!(
5235    //                 bottom_dock.read(cx).active_panel_size(cx).unwrap(),
5236    //                 panel_1.size(cx),
5237    //             );
5238    //             // Close bottom dock and move panel_1 back to the left.
5239    //             bottom_dock.update(cx, |bottom_dock, cx| bottom_dock.set_open(false, cx));
5240    //             panel_1.set_position(DockPosition::Left, cx);
5241    //         });
5242
5243    //         // Emit activated event on panel 1
5244    //         panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::Activated));
5245
5246    //         // Now the left dock is open and panel_1 is active and focused.
5247    //         workspace.update(cx, |workspace, cx| {
5248    //             let left_dock = workspace.left_dock();
5249    //             assert!(left_dock.read(cx).is_open());
5250    //             assert_eq!(
5251    //                 left_dock.read(cx).visible_panel().unwrap().id(),
5252    //                 panel_1.id()
5253    //             );
5254    //             assert!(panel_1.is_focused(cx));
5255    //         });
5256
5257    //         // Emit closed event on panel 2, which is not active
5258    //         panel_2.update(cx, |_, cx| cx.emit(TestPanelEvent::Closed));
5259
5260    //         // Wo don't close the left dock, because panel_2 wasn't the active panel
5261    //         workspace.update(cx, |workspace, cx| {
5262    //             let left_dock = workspace.left_dock();
5263    //             assert!(left_dock.read(cx).is_open());
5264    //             assert_eq!(
5265    //                 left_dock.read(cx).visible_panel().unwrap().id(),
5266    //                 panel_1.id()
5267    //             );
5268    //         });
5269
5270    //         // Emitting a ZoomIn event shows the panel as zoomed.
5271    //         panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::ZoomIn));
5272    //         workspace.update(cx, |workspace, _| {
5273    //             assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
5274    //             assert_eq!(workspace.zoomed_position, Some(DockPosition::Left));
5275    //         });
5276
5277    //         // Move panel to another dock while it is zoomed
5278    //         panel_1.update(cx, |panel, cx| panel.set_position(DockPosition::Right, cx));
5279    //         workspace.update(cx, |workspace, _| {
5280    //             assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
5281    //             assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5282    //         });
5283
5284    //         // If focus is transferred to another view that's not a panel or another pane, we still show
5285    //         // the panel as zoomed.
5286    //         let focus_receiver = cx.build_view(|_| EmptyView);
5287    //         focus_receiver.update(cx, |_, cx| cx.focus_self());
5288    //         workspace.update(cx, |workspace, _| {
5289    //             assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
5290    //             assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5291    //         });
5292
5293    //         // If focus is transferred elsewhere in the workspace, the panel is no longer zoomed.
5294    //         workspace.update(cx, |_, cx| cx.focus_self());
5295    //         workspace.update(cx, |workspace, _| {
5296    //             assert_eq!(workspace.zoomed, None);
5297    //             assert_eq!(workspace.zoomed_position, None);
5298    //         });
5299
5300    //         // If focus is transferred again to another view that's not a panel or a pane, we won't
5301    //         // show the panel as zoomed because it wasn't zoomed before.
5302    //         focus_receiver.update(cx, |_, cx| cx.focus_self());
5303    //         workspace.update(cx, |workspace, _| {
5304    //             assert_eq!(workspace.zoomed, None);
5305    //             assert_eq!(workspace.zoomed_position, None);
5306    //         });
5307
5308    //         // When focus is transferred back to the panel, it is zoomed again.
5309    //         panel_1.update(cx, |_, cx| cx.focus_self());
5310    //         workspace.update(cx, |workspace, _| {
5311    //             assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
5312    //             assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5313    //         });
5314
5315    //         // Emitting a ZoomOut event unzooms the panel.
5316    //         panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::ZoomOut));
5317    //         workspace.update(cx, |workspace, _| {
5318    //             assert_eq!(workspace.zoomed, None);
5319    //             assert_eq!(workspace.zoomed_position, None);
5320    //         });
5321
5322    //         // Emit closed event on panel 1, which is active
5323    //         panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::Closed));
5324
5325    //         // Now the left dock is closed, because panel_1 was the active panel
5326    //         workspace.update(cx, |workspace, cx| {
5327    //             let right_dock = workspace.right_dock();
5328    //             assert!(!right_dock.read(cx).is_open());
5329    //         });
5330    //     }
5331
5332    pub fn init_test(cx: &mut TestAppContext) {
5333        cx.update(|cx| {
5334            let settings_store = SettingsStore::test(cx);
5335            cx.set_global(settings_store);
5336            theme::init(theme::LoadThemes::JustBase, cx);
5337            language::init(cx);
5338            crate::init_settings(cx);
5339            Project::init_settings(cx);
5340        });
5341    }
5342}