workspace2.rs

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