workspace2.rs

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