workspace2.rs

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