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