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                    self.update_active_view_for_followers(cx);
2081                }
2082            }
2083            pane::Event::ChangeItemTitle => {
2084                if pane == self.active_pane {
2085                    self.active_item_path_changed(cx);
2086                }
2087                self.update_window_edited(cx);
2088            }
2089            pane::Event::RemoveItem { item_id } => {
2090                self.update_window_edited(cx);
2091                if let hash_map::Entry::Occupied(entry) = self.panes_by_item.entry(*item_id) {
2092                    if entry.get().entity_id() == pane.entity_id() {
2093                        entry.remove();
2094                    }
2095                }
2096            }
2097            pane::Event::Focus => {
2098                self.handle_pane_focused(pane.clone(), cx);
2099            }
2100            pane::Event::ZoomIn => {
2101                if pane == self.active_pane {
2102                    pane.update(cx, |pane, cx| pane.set_zoomed(true, cx));
2103                    if pane.read(cx).has_focus(cx) {
2104                        self.zoomed = Some(pane.downgrade().into());
2105                        self.zoomed_position = None;
2106                    }
2107                    cx.notify();
2108                }
2109            }
2110            pane::Event::ZoomOut => {
2111                pane.update(cx, |pane, cx| pane.set_zoomed(false, cx));
2112                if self.zoomed_position.is_none() {
2113                    self.zoomed = None;
2114                }
2115                cx.notify();
2116            }
2117        }
2118
2119        self.serialize_workspace(cx);
2120    }
2121
2122    pub fn split_pane(
2123        &mut self,
2124        pane_to_split: View<Pane>,
2125        split_direction: SplitDirection,
2126        cx: &mut ViewContext<Self>,
2127    ) -> View<Pane> {
2128        let new_pane = self.add_pane(cx);
2129        self.center
2130            .split(&pane_to_split, &new_pane, split_direction)
2131            .unwrap();
2132        cx.notify();
2133        new_pane
2134    }
2135
2136    pub fn split_and_clone(
2137        &mut self,
2138        pane: View<Pane>,
2139        direction: SplitDirection,
2140        cx: &mut ViewContext<Self>,
2141    ) -> Option<View<Pane>> {
2142        let item = pane.read(cx).active_item()?;
2143        let maybe_pane_handle = if let Some(clone) = item.clone_on_split(self.database_id(), cx) {
2144            let new_pane = self.add_pane(cx);
2145            new_pane.update(cx, |pane, cx| pane.add_item(clone, true, true, None, cx));
2146            self.center.split(&pane, &new_pane, direction).unwrap();
2147            Some(new_pane)
2148        } else {
2149            None
2150        };
2151        cx.notify();
2152        maybe_pane_handle
2153    }
2154
2155    pub fn split_pane_with_item(
2156        &mut self,
2157        pane_to_split: WeakView<Pane>,
2158        split_direction: SplitDirection,
2159        from: WeakView<Pane>,
2160        item_id_to_move: EntityId,
2161        cx: &mut ViewContext<Self>,
2162    ) {
2163        let Some(pane_to_split) = pane_to_split.upgrade() else {
2164            return;
2165        };
2166        let Some(from) = from.upgrade() else {
2167            return;
2168        };
2169
2170        let new_pane = self.add_pane(cx);
2171        self.move_item(from.clone(), new_pane.clone(), item_id_to_move, 0, cx);
2172        self.center
2173            .split(&pane_to_split, &new_pane, split_direction)
2174            .unwrap();
2175        cx.notify();
2176    }
2177
2178    pub fn split_pane_with_project_entry(
2179        &mut self,
2180        pane_to_split: WeakView<Pane>,
2181        split_direction: SplitDirection,
2182        project_entry: ProjectEntryId,
2183        cx: &mut ViewContext<Self>,
2184    ) -> Option<Task<Result<()>>> {
2185        let pane_to_split = pane_to_split.upgrade()?;
2186        let new_pane = self.add_pane(cx);
2187        self.center
2188            .split(&pane_to_split, &new_pane, split_direction)
2189            .unwrap();
2190
2191        let path = self.project.read(cx).path_for_entry(project_entry, cx)?;
2192        let task = self.open_path(path, Some(new_pane.downgrade()), true, cx);
2193        Some(cx.foreground_executor().spawn(async move {
2194            task.await?;
2195            Ok(())
2196        }))
2197    }
2198
2199    pub fn move_item(
2200        &mut self,
2201        source: View<Pane>,
2202        destination: View<Pane>,
2203        item_id_to_move: EntityId,
2204        destination_index: usize,
2205        cx: &mut ViewContext<Self>,
2206    ) {
2207        let item_to_move = source
2208            .read(cx)
2209            .items()
2210            .enumerate()
2211            .find(|(_, item_handle)| item_handle.item_id() == item_id_to_move);
2212
2213        if item_to_move.is_none() {
2214            log::warn!("Tried to move item handle which was not in `from` pane. Maybe tab was closed during drop");
2215            return;
2216        }
2217        let (item_ix, item_handle) = item_to_move.unwrap();
2218        let item_handle = item_handle.clone();
2219
2220        if source != destination {
2221            // Close item from previous pane
2222            source.update(cx, |source, cx| {
2223                source.remove_item(item_ix, false, cx);
2224            });
2225        }
2226
2227        // This automatically removes duplicate items in the pane
2228        destination.update(cx, |destination, cx| {
2229            destination.add_item(item_handle, true, true, Some(destination_index), cx);
2230            destination.focus(cx)
2231        });
2232    }
2233
2234    fn remove_pane(&mut self, pane: View<Pane>, cx: &mut ViewContext<Self>) {
2235        if self.center.remove(&pane).unwrap() {
2236            self.force_remove_pane(&pane, cx);
2237            self.unfollow(&pane, cx);
2238            self.last_leaders_by_pane.remove(&pane.downgrade());
2239            for removed_item in pane.read(cx).items() {
2240                self.panes_by_item.remove(&removed_item.item_id());
2241            }
2242
2243            cx.notify();
2244        } else {
2245            self.active_item_path_changed(cx);
2246        }
2247    }
2248
2249    pub fn panes(&self) -> &[View<Pane>] {
2250        &self.panes
2251    }
2252
2253    pub fn active_pane(&self) -> &View<Pane> {
2254        &self.active_pane
2255    }
2256
2257    pub fn pane_for(&self, handle: &dyn ItemHandle) -> Option<View<Pane>> {
2258        let weak_pane = self.panes_by_item.get(&handle.item_id())?;
2259        weak_pane.upgrade()
2260    }
2261
2262    fn collaborator_left(&mut self, peer_id: PeerId, cx: &mut ViewContext<Self>) {
2263        self.follower_states.retain(|_, state| {
2264            if state.leader_id == peer_id {
2265                for item in state.items_by_leader_view_id.values() {
2266                    item.set_leader_peer_id(None, cx);
2267                }
2268                false
2269            } else {
2270                true
2271            }
2272        });
2273        cx.notify();
2274    }
2275
2276    fn start_following(
2277        &mut self,
2278        leader_id: PeerId,
2279        cx: &mut ViewContext<Self>,
2280    ) -> Option<Task<Result<()>>> {
2281        let pane = self.active_pane().clone();
2282
2283        self.last_leaders_by_pane
2284            .insert(pane.downgrade(), leader_id);
2285        self.unfollow(&pane, cx);
2286        self.follower_states.insert(
2287            pane.clone(),
2288            FollowerState {
2289                leader_id,
2290                active_view_id: None,
2291                items_by_leader_view_id: Default::default(),
2292            },
2293        );
2294        cx.notify();
2295
2296        let room_id = self.active_call()?.read(cx).room()?.read(cx).id();
2297        let project_id = self.project.read(cx).remote_id();
2298        let request = self.app_state.client.request(proto::Follow {
2299            room_id,
2300            project_id,
2301            leader_id: Some(leader_id),
2302        });
2303
2304        Some(cx.spawn(|this, mut cx| async move {
2305            let response = request.await?;
2306            this.update(&mut cx, |this, _| {
2307                let state = this
2308                    .follower_states
2309                    .get_mut(&pane)
2310                    .ok_or_else(|| anyhow!("following interrupted"))?;
2311                state.active_view_id = if let Some(active_view_id) = response.active_view_id {
2312                    Some(ViewId::from_proto(active_view_id)?)
2313                } else {
2314                    None
2315                };
2316                Ok::<_, anyhow::Error>(())
2317            })??;
2318            Self::add_views_from_leader(
2319                this.clone(),
2320                leader_id,
2321                vec![pane],
2322                response.views,
2323                &mut cx,
2324            )
2325            .await?;
2326            this.update(&mut cx, |this, cx| this.leader_updated(leader_id, cx))?;
2327            Ok(())
2328        }))
2329    }
2330
2331    // pub fn follow_next_collaborator(
2332    //     &mut self,
2333    //     _: &FollowNextCollaborator,
2334    //     cx: &mut ViewContext<Self>,
2335    // ) {
2336    //     let collaborators = self.project.read(cx).collaborators();
2337    //     let next_leader_id = if let Some(leader_id) = self.leader_for_pane(&self.active_pane) {
2338    //         let mut collaborators = collaborators.keys().copied();
2339    //         for peer_id in collaborators.by_ref() {
2340    //             if peer_id == leader_id {
2341    //                 break;
2342    //             }
2343    //         }
2344    //         collaborators.next()
2345    //     } else if let Some(last_leader_id) =
2346    //         self.last_leaders_by_pane.get(&self.active_pane.downgrade())
2347    //     {
2348    //         if collaborators.contains_key(last_leader_id) {
2349    //             Some(*last_leader_id)
2350    //         } else {
2351    //             None
2352    //         }
2353    //     } else {
2354    //         None
2355    //     };
2356
2357    //     let pane = self.active_pane.clone();
2358    //     let Some(leader_id) = next_leader_id.or_else(|| collaborators.keys().copied().next())
2359    //     else {
2360    //         return;
2361    //     };
2362    //     if Some(leader_id) == self.unfollow(&pane, cx) {
2363    //         return;
2364    //     }
2365    //     if let Some(task) = self.follow(leader_id, cx) {
2366    //         task.detach();
2367    //     }
2368    // }
2369
2370    pub fn follow(
2371        &mut self,
2372        leader_id: PeerId,
2373        cx: &mut ViewContext<Self>,
2374    ) -> Option<Task<Result<()>>> {
2375        let room = ActiveCall::global(cx).read(cx).room()?.read(cx);
2376        let project = self.project.read(cx);
2377
2378        let Some(remote_participant) = room.remote_participant_for_peer_id(leader_id) else {
2379            return None;
2380        };
2381
2382        let other_project_id = match remote_participant.location {
2383            call::ParticipantLocation::External => None,
2384            call::ParticipantLocation::UnsharedProject => None,
2385            call::ParticipantLocation::SharedProject { project_id } => {
2386                if Some(project_id) == project.remote_id() {
2387                    None
2388                } else {
2389                    Some(project_id)
2390                }
2391            }
2392        };
2393
2394        // if they are active in another project, follow there.
2395        if let Some(project_id) = other_project_id {
2396            let app_state = self.app_state.clone();
2397            return Some(crate::join_remote_project(
2398                project_id,
2399                remote_participant.user.id,
2400                app_state,
2401                cx,
2402            ));
2403        }
2404
2405        // if you're already following, find the right pane and focus it.
2406        for (pane, state) in &self.follower_states {
2407            if leader_id == state.leader_id {
2408                cx.focus_view(pane);
2409                return None;
2410            }
2411        }
2412
2413        // Otherwise, follow.
2414        self.start_following(leader_id, cx)
2415    }
2416
2417    //     // if you're already following, find the right pane and focus it.
2418    //     for (pane, state) in &self.follower_states {
2419    //         if leader_id == state.leader_id {
2420    //             cx.focus(pane);
2421    //             return None;
2422    //         }
2423    //     }
2424
2425    //     // Otherwise, follow.
2426    //     self.start_following(leader_id, cx)
2427    // }
2428
2429    pub fn unfollow(&mut self, pane: &View<Pane>, cx: &mut ViewContext<Self>) -> Option<PeerId> {
2430        let state = self.follower_states.remove(pane)?;
2431        let leader_id = state.leader_id;
2432        for (_, item) in state.items_by_leader_view_id {
2433            item.set_leader_peer_id(None, cx);
2434        }
2435
2436        if self
2437            .follower_states
2438            .values()
2439            .all(|state| state.leader_id != state.leader_id)
2440        {
2441            let project_id = self.project.read(cx).remote_id();
2442            let room_id = self.active_call()?.read(cx).room()?.read(cx).id();
2443            self.app_state
2444                .client
2445                .send(proto::Unfollow {
2446                    room_id,
2447                    project_id,
2448                    leader_id: Some(leader_id),
2449                })
2450                .log_err();
2451        }
2452
2453        cx.notify();
2454        Some(leader_id)
2455    }
2456
2457    //     pub fn is_being_followed(&self, peer_id: PeerId) -> bool {
2458    //         self.follower_states
2459    //             .values()
2460    //             .any(|state| state.leader_id == peer_id)
2461    //     }
2462
2463    fn active_item_path_changed(&mut self, cx: &mut ViewContext<Self>) {
2464        let active_entry = self.active_project_path(cx);
2465        self.project
2466            .update(cx, |project, cx| project.set_active_path(active_entry, cx));
2467        self.update_window_title(cx);
2468    }
2469
2470    fn update_window_title(&mut self, cx: &mut ViewContext<Self>) {
2471        let project = self.project().read(cx);
2472        let mut title = String::new();
2473
2474        if let Some(path) = self.active_item(cx).and_then(|item| item.project_path(cx)) {
2475            let filename = path
2476                .path
2477                .file_name()
2478                .map(|s| s.to_string_lossy())
2479                .or_else(|| {
2480                    Some(Cow::Borrowed(
2481                        project
2482                            .worktree_for_id(path.worktree_id, cx)?
2483                            .read(cx)
2484                            .root_name(),
2485                    ))
2486                });
2487
2488            if let Some(filename) = filename {
2489                title.push_str(filename.as_ref());
2490                title.push_str(" β€” ");
2491            }
2492        }
2493
2494        for (i, name) in project.worktree_root_names(cx).enumerate() {
2495            if i > 0 {
2496                title.push_str(", ");
2497            }
2498            title.push_str(name);
2499        }
2500
2501        if title.is_empty() {
2502            title = "empty project".to_string();
2503        }
2504
2505        if project.is_remote() {
2506            title.push_str(" ↙");
2507        } else if project.is_shared() {
2508            title.push_str(" β†—");
2509        }
2510
2511        cx.set_window_title(&title);
2512    }
2513
2514    fn update_window_edited(&mut self, cx: &mut ViewContext<Self>) {
2515        let is_edited = !self.project.read(cx).is_read_only()
2516            && self
2517                .items(cx)
2518                .any(|item| item.has_conflict(cx) || item.is_dirty(cx));
2519        if is_edited != self.window_edited {
2520            self.window_edited = is_edited;
2521            // todo!()
2522            // cx.set_window_edited(self.window_edited)
2523        }
2524    }
2525
2526    //     fn render_disconnected_overlay(
2527    //         &self,
2528    //         cx: &mut ViewContext<Workspace>,
2529    //     ) -> Option<AnyElement<Workspace>> {
2530    //         if self.project.read(cx).is_read_only() {
2531    //             enum DisconnectedOverlay {}
2532    //             Some(
2533    //                 MouseEventHandler::new::<DisconnectedOverlay, _>(0, cx, |_, cx| {
2534    //                     let theme = &theme::current(cx);
2535    //                     Label::new(
2536    //                         "Your connection to the remote project has been lost.",
2537    //                         theme.workspace.disconnected_overlay.text.clone(),
2538    //                     )
2539    //                     .aligned()
2540    //                     .contained()
2541    //                     .with_style(theme.workspace.disconnected_overlay.container)
2542    //                 })
2543    //                 .with_cursor_style(CursorStyle::Arrow)
2544    //                 .capture_all()
2545    //                 .into_any_named("disconnected overlay"),
2546    //             )
2547    //         } else {
2548    //             None
2549    //         }
2550    //     }
2551
2552    fn render_notifications(&self, cx: &ViewContext<Self>) -> Option<Div> {
2553        if self.notifications.is_empty() {
2554            None
2555        } else {
2556            Some(
2557                div()
2558                    .absolute()
2559                    .z_index(100)
2560                    .right_3()
2561                    .bottom_3()
2562                    .w_96()
2563                    .h_full()
2564                    .flex()
2565                    .flex_col()
2566                    .justify_end()
2567                    .gap_2()
2568                    .children(
2569                        self.notifications
2570                            .iter()
2571                            .map(|(_, _, notification)| notification.to_any()),
2572                    ),
2573            )
2574        }
2575    }
2576
2577    // RPC handlers
2578
2579    fn handle_follow(
2580        &mut self,
2581        follower_project_id: Option<u64>,
2582        cx: &mut ViewContext<Self>,
2583    ) -> proto::FollowResponse {
2584        let client = &self.app_state.client;
2585        let project_id = self.project.read(cx).remote_id();
2586
2587        let active_view_id = self.active_item(cx).and_then(|i| {
2588            Some(
2589                i.to_followable_item_handle(cx)?
2590                    .remote_id(client, cx)?
2591                    .to_proto(),
2592            )
2593        });
2594
2595        cx.notify();
2596
2597        self.last_active_view_id = active_view_id.clone();
2598        proto::FollowResponse {
2599            active_view_id,
2600            views: self
2601                .panes()
2602                .iter()
2603                .flat_map(|pane| {
2604                    let leader_id = self.leader_for_pane(pane);
2605                    pane.read(cx).items().filter_map({
2606                        let cx = &cx;
2607                        move |item| {
2608                            let item = item.to_followable_item_handle(cx)?;
2609                            if (project_id.is_none() || project_id != follower_project_id)
2610                                && item.is_project_item(cx)
2611                            {
2612                                return None;
2613                            }
2614                            let id = item.remote_id(client, cx)?.to_proto();
2615                            let variant = item.to_state_proto(cx)?;
2616                            Some(proto::View {
2617                                id: Some(id),
2618                                leader_id,
2619                                variant: Some(variant),
2620                            })
2621                        }
2622                    })
2623                })
2624                .collect(),
2625        }
2626    }
2627
2628    fn handle_update_followers(
2629        &mut self,
2630        leader_id: PeerId,
2631        message: proto::UpdateFollowers,
2632        _cx: &mut ViewContext<Self>,
2633    ) {
2634        self.leader_updates_tx
2635            .unbounded_send((leader_id, message))
2636            .ok();
2637    }
2638
2639    async fn process_leader_update(
2640        this: &WeakView<Self>,
2641        leader_id: PeerId,
2642        update: proto::UpdateFollowers,
2643        cx: &mut AsyncWindowContext,
2644    ) -> Result<()> {
2645        match update.variant.ok_or_else(|| anyhow!("invalid update"))? {
2646            proto::update_followers::Variant::UpdateActiveView(update_active_view) => {
2647                this.update(cx, |this, _| {
2648                    for (_, state) in &mut this.follower_states {
2649                        if state.leader_id == leader_id {
2650                            state.active_view_id =
2651                                if let Some(active_view_id) = update_active_view.id.clone() {
2652                                    Some(ViewId::from_proto(active_view_id)?)
2653                                } else {
2654                                    None
2655                                };
2656                        }
2657                    }
2658                    anyhow::Ok(())
2659                })??;
2660            }
2661            proto::update_followers::Variant::UpdateView(update_view) => {
2662                let variant = update_view
2663                    .variant
2664                    .ok_or_else(|| anyhow!("missing update view variant"))?;
2665                let id = update_view
2666                    .id
2667                    .ok_or_else(|| anyhow!("missing update view id"))?;
2668                let mut tasks = Vec::new();
2669                this.update(cx, |this, cx| {
2670                    let project = this.project.clone();
2671                    for (_, state) in &mut this.follower_states {
2672                        if state.leader_id == leader_id {
2673                            let view_id = ViewId::from_proto(id.clone())?;
2674                            if let Some(item) = state.items_by_leader_view_id.get(&view_id) {
2675                                tasks.push(item.apply_update_proto(&project, variant.clone(), cx));
2676                            }
2677                        }
2678                    }
2679                    anyhow::Ok(())
2680                })??;
2681                try_join_all(tasks).await.log_err();
2682            }
2683            proto::update_followers::Variant::CreateView(view) => {
2684                let panes = this.update(cx, |this, _| {
2685                    this.follower_states
2686                        .iter()
2687                        .filter_map(|(pane, state)| (state.leader_id == leader_id).then_some(pane))
2688                        .cloned()
2689                        .collect()
2690                })?;
2691                Self::add_views_from_leader(this.clone(), leader_id, panes, vec![view], cx).await?;
2692            }
2693        }
2694        this.update(cx, |this, cx| this.leader_updated(leader_id, cx))?;
2695        Ok(())
2696    }
2697
2698    async fn add_views_from_leader(
2699        this: WeakView<Self>,
2700        leader_id: PeerId,
2701        panes: Vec<View<Pane>>,
2702        views: Vec<proto::View>,
2703        cx: &mut AsyncWindowContext,
2704    ) -> Result<()> {
2705        let this = this.upgrade().context("workspace dropped")?;
2706
2707        let item_builders = cx.update(|_, cx| {
2708            cx.default_global::<FollowableItemBuilders>()
2709                .values()
2710                .map(|b| b.0)
2711                .collect::<Vec<_>>()
2712        })?;
2713
2714        let mut item_tasks_by_pane = HashMap::default();
2715        for pane in panes {
2716            let mut item_tasks = Vec::new();
2717            let mut leader_view_ids = Vec::new();
2718            for view in &views {
2719                let Some(id) = &view.id else { continue };
2720                let id = ViewId::from_proto(id.clone())?;
2721                let mut variant = view.variant.clone();
2722                if variant.is_none() {
2723                    Err(anyhow!("missing view variant"))?;
2724                }
2725                for build_item in &item_builders {
2726                    let task = cx.update(|_, cx| {
2727                        build_item(pane.clone(), this.clone(), id, &mut variant, cx)
2728                    })?;
2729                    if let Some(task) = task {
2730                        item_tasks.push(task);
2731                        leader_view_ids.push(id);
2732                        break;
2733                    } else {
2734                        assert!(variant.is_some());
2735                    }
2736                }
2737            }
2738
2739            item_tasks_by_pane.insert(pane, (item_tasks, leader_view_ids));
2740        }
2741
2742        for (pane, (item_tasks, leader_view_ids)) in item_tasks_by_pane {
2743            let items = futures::future::try_join_all(item_tasks).await?;
2744            this.update(cx, |this, cx| {
2745                let state = this.follower_states.get_mut(&pane)?;
2746                for (id, item) in leader_view_ids.into_iter().zip(items) {
2747                    item.set_leader_peer_id(Some(leader_id), cx);
2748                    state.items_by_leader_view_id.insert(id, item);
2749                }
2750
2751                Some(())
2752            })?;
2753        }
2754        Ok(())
2755    }
2756
2757    fn update_active_view_for_followers(&mut self, cx: &mut ViewContext<Self>) {
2758        let mut is_project_item = true;
2759        let mut update = proto::UpdateActiveView::default();
2760
2761        if let Some(item) = self.active_item(cx) {
2762            if item.focus_handle(cx).contains_focused(cx) {
2763                if let Some(item) = item.to_followable_item_handle(cx) {
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
2775        if update.id != self.last_active_view_id {
2776            self.last_active_view_id = update.id.clone();
2777            self.update_followers(
2778                is_project_item,
2779                proto::update_followers::Variant::UpdateActiveView(update),
2780                cx,
2781            );
2782        }
2783    }
2784
2785    fn update_followers(
2786        &self,
2787        project_only: bool,
2788        update: proto::update_followers::Variant,
2789        cx: &mut WindowContext,
2790    ) -> Option<()> {
2791        let project_id = if project_only {
2792            self.project.read(cx).remote_id()
2793        } else {
2794            None
2795        };
2796        self.app_state().workspace_store.update(cx, |store, cx| {
2797            store.update_followers(project_id, update, cx)
2798        })
2799    }
2800
2801    pub fn leader_for_pane(&self, pane: &View<Pane>) -> Option<PeerId> {
2802        self.follower_states.get(pane).map(|state| state.leader_id)
2803    }
2804
2805    fn leader_updated(&mut self, leader_id: PeerId, cx: &mut ViewContext<Self>) -> Option<()> {
2806        cx.notify();
2807
2808        let call = self.active_call()?;
2809        let room = call.read(cx).room()?.read(cx);
2810        let participant = room.remote_participant_for_peer_id(leader_id)?;
2811        let mut items_to_activate = Vec::new();
2812
2813        let leader_in_this_app;
2814        let leader_in_this_project;
2815        match participant.location {
2816            call::ParticipantLocation::SharedProject { project_id } => {
2817                leader_in_this_app = true;
2818                leader_in_this_project = Some(project_id) == self.project.read(cx).remote_id();
2819            }
2820            call::ParticipantLocation::UnsharedProject => {
2821                leader_in_this_app = true;
2822                leader_in_this_project = false;
2823            }
2824            call::ParticipantLocation::External => {
2825                leader_in_this_app = false;
2826                leader_in_this_project = false;
2827            }
2828        };
2829
2830        for (pane, state) in &self.follower_states {
2831            if state.leader_id != leader_id {
2832                continue;
2833            }
2834            if let (Some(active_view_id), true) = (state.active_view_id, leader_in_this_app) {
2835                if let Some(item) = state.items_by_leader_view_id.get(&active_view_id) {
2836                    if leader_in_this_project || !item.is_project_item(cx) {
2837                        items_to_activate.push((pane.clone(), item.boxed_clone()));
2838                    }
2839                } else {
2840                    log::warn!(
2841                        "unknown view id {:?} for leader {:?}",
2842                        active_view_id,
2843                        leader_id
2844                    );
2845                }
2846                continue;
2847            }
2848
2849            if let Some(shared_screen) = self.shared_screen_for_peer(leader_id, pane, cx) {
2850                items_to_activate.push((pane.clone(), Box::new(shared_screen)));
2851            }
2852        }
2853
2854        for (pane, item) in items_to_activate {
2855            let pane_was_focused = pane.read(cx).has_focus(cx);
2856            if let Some(index) = pane.update(cx, |pane, _| pane.index_for_item(item.as_ref())) {
2857                pane.update(cx, |pane, cx| pane.activate_item(index, false, false, cx));
2858            } else {
2859                pane.update(cx, |pane, cx| {
2860                    pane.add_item(item.boxed_clone(), false, false, None, cx)
2861                });
2862            }
2863
2864            if pane_was_focused {
2865                pane.update(cx, |pane, cx| pane.focus_active_item(cx));
2866            }
2867        }
2868
2869        None
2870    }
2871
2872    fn shared_screen_for_peer(
2873        &self,
2874        peer_id: PeerId,
2875        pane: &View<Pane>,
2876        cx: &mut ViewContext<Self>,
2877    ) -> Option<View<SharedScreen>> {
2878        let call = self.active_call()?;
2879        let room = call.read(cx).room()?.read(cx);
2880        let participant = room.remote_participant_for_peer_id(peer_id)?;
2881        let track = participant.video_tracks.values().next()?.clone();
2882        let user = participant.user.clone();
2883
2884        for item in pane.read(cx).items_of_type::<SharedScreen>() {
2885            if item.read(cx).peer_id == peer_id {
2886                return Some(item);
2887            }
2888        }
2889
2890        Some(cx.build_view(|cx| SharedScreen::new(&track, peer_id, user.clone(), cx)))
2891    }
2892
2893    pub fn on_window_activation_changed(&mut self, cx: &mut ViewContext<Self>) {
2894        if cx.is_window_active() {
2895            self.update_active_view_for_followers(cx);
2896            cx.background_executor()
2897                .spawn(persistence::DB.update_timestamp(self.database_id()))
2898                .detach();
2899        } else {
2900            for pane in &self.panes {
2901                pane.update(cx, |pane, cx| {
2902                    if let Some(item) = pane.active_item() {
2903                        item.workspace_deactivated(cx);
2904                    }
2905                    if matches!(
2906                        WorkspaceSettings::get_global(cx).autosave,
2907                        AutosaveSetting::OnWindowChange | AutosaveSetting::OnFocusChange
2908                    ) {
2909                        for item in pane.items() {
2910                            Pane::autosave_item(item.as_ref(), self.project.clone(), cx)
2911                                .detach_and_log_err(cx);
2912                        }
2913                    }
2914                });
2915            }
2916        }
2917    }
2918
2919    fn active_call(&self) -> Option<&Model<ActiveCall>> {
2920        self.active_call.as_ref().map(|(call, _)| call)
2921    }
2922
2923    fn on_active_call_event(
2924        &mut self,
2925        _: Model<ActiveCall>,
2926        event: &call::room::Event,
2927        cx: &mut ViewContext<Self>,
2928    ) {
2929        match event {
2930            call::room::Event::ParticipantLocationChanged { participant_id }
2931            | call::room::Event::RemoteVideoTracksChanged { participant_id } => {
2932                self.leader_updated(*participant_id, cx);
2933            }
2934            _ => {}
2935        }
2936    }
2937
2938    pub fn database_id(&self) -> WorkspaceId {
2939        self.database_id
2940    }
2941
2942    fn location(&self, cx: &AppContext) -> Option<WorkspaceLocation> {
2943        let project = self.project().read(cx);
2944
2945        if project.is_local() {
2946            Some(
2947                project
2948                    .visible_worktrees(cx)
2949                    .map(|worktree| worktree.read(cx).abs_path())
2950                    .collect::<Vec<_>>()
2951                    .into(),
2952            )
2953        } else {
2954            None
2955        }
2956    }
2957
2958    fn remove_panes(&mut self, member: Member, cx: &mut ViewContext<Workspace>) {
2959        match member {
2960            Member::Axis(PaneAxis { members, .. }) => {
2961                for child in members.iter() {
2962                    self.remove_panes(child.clone(), cx)
2963                }
2964            }
2965            Member::Pane(pane) => {
2966                self.force_remove_pane(&pane, cx);
2967            }
2968        }
2969    }
2970
2971    fn force_remove_pane(&mut self, pane: &View<Pane>, cx: &mut ViewContext<Workspace>) {
2972        self.panes.retain(|p| p != pane);
2973        self.panes
2974            .last()
2975            .unwrap()
2976            .update(cx, |pane, cx| pane.focus(cx));
2977        if self.last_active_center_pane == Some(pane.downgrade()) {
2978            self.last_active_center_pane = None;
2979        }
2980        cx.notify();
2981    }
2982
2983    fn schedule_serialize(&mut self, cx: &mut ViewContext<Self>) {
2984        self._schedule_serialize = Some(cx.spawn(|this, mut cx| async move {
2985            cx.background_executor()
2986                .timer(Duration::from_millis(100))
2987                .await;
2988            this.update(&mut cx, |this, cx| this.serialize_workspace(cx))
2989                .log_err();
2990        }));
2991    }
2992
2993    fn serialize_workspace(&self, cx: &mut ViewContext<Self>) {
2994        fn serialize_pane_handle(pane_handle: &View<Pane>, cx: &WindowContext) -> SerializedPane {
2995            let (items, active) = {
2996                let pane = pane_handle.read(cx);
2997                let active_item_id = pane.active_item().map(|item| item.item_id());
2998                (
2999                    pane.items()
3000                        .filter_map(|item_handle| {
3001                            Some(SerializedItem {
3002                                kind: Arc::from(item_handle.serialized_item_kind()?),
3003                                item_id: item_handle.item_id().as_u64(),
3004                                active: Some(item_handle.item_id()) == active_item_id,
3005                            })
3006                        })
3007                        .collect::<Vec<_>>(),
3008                    pane.has_focus(cx),
3009                )
3010            };
3011
3012            SerializedPane::new(items, active)
3013        }
3014
3015        fn build_serialized_pane_group(
3016            pane_group: &Member,
3017            cx: &WindowContext,
3018        ) -> SerializedPaneGroup {
3019            match pane_group {
3020                Member::Axis(PaneAxis {
3021                    axis,
3022                    members,
3023                    flexes,
3024                    bounding_boxes: _,
3025                }) => SerializedPaneGroup::Group {
3026                    axis: *axis,
3027                    children: members
3028                        .iter()
3029                        .map(|member| build_serialized_pane_group(member, cx))
3030                        .collect::<Vec<_>>(),
3031                    flexes: Some(flexes.lock().clone()),
3032                },
3033                Member::Pane(pane_handle) => {
3034                    SerializedPaneGroup::Pane(serialize_pane_handle(&pane_handle, cx))
3035                }
3036            }
3037        }
3038
3039        fn build_serialized_docks(
3040            this: &Workspace,
3041            cx: &mut ViewContext<Workspace>,
3042        ) -> DockStructure {
3043            let left_dock = this.left_dock.read(cx);
3044            let left_visible = left_dock.is_open();
3045            let left_active_panel = left_dock
3046                .visible_panel()
3047                .and_then(|panel| Some(panel.persistent_name().to_string()));
3048            let left_dock_zoom = left_dock
3049                .visible_panel()
3050                .map(|panel| panel.is_zoomed(cx))
3051                .unwrap_or(false);
3052
3053            let right_dock = this.right_dock.read(cx);
3054            let right_visible = right_dock.is_open();
3055            let right_active_panel = right_dock
3056                .visible_panel()
3057                .and_then(|panel| Some(panel.persistent_name().to_string()));
3058            let right_dock_zoom = right_dock
3059                .visible_panel()
3060                .map(|panel| panel.is_zoomed(cx))
3061                .unwrap_or(false);
3062
3063            let bottom_dock = this.bottom_dock.read(cx);
3064            let bottom_visible = bottom_dock.is_open();
3065            let bottom_active_panel = bottom_dock
3066                .visible_panel()
3067                .and_then(|panel| Some(panel.persistent_name().to_string()));
3068            let bottom_dock_zoom = bottom_dock
3069                .visible_panel()
3070                .map(|panel| panel.is_zoomed(cx))
3071                .unwrap_or(false);
3072
3073            DockStructure {
3074                left: DockData {
3075                    visible: left_visible,
3076                    active_panel: left_active_panel,
3077                    zoom: left_dock_zoom,
3078                },
3079                right: DockData {
3080                    visible: right_visible,
3081                    active_panel: right_active_panel,
3082                    zoom: right_dock_zoom,
3083                },
3084                bottom: DockData {
3085                    visible: bottom_visible,
3086                    active_panel: bottom_active_panel,
3087                    zoom: bottom_dock_zoom,
3088                },
3089            }
3090        }
3091
3092        if let Some(location) = self.location(cx) {
3093            // Load bearing special case:
3094            //  - with_local_workspace() relies on this to not have other stuff open
3095            //    when you open your log
3096            if !location.paths().is_empty() {
3097                let center_group = build_serialized_pane_group(&self.center.root, cx);
3098                let docks = build_serialized_docks(self, cx);
3099
3100                let serialized_workspace = SerializedWorkspace {
3101                    id: self.database_id,
3102                    location,
3103                    center_group,
3104                    bounds: Default::default(),
3105                    display: Default::default(),
3106                    docks,
3107                };
3108
3109                cx.spawn(|_, _| persistence::DB.save_workspace(serialized_workspace))
3110                    .detach();
3111            }
3112        }
3113    }
3114
3115    pub(crate) fn load_workspace(
3116        serialized_workspace: SerializedWorkspace,
3117        paths_to_open: Vec<Option<ProjectPath>>,
3118        cx: &mut ViewContext<Workspace>,
3119    ) -> Task<Result<Vec<Option<Box<dyn ItemHandle>>>>> {
3120        cx.spawn(|workspace, mut cx| async move {
3121            let (project, old_center_pane) = workspace.update(&mut cx, |workspace, _| {
3122                (
3123                    workspace.project().clone(),
3124                    workspace.last_active_center_pane.clone(),
3125                )
3126            })?;
3127
3128            let mut center_group = None;
3129            let mut center_items = None;
3130
3131            // Traverse the splits tree and add to things
3132            if let Some((group, active_pane, items)) = serialized_workspace
3133                .center_group
3134                .deserialize(
3135                    &project,
3136                    serialized_workspace.id,
3137                    workspace.clone(),
3138                    &mut cx,
3139                )
3140                .await
3141            {
3142                center_items = Some(items);
3143                center_group = Some((group, active_pane))
3144            }
3145
3146            let mut items_by_project_path = cx.update(|_, cx| {
3147                center_items
3148                    .unwrap_or_default()
3149                    .into_iter()
3150                    .filter_map(|item| {
3151                        let item = item?;
3152                        let project_path = item.project_path(cx)?;
3153                        Some((project_path, item))
3154                    })
3155                    .collect::<HashMap<_, _>>()
3156            })?;
3157
3158            let opened_items = paths_to_open
3159                .into_iter()
3160                .map(|path_to_open| {
3161                    path_to_open
3162                        .and_then(|path_to_open| items_by_project_path.remove(&path_to_open))
3163                })
3164                .collect::<Vec<_>>();
3165
3166            // Remove old panes from workspace panes list
3167            workspace.update(&mut cx, |workspace, cx| {
3168                if let Some((center_group, active_pane)) = center_group {
3169                    workspace.remove_panes(workspace.center.root.clone(), cx);
3170
3171                    // Swap workspace center group
3172                    workspace.center = PaneGroup::with_root(center_group);
3173                    workspace.last_active_center_pane = active_pane.as_ref().map(|p| p.downgrade());
3174                    if let Some(active_pane) = active_pane {
3175                        workspace.active_pane = active_pane;
3176                        cx.focus_self();
3177                    } else {
3178                        workspace.active_pane = workspace.center.first_pane().clone();
3179                    }
3180                }
3181
3182                let docks = serialized_workspace.docks;
3183                workspace.left_dock.update(cx, |dock, cx| {
3184                    dock.set_open(docks.left.visible, cx);
3185                    if let Some(active_panel) = docks.left.active_panel {
3186                        if let Some(ix) = dock.panel_index_for_persistent_name(&active_panel, cx) {
3187                            dock.activate_panel(ix, cx);
3188                        }
3189                    }
3190                    dock.active_panel()
3191                        .map(|panel| panel.set_zoomed(docks.left.zoom, cx));
3192                    if docks.left.visible && docks.left.zoom {
3193                        cx.focus_self()
3194                    }
3195                });
3196                // TODO: I think the bug is that setting zoom or active undoes the bottom zoom or something
3197                workspace.right_dock.update(cx, |dock, cx| {
3198                    dock.set_open(docks.right.visible, cx);
3199                    if let Some(active_panel) = docks.right.active_panel {
3200                        if let Some(ix) = dock.panel_index_for_persistent_name(&active_panel, cx) {
3201                            dock.activate_panel(ix, cx);
3202                        }
3203                    }
3204                    dock.active_panel()
3205                        .map(|panel| panel.set_zoomed(docks.right.zoom, cx));
3206
3207                    if docks.right.visible && docks.right.zoom {
3208                        cx.focus_self()
3209                    }
3210                });
3211                workspace.bottom_dock.update(cx, |dock, cx| {
3212                    dock.set_open(docks.bottom.visible, cx);
3213                    if let Some(active_panel) = docks.bottom.active_panel {
3214                        if let Some(ix) = dock.panel_index_for_persistent_name(&active_panel, cx) {
3215                            dock.activate_panel(ix, cx);
3216                        }
3217                    }
3218
3219                    dock.active_panel()
3220                        .map(|panel| panel.set_zoomed(docks.bottom.zoom, cx));
3221
3222                    if docks.bottom.visible && docks.bottom.zoom {
3223                        cx.focus_self()
3224                    }
3225                });
3226
3227                cx.notify();
3228            })?;
3229
3230            // Serialize ourself to make sure our timestamps and any pane / item changes are replicated
3231            workspace.update(&mut cx, |workspace, cx| workspace.serialize_workspace(cx))?;
3232
3233            Ok(opened_items)
3234        })
3235    }
3236
3237    fn actions(&self, div: Div, cx: &mut ViewContext<Self>) -> Div {
3238        self.add_workspace_actions_listeners(div, cx)
3239            .on_action(cx.listener(Self::close_inactive_items_and_panes))
3240            .on_action(cx.listener(Self::close_all_items_and_panes))
3241            .on_action(cx.listener(Self::save_all))
3242            .on_action(cx.listener(Self::add_folder_to_project))
3243            .on_action(cx.listener(|workspace, _: &Unfollow, cx| {
3244                let pane = workspace.active_pane().clone();
3245                workspace.unfollow(&pane, cx);
3246            }))
3247            .on_action(cx.listener(|workspace, action: &Save, cx| {
3248                workspace
3249                    .save_active_item(action.save_intent.unwrap_or(SaveIntent::Save), cx)
3250                    .detach_and_log_err(cx);
3251            }))
3252            .on_action(cx.listener(|workspace, _: &SaveAs, cx| {
3253                workspace
3254                    .save_active_item(SaveIntent::SaveAs, cx)
3255                    .detach_and_log_err(cx);
3256            }))
3257            .on_action(cx.listener(|workspace, _: &ActivatePreviousPane, cx| {
3258                workspace.activate_previous_pane(cx)
3259            }))
3260            .on_action(
3261                cx.listener(|workspace, _: &ActivateNextPane, cx| workspace.activate_next_pane(cx)),
3262            )
3263            .on_action(
3264                cx.listener(|workspace, action: &ActivatePaneInDirection, cx| {
3265                    workspace.activate_pane_in_direction(action.0, cx)
3266                }),
3267            )
3268            .on_action(cx.listener(|workspace, action: &SwapPaneInDirection, cx| {
3269                workspace.swap_pane_in_direction(action.0, cx)
3270            }))
3271            .on_action(cx.listener(|this, e: &ToggleLeftDock, cx| {
3272                this.toggle_dock(DockPosition::Left, cx);
3273            }))
3274            .on_action(
3275                cx.listener(|workspace: &mut Workspace, _: &ToggleRightDock, cx| {
3276                    workspace.toggle_dock(DockPosition::Right, cx);
3277                }),
3278            )
3279            .on_action(
3280                cx.listener(|workspace: &mut Workspace, _: &ToggleBottomDock, cx| {
3281                    workspace.toggle_dock(DockPosition::Bottom, cx);
3282                }),
3283            )
3284            .on_action(
3285                cx.listener(|workspace: &mut Workspace, _: &CloseAllDocks, cx| {
3286                    workspace.close_all_docks(cx);
3287                }),
3288            )
3289            .on_action(cx.listener(Workspace::open))
3290            .on_action(cx.listener(Workspace::close_window))
3291
3292        //     cx.add_action(Workspace::activate_pane_at_index);
3293        //     cx.add_action(|workspace: &mut Workspace, _: &ReopenClosedItem, cx| {
3294        //         workspace.reopen_closed_item(cx).detach();
3295        //     });
3296        //     cx.add_action(|workspace: &mut Workspace, _: &GoBack, cx| {
3297        //         workspace
3298        //             .go_back(workspace.active_pane().downgrade(), cx)
3299        //             .detach();
3300        //     });
3301        //     cx.add_action(|workspace: &mut Workspace, _: &GoForward, cx| {
3302        //         workspace
3303        //             .go_forward(workspace.active_pane().downgrade(), cx)
3304        //             .detach();
3305        //     });
3306
3307        //     cx.add_action(|_: &mut Workspace, _: &install_cli::Install, cx| {
3308        //         cx.spawn(|workspace, mut cx| async move {
3309        //             let err = install_cli::install_cli(&cx)
3310        //                 .await
3311        //                 .context("Failed to create CLI symlink");
3312
3313        //             workspace.update(&mut cx, |workspace, cx| {
3314        //                 if matches!(err, Err(_)) {
3315        //                     err.notify_err(workspace, cx);
3316        //                 } else {
3317        //                     workspace.show_notification(1, cx, |cx| {
3318        //                         cx.build_view(|_| {
3319        //                             MessageNotification::new("Successfully installed the `zed` binary")
3320        //                         })
3321        //                     });
3322        //                 }
3323        //             })
3324        //         })
3325        //         .detach();
3326        //     });
3327    }
3328
3329    #[cfg(any(test, feature = "test-support"))]
3330    pub fn test_new(project: Model<Project>, cx: &mut ViewContext<Self>) -> Self {
3331        use node_runtime::FakeNodeRuntime;
3332
3333        let client = project.read(cx).client();
3334        let user_store = project.read(cx).user_store();
3335
3336        let workspace_store = cx.build_model(|cx| WorkspaceStore::new(client.clone(), cx));
3337        let app_state = Arc::new(AppState {
3338            languages: project.read(cx).languages().clone(),
3339            workspace_store,
3340            client,
3341            user_store,
3342            fs: project.read(cx).fs().clone(),
3343            build_window_options: |_, _, _| Default::default(),
3344            node_runtime: FakeNodeRuntime::new(),
3345        });
3346        let workspace = Self::new(0, project, app_state, cx);
3347        workspace.active_pane.update(cx, |pane, cx| pane.focus(cx));
3348        workspace
3349    }
3350
3351    //     fn render_dock(&self, position: DockPosition, cx: &WindowContext) -> Option<AnyElement<Self>> {
3352    //         let dock = match position {
3353    //             DockPosition::Left => &self.left_dock,
3354    //             DockPosition::Right => &self.right_dock,
3355    //             DockPosition::Bottom => &self.bottom_dock,
3356    //         };
3357    //         let active_panel = dock.read(cx).visible_panel()?;
3358    //         let element = if Some(active_panel.id()) == self.zoomed.as_ref().map(|zoomed| zoomed.id()) {
3359    //             dock.read(cx).render_placeholder(cx)
3360    //         } else {
3361    //             ChildView::new(dock, cx).into_any()
3362    //         };
3363
3364    //         Some(
3365    //             element
3366    //                 .constrained()
3367    //                 .dynamically(move |constraint, _, cx| match position {
3368    //                     DockPosition::Left | DockPosition::Right => SizeConstraint::new(
3369    //                         Vector2F::new(20., constraint.min.y()),
3370    //                         Vector2F::new(cx.window_size().x() * 0.8, constraint.max.y()),
3371    //                     ),
3372    //                     DockPosition::Bottom => SizeConstraint::new(
3373    //                         Vector2F::new(constraint.min.x(), 20.),
3374    //                         Vector2F::new(constraint.max.x(), cx.window_size().y() * 0.8),
3375    //                     ),
3376    //                 })
3377    //                 .into_any(),
3378    //         )
3379    //     }
3380    // }
3381    pub fn register_action<A: Action>(
3382        &mut self,
3383        callback: impl Fn(&mut Self, &A, &mut ViewContext<Self>) + 'static,
3384    ) -> &mut Self {
3385        let callback = Arc::new(callback);
3386
3387        self.workspace_actions.push(Box::new(move |div, cx| {
3388            let callback = callback.clone();
3389            div.on_action(
3390                cx.listener(move |workspace, event, cx| (callback.clone())(workspace, event, cx)),
3391            )
3392        }));
3393        self
3394    }
3395
3396    fn add_workspace_actions_listeners(&self, mut div: Div, cx: &mut ViewContext<Self>) -> Div {
3397        let mut div = div
3398            .on_action(cx.listener(Self::close_inactive_items_and_panes))
3399            .on_action(cx.listener(Self::close_all_items_and_panes))
3400            .on_action(cx.listener(Self::add_folder_to_project))
3401            .on_action(cx.listener(Self::save_all))
3402            .on_action(cx.listener(Self::open));
3403        for action in self.workspace_actions.iter() {
3404            div = (action)(div, cx)
3405        }
3406        div
3407    }
3408
3409    pub fn active_modal<V: ManagedView + 'static>(
3410        &mut self,
3411        cx: &ViewContext<Self>,
3412    ) -> Option<View<V>> {
3413        self.modal_layer.read(cx).active_modal()
3414    }
3415
3416    pub fn toggle_modal<V: ManagedView, B>(&mut self, cx: &mut ViewContext<Self>, build: B)
3417    where
3418        B: FnOnce(&mut ViewContext<V>) -> V,
3419    {
3420        self.modal_layer
3421            .update(cx, |modal_layer, cx| modal_layer.toggle_modal(cx, build))
3422    }
3423}
3424
3425fn window_bounds_env_override(cx: &AsyncAppContext) -> Option<WindowBounds> {
3426    let display_origin = cx
3427        .update(|cx| Some(cx.displays().first()?.bounds().origin))
3428        .ok()??;
3429    ZED_WINDOW_POSITION
3430        .zip(*ZED_WINDOW_SIZE)
3431        .map(|(position, size)| {
3432            WindowBounds::Fixed(Bounds {
3433                origin: display_origin + position,
3434                size,
3435            })
3436        })
3437}
3438
3439fn open_items(
3440    serialized_workspace: Option<SerializedWorkspace>,
3441    mut project_paths_to_open: Vec<(PathBuf, Option<ProjectPath>)>,
3442    app_state: Arc<AppState>,
3443    cx: &mut ViewContext<Workspace>,
3444) -> impl 'static + Future<Output = Result<Vec<Option<Result<Box<dyn ItemHandle>>>>>> {
3445    let restored_items = serialized_workspace.map(|serialized_workspace| {
3446        Workspace::load_workspace(
3447            serialized_workspace,
3448            project_paths_to_open
3449                .iter()
3450                .map(|(_, project_path)| project_path)
3451                .cloned()
3452                .collect(),
3453            cx,
3454        )
3455    });
3456
3457    cx.spawn(|workspace, mut cx| async move {
3458        let mut opened_items = Vec::with_capacity(project_paths_to_open.len());
3459
3460        if let Some(restored_items) = restored_items {
3461            let restored_items = restored_items.await?;
3462
3463            let restored_project_paths = restored_items
3464                .iter()
3465                .filter_map(|item| {
3466                    cx.update(|_, cx| item.as_ref()?.project_path(cx))
3467                        .ok()
3468                        .flatten()
3469                })
3470                .collect::<HashSet<_>>();
3471
3472            for restored_item in restored_items {
3473                opened_items.push(restored_item.map(Ok));
3474            }
3475
3476            project_paths_to_open
3477                .iter_mut()
3478                .for_each(|(_, project_path)| {
3479                    if let Some(project_path_to_open) = project_path {
3480                        if restored_project_paths.contains(project_path_to_open) {
3481                            *project_path = None;
3482                        }
3483                    }
3484                });
3485        } else {
3486            for _ in 0..project_paths_to_open.len() {
3487                opened_items.push(None);
3488            }
3489        }
3490        assert!(opened_items.len() == project_paths_to_open.len());
3491
3492        let tasks =
3493            project_paths_to_open
3494                .into_iter()
3495                .enumerate()
3496                .map(|(i, (abs_path, project_path))| {
3497                    let workspace = workspace.clone();
3498                    cx.spawn(|mut cx| {
3499                        let fs = app_state.fs.clone();
3500                        async move {
3501                            let file_project_path = project_path?;
3502                            if fs.is_file(&abs_path).await {
3503                                Some((
3504                                    i,
3505                                    workspace
3506                                        .update(&mut cx, |workspace, cx| {
3507                                            workspace.open_path(file_project_path, None, true, cx)
3508                                        })
3509                                        .log_err()?
3510                                        .await,
3511                                ))
3512                            } else {
3513                                None
3514                            }
3515                        }
3516                    })
3517                });
3518
3519        let tasks = tasks.collect::<Vec<_>>();
3520
3521        let tasks = futures::future::join_all(tasks.into_iter());
3522        for maybe_opened_path in tasks.await.into_iter() {
3523            if let Some((i, path_open_result)) = maybe_opened_path {
3524                opened_items[i] = Some(path_open_result);
3525            }
3526        }
3527
3528        Ok(opened_items)
3529    })
3530}
3531
3532fn notify_if_database_failed(workspace: WindowHandle<Workspace>, cx: &mut AsyncAppContext) {
3533    const REPORT_ISSUE_URL: &str ="https://github.com/zed-industries/community/issues/new?assignees=&labels=defect%2Ctriage&template=2_bug_report.yml";
3534
3535    workspace
3536        .update(cx, |workspace, cx| {
3537            if (*db::ALL_FILE_DB_FAILED).load(std::sync::atomic::Ordering::Acquire) {
3538                workspace.show_notification_once(0, cx, |cx| {
3539                    cx.build_view(|_| {
3540                        MessageNotification::new("Failed to load the database file.")
3541                            .with_click_message("Click to let us know about this error")
3542                            .on_click(|cx| cx.open_url(REPORT_ISSUE_URL))
3543                    })
3544                });
3545            }
3546        })
3547        .log_err();
3548}
3549
3550impl FocusableView for Workspace {
3551    fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
3552        self.active_pane.focus_handle(cx)
3553    }
3554}
3555
3556impl Render for Workspace {
3557    type Element = Div;
3558
3559    fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
3560        let mut context = KeyContext::default();
3561        context.add("Workspace");
3562
3563        let (ui_font, ui_font_size) = {
3564            let theme_settings = ThemeSettings::get_global(cx);
3565            (
3566                theme_settings.ui_font.family.clone(),
3567                theme_settings.ui_font_size.clone(),
3568            )
3569        };
3570
3571        cx.set_rem_size(ui_font_size);
3572
3573        self.actions(div(), cx)
3574            .key_context(context)
3575            .relative()
3576            .size_full()
3577            .flex()
3578            .flex_col()
3579            .font(ui_font)
3580            .gap_0()
3581            .justify_start()
3582            .items_start()
3583            .text_color(cx.theme().colors().text)
3584            .bg(cx.theme().colors().background)
3585            .border()
3586            .border_color(cx.theme().colors().border)
3587            .children(self.titlebar_item.clone())
3588            .child(
3589                div()
3590                    .id("workspace")
3591                    .relative()
3592                    .flex_1()
3593                    .w_full()
3594                    .flex()
3595                    .overflow_hidden()
3596                    .border_t()
3597                    .border_b()
3598                    .border_color(cx.theme().colors().border)
3599                    .child(self.modal_layer.clone())
3600                    .child(
3601                        div()
3602                            .flex()
3603                            .flex_row()
3604                            .flex_1()
3605                            .h_full()
3606                            // Left Dock
3607                            .child(
3608                                div()
3609                                    .flex()
3610                                    .flex_none()
3611                                    .overflow_hidden()
3612                                    .child(self.left_dock.clone()),
3613                            )
3614                            // Panes
3615                            .child(
3616                                div()
3617                                    .flex()
3618                                    .flex_col()
3619                                    .flex_1()
3620                                    .child(self.center.render(
3621                                        &self.project,
3622                                        &self.follower_states,
3623                                        self.active_call(),
3624                                        &self.active_pane,
3625                                        self.zoomed.as_ref(),
3626                                        &self.app_state,
3627                                        cx,
3628                                    ))
3629                                    .child(self.bottom_dock.clone()),
3630                            )
3631                            // Right Dock
3632                            .child(
3633                                div()
3634                                    .flex()
3635                                    .flex_none()
3636                                    .overflow_hidden()
3637                                    .child(self.right_dock.clone()),
3638                            ),
3639                    )
3640                    .children(self.render_notifications(cx)),
3641            )
3642            .child(self.status_bar.clone())
3643    }
3644}
3645
3646// impl View for Workspace {
3647
3648//     fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
3649//         let theme = theme::current(cx).clone();
3650//         Stack::new()
3651//             .with_child(
3652//                 Flex::column()
3653//                     .with_child(self.render_titlebar(&theme, cx))
3654//                     .with_child(
3655//                         Stack::new()
3656//                             .with_child({
3657//                                 let project = self.project.clone();
3658//                                 Flex::row()
3659//                                     .with_children(self.render_dock(DockPosition::Left, cx))
3660//                                     .with_child(
3661//                                         Flex::column()
3662//                                             .with_child(
3663//                                                 FlexItem::new(
3664//                                                     self.center.render(
3665//                                                         &project,
3666//                                                         &theme,
3667//                                                         &self.follower_states,
3668//                                                         self.active_call(),
3669//                                                         self.active_pane(),
3670//                                                         self.zoomed
3671//                                                             .as_ref()
3672//                                                             .and_then(|zoomed| zoomed.upgrade(cx))
3673//                                                             .as_ref(),
3674//                                                         &self.app_state,
3675//                                                         cx,
3676//                                                     ),
3677//                                                 )
3678//                                                 .flex(1., true),
3679//                                             )
3680//                                             .with_children(
3681//                                                 self.render_dock(DockPosition::Bottom, cx),
3682//                                             )
3683//                                             .flex(1., true),
3684//                                     )
3685//                                     .with_children(self.render_dock(DockPosition::Right, cx))
3686//                             })
3687//                             .with_child(Overlay::new(
3688//                                 Stack::new()
3689//                                     .with_children(self.zoomed.as_ref().and_then(|zoomed| {
3690//                                         enum ZoomBackground {}
3691//                                         let zoomed = zoomed.upgrade(cx)?;
3692
3693//                                         let mut foreground_style =
3694//                                             theme.workspace.zoomed_pane_foreground;
3695//                                         if let Some(zoomed_dock_position) = self.zoomed_position {
3696//                                             foreground_style =
3697//                                                 theme.workspace.zoomed_panel_foreground;
3698//                                             let margin = foreground_style.margin.top;
3699//                                             let border = foreground_style.border.top;
3700
3701//                                             // Only include a margin and border on the opposite side.
3702//                                             foreground_style.margin.top = 0.;
3703//                                             foreground_style.margin.left = 0.;
3704//                                             foreground_style.margin.bottom = 0.;
3705//                                             foreground_style.margin.right = 0.;
3706//                                             foreground_style.border.top = false;
3707//                                             foreground_style.border.left = false;
3708//                                             foreground_style.border.bottom = false;
3709//                                             foreground_style.border.right = false;
3710//                                             match zoomed_dock_position {
3711//                                                 DockPosition::Left => {
3712//                                                     foreground_style.margin.right = margin;
3713//                                                     foreground_style.border.right = border;
3714//                                                 }
3715//                                                 DockPosition::Right => {
3716//                                                     foreground_style.margin.left = margin;
3717//                                                     foreground_style.border.left = border;
3718//                                                 }
3719//                                                 DockPosition::Bottom => {
3720//                                                     foreground_style.margin.top = margin;
3721//                                                     foreground_style.border.top = border;
3722//                                                 }
3723//                                             }
3724//                                         }
3725
3726//                                         Some(
3727//                                             ChildView::new(&zoomed, cx)
3728//                                                 .contained()
3729//                                                 .with_style(foreground_style)
3730//                                                 .aligned()
3731//                                                 .contained()
3732//                                                 .with_style(theme.workspace.zoomed_background)
3733//                                                 .mouse::<ZoomBackground>(0)
3734//                                                 .capture_all()
3735//                                                 .on_down(
3736//                                                     MouseButton::Left,
3737//                                                     |_, this: &mut Self, cx| {
3738//                                                         this.zoom_out(cx);
3739//                                                     },
3740//                                                 ),
3741//                                         )
3742//                                     }))
3743//                                     .with_children(self.modal.as_ref().map(|modal| {
3744//                                         // Prevent clicks within the modal from falling
3745//                                         // through to the rest of the workspace.
3746//                                         enum ModalBackground {}
3747//                                         MouseEventHandler::new::<ModalBackground, _>(
3748//                                             0,
3749//                                             cx,
3750//                                             |_, cx| ChildView::new(modal.view.as_any(), cx),
3751//                                         )
3752//                                         .on_click(MouseButton::Left, |_, _, _| {})
3753//                                         .contained()
3754//                                         .with_style(theme.workspace.modal)
3755//                                         .aligned()
3756//                                         .top()
3757//                                     }))
3758//                                     .with_children(self.render_notifications(&theme.workspace, cx)),
3759//                             ))
3760//                             .provide_resize_bounds::<WorkspaceBounds>()
3761//                             .flex(1.0, true),
3762//                     )
3763//                     .with_child(ChildView::new(&self.status_bar, cx))
3764//                     .contained()
3765//                     .with_background_color(theme.workspace.background),
3766//             )
3767//             .with_children(DragAndDrop::render(cx))
3768//             .with_children(self.render_disconnected_overlay(cx))
3769//             .into_any_named("workspace")
3770//     }
3771
3772//     fn modifiers_changed(&mut self, e: &ModifiersChangedEvent, cx: &mut ViewContext<Self>) -> bool {
3773//         DragAndDrop::<Workspace>::update_modifiers(e.modifiers, cx)
3774//     }
3775// }
3776
3777impl WorkspaceStore {
3778    pub fn new(client: Arc<Client>, cx: &mut ModelContext<Self>) -> Self {
3779        Self {
3780            workspaces: Default::default(),
3781            followers: Default::default(),
3782            _subscriptions: vec![
3783                client.add_request_handler(cx.weak_model(), Self::handle_follow),
3784                client.add_message_handler(cx.weak_model(), Self::handle_unfollow),
3785                client.add_message_handler(cx.weak_model(), Self::handle_update_followers),
3786            ],
3787            client,
3788        }
3789    }
3790
3791    pub fn update_followers(
3792        &self,
3793        project_id: Option<u64>,
3794        update: proto::update_followers::Variant,
3795        cx: &AppContext,
3796    ) -> Option<()> {
3797        if !cx.has_global::<Model<ActiveCall>>() {
3798            return None;
3799        }
3800
3801        let room_id = ActiveCall::global(cx).read(cx).room()?.read(cx).id();
3802        let follower_ids: Vec<_> = self
3803            .followers
3804            .iter()
3805            .filter_map(|follower| {
3806                if follower.project_id == project_id || project_id.is_none() {
3807                    Some(follower.peer_id.into())
3808                } else {
3809                    None
3810                }
3811            })
3812            .collect();
3813        if follower_ids.is_empty() {
3814            return None;
3815        }
3816        self.client
3817            .send(proto::UpdateFollowers {
3818                room_id,
3819                project_id,
3820                follower_ids,
3821                variant: Some(update),
3822            })
3823            .log_err()
3824    }
3825
3826    pub async fn handle_follow(
3827        this: Model<Self>,
3828        envelope: TypedEnvelope<proto::Follow>,
3829        _: Arc<Client>,
3830        mut cx: AsyncAppContext,
3831    ) -> Result<proto::FollowResponse> {
3832        this.update(&mut cx, |this, cx| {
3833            let follower = Follower {
3834                project_id: envelope.payload.project_id,
3835                peer_id: envelope.original_sender_id()?,
3836            };
3837            let active_project = ActiveCall::global(cx).read(cx).location().cloned();
3838
3839            let mut response = proto::FollowResponse::default();
3840            for workspace in &this.workspaces {
3841                workspace
3842                    .update(cx, |workspace, cx| {
3843                        let handler_response = workspace.handle_follow(follower.project_id, cx);
3844                        if response.views.is_empty() {
3845                            response.views = handler_response.views;
3846                        } else {
3847                            response.views.extend_from_slice(&handler_response.views);
3848                        }
3849
3850                        if let Some(active_view_id) = handler_response.active_view_id.clone() {
3851                            if response.active_view_id.is_none()
3852                                || Some(workspace.project.downgrade()) == active_project
3853                            {
3854                                response.active_view_id = Some(active_view_id);
3855                            }
3856                        }
3857                    })
3858                    .ok();
3859            }
3860
3861            if let Err(ix) = this.followers.binary_search(&follower) {
3862                this.followers.insert(ix, follower);
3863            }
3864
3865            Ok(response)
3866        })?
3867    }
3868
3869    async fn handle_unfollow(
3870        model: Model<Self>,
3871        envelope: TypedEnvelope<proto::Unfollow>,
3872        _: Arc<Client>,
3873        mut cx: AsyncAppContext,
3874    ) -> Result<()> {
3875        model.update(&mut cx, |this, _| {
3876            let follower = Follower {
3877                project_id: envelope.payload.project_id,
3878                peer_id: envelope.original_sender_id()?,
3879            };
3880            if let Ok(ix) = this.followers.binary_search(&follower) {
3881                this.followers.remove(ix);
3882            }
3883            Ok(())
3884        })?
3885    }
3886
3887    async fn handle_update_followers(
3888        this: Model<Self>,
3889        envelope: TypedEnvelope<proto::UpdateFollowers>,
3890        _: Arc<Client>,
3891        mut cx: AsyncAppContext,
3892    ) -> Result<()> {
3893        let leader_id = envelope.original_sender_id()?;
3894        let update = envelope.payload;
3895
3896        this.update(&mut cx, |this, cx| {
3897            for workspace in &this.workspaces {
3898                workspace.update(cx, |workspace, cx| {
3899                    let project_id = workspace.project.read(cx).remote_id();
3900                    if update.project_id != project_id && update.project_id.is_some() {
3901                        return;
3902                    }
3903                    workspace.handle_update_followers(leader_id, update.clone(), cx);
3904                })?;
3905            }
3906            Ok(())
3907        })?
3908    }
3909}
3910
3911impl ViewId {
3912    pub(crate) fn from_proto(message: proto::ViewId) -> Result<Self> {
3913        Ok(Self {
3914            creator: message
3915                .creator
3916                .ok_or_else(|| anyhow!("creator is missing"))?,
3917            id: message.id,
3918        })
3919    }
3920
3921    pub(crate) fn to_proto(&self) -> proto::ViewId {
3922        proto::ViewId {
3923            creator: Some(self.creator),
3924            id: self.id,
3925        }
3926    }
3927}
3928
3929pub trait WorkspaceHandle {
3930    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath>;
3931}
3932
3933impl WorkspaceHandle for View<Workspace> {
3934    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath> {
3935        self.read(cx)
3936            .worktrees(cx)
3937            .flat_map(|worktree| {
3938                let worktree_id = worktree.read(cx).id();
3939                worktree.read(cx).files(true, 0).map(move |f| ProjectPath {
3940                    worktree_id,
3941                    path: f.path.clone(),
3942                })
3943            })
3944            .collect::<Vec<_>>()
3945    }
3946}
3947
3948impl std::fmt::Debug for OpenPaths {
3949    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3950        f.debug_struct("OpenPaths")
3951            .field("paths", &self.paths)
3952            .finish()
3953    }
3954}
3955
3956pub fn activate_workspace_for_project(
3957    cx: &mut AppContext,
3958    predicate: impl Fn(&Project, &AppContext) -> bool + Send + 'static,
3959) -> Option<WindowHandle<Workspace>> {
3960    for window in cx.windows() {
3961        let Some(workspace) = window.downcast::<Workspace>() else {
3962            continue;
3963        };
3964
3965        let predicate = workspace
3966            .update(cx, |workspace, cx| {
3967                let project = workspace.project.read(cx);
3968                if predicate(project, cx) {
3969                    cx.activate_window();
3970                    true
3971                } else {
3972                    false
3973                }
3974            })
3975            .log_err()
3976            .unwrap_or(false);
3977
3978        if predicate {
3979            return Some(workspace);
3980        }
3981    }
3982
3983    None
3984}
3985
3986pub async fn last_opened_workspace_paths() -> Option<WorkspaceLocation> {
3987    DB.last_workspace().await.log_err().flatten()
3988}
3989
3990async fn join_channel_internal(
3991    channel_id: u64,
3992    app_state: &Arc<AppState>,
3993    requesting_window: Option<WindowHandle<Workspace>>,
3994    active_call: &Model<ActiveCall>,
3995    cx: &mut AsyncAppContext,
3996) -> Result<bool> {
3997    let (should_prompt, open_room) = active_call.read_with(cx, |active_call, cx| {
3998        let Some(room) = active_call.room().map(|room| room.read(cx)) else {
3999            return (false, None);
4000        };
4001
4002        let already_in_channel = room.channel_id() == Some(channel_id);
4003        let should_prompt = room.is_sharing_project()
4004            && room.remote_participants().len() > 0
4005            && !already_in_channel;
4006        let open_room = if already_in_channel {
4007            active_call.room().cloned()
4008        } else {
4009            None
4010        };
4011        (should_prompt, open_room)
4012    })?;
4013
4014    if let Some(room) = open_room {
4015        let task = room.update(cx, |room, cx| {
4016            if let Some((project, host)) = room.most_active_project(cx) {
4017                return Some(join_remote_project(project, host, app_state.clone(), cx));
4018            }
4019
4020            None
4021        })?;
4022        if let Some(task) = task {
4023            task.await?;
4024        }
4025        return anyhow::Ok(true);
4026    }
4027
4028    if should_prompt {
4029        if let Some(workspace) = requesting_window {
4030            let answer  = workspace.update(cx, |_, cx| {
4031                cx.prompt(
4032                    PromptLevel::Warning,
4033                    "Leaving this call will unshare your current project.\nDo you want to switch channels?",
4034                    &["Yes, Join Channel", "Cancel"],
4035                )
4036            })?.await;
4037
4038            if answer == Ok(1) {
4039                return Ok(false);
4040            }
4041        } else {
4042            return Ok(false); // unreachable!() hopefully
4043        }
4044    }
4045
4046    let client = cx.update(|cx| active_call.read(cx).client())?;
4047
4048    let mut client_status = client.status();
4049
4050    // this loop will terminate within client::CONNECTION_TIMEOUT seconds.
4051    'outer: loop {
4052        let Some(status) = client_status.recv().await else {
4053            return Err(anyhow!("error connecting"));
4054        };
4055
4056        match status {
4057            Status::Connecting
4058            | Status::Authenticating
4059            | Status::Reconnecting
4060            | Status::Reauthenticating => continue,
4061            Status::Connected { .. } => break 'outer,
4062            Status::SignedOut => return Err(anyhow!("not signed in")),
4063            Status::UpgradeRequired => return Err(anyhow!("zed is out of date")),
4064            Status::ConnectionError | Status::ConnectionLost | Status::ReconnectionError { .. } => {
4065                return Err(anyhow!("zed is offline"))
4066            }
4067        }
4068    }
4069
4070    let room = active_call
4071        .update(cx, |active_call, cx| {
4072            active_call.join_channel(channel_id, cx)
4073        })?
4074        .await?;
4075
4076    let Some(room) = room else {
4077        return anyhow::Ok(true);
4078    };
4079
4080    room.update(cx, |room, _| room.room_update_completed())?
4081        .await;
4082
4083    let task = room.update(cx, |room, cx| {
4084        if let Some((project, host)) = room.most_active_project(cx) {
4085            return Some(join_remote_project(project, host, app_state.clone(), cx));
4086        }
4087
4088        None
4089    })?;
4090    if let Some(task) = task {
4091        task.await?;
4092        return anyhow::Ok(true);
4093    }
4094    anyhow::Ok(false)
4095}
4096
4097pub fn join_channel(
4098    channel_id: u64,
4099    app_state: Arc<AppState>,
4100    requesting_window: Option<WindowHandle<Workspace>>,
4101    cx: &mut AppContext,
4102) -> Task<Result<()>> {
4103    let active_call = ActiveCall::global(cx);
4104    cx.spawn(|mut cx| async move {
4105        let result = join_channel_internal(
4106            channel_id,
4107            &app_state,
4108            requesting_window,
4109            &active_call,
4110            &mut cx,
4111        )
4112        .await;
4113
4114        // join channel succeeded, and opened a window
4115        if matches!(result, Ok(true)) {
4116            return anyhow::Ok(());
4117        }
4118
4119        if requesting_window.is_some() {
4120            return anyhow::Ok(());
4121        }
4122
4123        // find an existing workspace to focus and show call controls
4124        let mut active_window = activate_any_workspace_window(&mut cx);
4125        if active_window.is_none() {
4126            // no open workspaces, make one to show the error in (blergh)
4127            cx.update(|cx| Workspace::new_local(vec![], app_state.clone(), requesting_window, cx))?
4128                .await?;
4129        }
4130
4131        active_window = activate_any_workspace_window(&mut cx);
4132        let Some(active_window) = active_window else {
4133            return anyhow::Ok(());
4134        };
4135
4136        if let Err(err) = result {
4137            active_window
4138                .update(&mut cx, |_, cx| {
4139                    cx.prompt(
4140                        PromptLevel::Critical,
4141                        &format!("Failed to join channel: {}", err),
4142                        &["Ok"],
4143                    )
4144                })?
4145                .await
4146                .ok();
4147        }
4148
4149        // return ok, we showed the error to the user.
4150        return anyhow::Ok(());
4151    })
4152}
4153
4154pub fn activate_any_workspace_window(cx: &mut AsyncAppContext) -> Option<AnyWindowHandle> {
4155    cx.update(|cx| {
4156        for window in cx.windows() {
4157            let is_workspace = window.downcast::<Workspace>().is_some();
4158            if is_workspace {
4159                window.update(cx, |_, cx| cx.activate_window()).ok();
4160                return Some(window);
4161            }
4162        }
4163        None
4164    })
4165    .ok()
4166    .flatten()
4167}
4168
4169#[allow(clippy::type_complexity)]
4170pub fn open_paths(
4171    abs_paths: &[PathBuf],
4172    app_state: &Arc<AppState>,
4173    requesting_window: Option<WindowHandle<Workspace>>,
4174    cx: &mut AppContext,
4175) -> Task<
4176    anyhow::Result<(
4177        WindowHandle<Workspace>,
4178        Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
4179    )>,
4180> {
4181    let app_state = app_state.clone();
4182    let abs_paths = abs_paths.to_vec();
4183    // Open paths in existing workspace if possible
4184    let existing = activate_workspace_for_project(cx, {
4185        let abs_paths = abs_paths.clone();
4186        move |project, cx| project.contains_paths(&abs_paths, cx)
4187    });
4188    cx.spawn(move |mut cx| async move {
4189        if let Some(existing) = existing {
4190            // // Ok((
4191            //     existing.clone(),
4192            //     cx.update_window_root(&existing, |workspace, cx| {
4193            //         workspace.open_paths(abs_paths, true, cx)
4194            //     })?
4195            //     .await,
4196            // ))
4197            todo!()
4198        } else {
4199            cx.update(move |cx| {
4200                Workspace::new_local(abs_paths, app_state.clone(), requesting_window, cx)
4201            })?
4202            .await
4203        }
4204    })
4205}
4206
4207pub fn open_new(
4208    app_state: &Arc<AppState>,
4209    cx: &mut AppContext,
4210    init: impl FnOnce(&mut Workspace, &mut ViewContext<Workspace>) + 'static + Send,
4211) -> Task<()> {
4212    let task = Workspace::new_local(Vec::new(), app_state.clone(), None, cx);
4213    cx.spawn(|mut cx| async move {
4214        if let Some((workspace, opened_paths)) = task.await.log_err() {
4215            workspace
4216                .update(&mut cx, |workspace, cx| {
4217                    if opened_paths.is_empty() {
4218                        init(workspace, cx)
4219                    }
4220                })
4221                .log_err();
4222        }
4223    })
4224}
4225
4226pub fn create_and_open_local_file(
4227    path: &'static Path,
4228    cx: &mut ViewContext<Workspace>,
4229    default_content: impl 'static + Send + FnOnce() -> Rope,
4230) -> Task<Result<Box<dyn ItemHandle>>> {
4231    cx.spawn(|workspace, mut cx| async move {
4232        let fs = workspace.update(&mut cx, |workspace, _| workspace.app_state().fs.clone())?;
4233        if !fs.is_file(path).await {
4234            fs.create_file(path, Default::default()).await?;
4235            fs.save(path, &default_content(), Default::default())
4236                .await?;
4237        }
4238
4239        let mut items = workspace
4240            .update(&mut cx, |workspace, cx| {
4241                workspace.with_local_workspace(cx, |workspace, cx| {
4242                    workspace.open_paths(vec![path.to_path_buf()], false, cx)
4243                })
4244            })?
4245            .await?
4246            .await;
4247
4248        let item = items.pop().flatten();
4249        item.ok_or_else(|| anyhow!("path {path:?} is not a file"))?
4250    })
4251}
4252
4253pub fn join_remote_project(
4254    project_id: u64,
4255    follow_user_id: u64,
4256    app_state: Arc<AppState>,
4257    cx: &mut AppContext,
4258) -> Task<Result<()>> {
4259    let windows = cx.windows();
4260    cx.spawn(|mut cx| async move {
4261        let existing_workspace = windows.into_iter().find_map(|window| {
4262            window.downcast::<Workspace>().and_then(|window| {
4263                window
4264                    .update(&mut cx, |workspace, cx| {
4265                        if workspace.project().read(cx).remote_id() == Some(project_id) {
4266                            Some(window)
4267                        } else {
4268                            None
4269                        }
4270                    })
4271                    .unwrap_or(None)
4272            })
4273        });
4274
4275        let workspace = if let Some(existing_workspace) = existing_workspace {
4276            existing_workspace
4277        } else {
4278            let active_call = cx.update(|cx| ActiveCall::global(cx))?;
4279            let room = active_call
4280                .read_with(&cx, |call, _| call.room().cloned())?
4281                .ok_or_else(|| anyhow!("not in a call"))?;
4282            let project = room
4283                .update(&mut cx, |room, cx| {
4284                    room.join_project(
4285                        project_id,
4286                        app_state.languages.clone(),
4287                        app_state.fs.clone(),
4288                        cx,
4289                    )
4290                })?
4291                .await?;
4292
4293            let window_bounds_override = window_bounds_env_override(&cx);
4294            cx.update(|cx| {
4295                let options = (app_state.build_window_options)(window_bounds_override, None, cx);
4296                cx.open_window(options, |cx| {
4297                    cx.build_view(|cx| Workspace::new(0, project, app_state.clone(), cx))
4298                })
4299            })?
4300        };
4301
4302        workspace.update(&mut cx, |workspace, cx| {
4303            cx.activate(true);
4304            cx.activate_window();
4305
4306            if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
4307                let follow_peer_id = room
4308                    .read(cx)
4309                    .remote_participants()
4310                    .iter()
4311                    .find(|(_, participant)| participant.user.id == follow_user_id)
4312                    .map(|(_, p)| p.peer_id)
4313                    .or_else(|| {
4314                        // If we couldn't follow the given user, follow the host instead.
4315                        let collaborator = workspace
4316                            .project()
4317                            .read(cx)
4318                            .collaborators()
4319                            .values()
4320                            .find(|collaborator| collaborator.replica_id == 0)?;
4321                        Some(collaborator.peer_id)
4322                    });
4323
4324                if let Some(follow_peer_id) = follow_peer_id {
4325                    workspace
4326                        .follow(follow_peer_id, cx)
4327                        .map(|follow| follow.detach_and_log_err(cx));
4328                }
4329            }
4330        })?;
4331
4332        anyhow::Ok(())
4333    })
4334}
4335
4336pub fn restart(_: &Restart, cx: &mut AppContext) {
4337    let should_confirm = WorkspaceSettings::get_global(cx).confirm_quit;
4338    let mut workspace_windows = cx
4339        .windows()
4340        .into_iter()
4341        .filter_map(|window| window.downcast::<Workspace>())
4342        .collect::<Vec<_>>();
4343
4344    // If multiple windows have unsaved changes, and need a save prompt,
4345    // prompt in the active window before switching to a different window.
4346    workspace_windows.sort_by_key(|window| window.is_active(&cx) == Some(false));
4347
4348    let mut prompt = None;
4349    if let (true, Some(window)) = (should_confirm, workspace_windows.first()) {
4350        prompt = window
4351            .update(cx, |_, cx| {
4352                cx.prompt(
4353                    PromptLevel::Info,
4354                    "Are you sure you want to restart?",
4355                    &["Restart", "Cancel"],
4356                )
4357            })
4358            .ok();
4359    }
4360
4361    cx.spawn(|mut cx| async move {
4362        if let Some(mut prompt) = prompt {
4363            let answer = prompt.await?;
4364            if answer != 0 {
4365                return Ok(());
4366            }
4367        }
4368
4369        // If the user cancels any save prompt, then keep the app open.
4370        for window in workspace_windows {
4371            if let Ok(should_close) = window.update(&mut cx, |workspace, cx| {
4372                workspace.prepare_to_close(true, cx)
4373            }) {
4374                if !should_close.await? {
4375                    return Ok(());
4376                }
4377            }
4378        }
4379
4380        cx.update(|cx| cx.restart())
4381    })
4382    .detach_and_log_err(cx);
4383}
4384
4385fn parse_pixel_position_env_var(value: &str) -> Option<Point<GlobalPixels>> {
4386    let mut parts = value.split(',');
4387    let x: usize = parts.next()?.parse().ok()?;
4388    let y: usize = parts.next()?.parse().ok()?;
4389    Some(point((x as f64).into(), (y as f64).into()))
4390}
4391
4392fn parse_pixel_size_env_var(value: &str) -> Option<Size<GlobalPixels>> {
4393    let mut parts = value.split(',');
4394    let width: usize = parts.next()?.parse().ok()?;
4395    let height: usize = parts.next()?.parse().ok()?;
4396    Some(size((width as f64).into(), (height as f64).into()))
4397}
4398
4399#[cfg(test)]
4400mod tests {
4401    use super::*;
4402    use crate::item::{
4403        test::{TestItem, TestProjectItem},
4404        ItemEvent,
4405    };
4406    use fs::FakeFs;
4407    use gpui::TestAppContext;
4408    use project::{Project, ProjectEntryId};
4409    use serde_json::json;
4410    use settings::SettingsStore;
4411    use std::{cell::RefCell, rc::Rc};
4412
4413    #[gpui::test]
4414    async fn test_tab_disambiguation(cx: &mut TestAppContext) {
4415        init_test(cx);
4416
4417        let fs = FakeFs::new(cx.executor());
4418        let project = Project::test(fs, [], cx).await;
4419        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
4420
4421        // Adding an item with no ambiguity renders the tab without detail.
4422        let item1 = cx.build_view(|cx| {
4423            let mut item = TestItem::new(cx);
4424            item.tab_descriptions = Some(vec!["c", "b1/c", "a/b1/c"]);
4425            item
4426        });
4427        workspace.update(cx, |workspace, cx| {
4428            workspace.add_item(Box::new(item1.clone()), cx);
4429        });
4430        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(0)));
4431
4432        // Adding an item that creates ambiguity increases the level of detail on
4433        // both tabs.
4434        let item2 = cx.build_view(|cx| {
4435            let mut item = TestItem::new(cx);
4436            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
4437            item
4438        });
4439        workspace.update(cx, |workspace, cx| {
4440            workspace.add_item(Box::new(item2.clone()), cx);
4441        });
4442        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4443        item2.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4444
4445        // Adding an item that creates ambiguity increases the level of detail only
4446        // on the ambiguous tabs. In this case, the ambiguity can't be resolved so
4447        // we stop at the highest detail available.
4448        let item3 = cx.build_view(|cx| {
4449            let mut item = TestItem::new(cx);
4450            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
4451            item
4452        });
4453        workspace.update(cx, |workspace, cx| {
4454            workspace.add_item(Box::new(item3.clone()), cx);
4455        });
4456        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4457        item2.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
4458        item3.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
4459    }
4460
4461    #[gpui::test]
4462    async fn test_tracking_active_path(cx: &mut TestAppContext) {
4463        init_test(cx);
4464
4465        let fs = FakeFs::new(cx.executor());
4466        fs.insert_tree(
4467            "/root1",
4468            json!({
4469                "one.txt": "",
4470                "two.txt": "",
4471            }),
4472        )
4473        .await;
4474        fs.insert_tree(
4475            "/root2",
4476            json!({
4477                "three.txt": "",
4478            }),
4479        )
4480        .await;
4481
4482        let project = Project::test(fs, ["root1".as_ref()], cx).await;
4483        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
4484        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
4485        let worktree_id = project.read_with(cx, |project, cx| {
4486            project.worktrees().next().unwrap().read(cx).id()
4487        });
4488
4489        let item1 = cx.build_view(|cx| {
4490            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "one.txt", cx)])
4491        });
4492        let item2 = cx.build_view(|cx| {
4493            TestItem::new(cx).with_project_items(&[TestProjectItem::new(2, "two.txt", cx)])
4494        });
4495
4496        // Add an item to an empty pane
4497        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item1), cx));
4498        project.read_with(cx, |project, cx| {
4499            assert_eq!(
4500                project.active_entry(),
4501                project
4502                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
4503                    .map(|e| e.id)
4504            );
4505        });
4506        assert_eq!(cx.window_title().as_deref(), Some("one.txt β€” root1"));
4507
4508        // Add a second item to a non-empty pane
4509        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item2), cx));
4510        assert_eq!(cx.window_title().as_deref(), Some("two.txt β€” root1"));
4511        project.read_with(cx, |project, cx| {
4512            assert_eq!(
4513                project.active_entry(),
4514                project
4515                    .entry_for_path(&(worktree_id, "two.txt").into(), cx)
4516                    .map(|e| e.id)
4517            );
4518        });
4519
4520        // Close the active item
4521        pane.update(cx, |pane, cx| {
4522            pane.close_active_item(&Default::default(), cx).unwrap()
4523        })
4524        .await
4525        .unwrap();
4526        assert_eq!(cx.window_title().as_deref(), Some("one.txt β€” root1"));
4527        project.read_with(cx, |project, cx| {
4528            assert_eq!(
4529                project.active_entry(),
4530                project
4531                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
4532                    .map(|e| e.id)
4533            );
4534        });
4535
4536        // Add a project folder
4537        project
4538            .update(cx, |project, cx| {
4539                project.find_or_create_local_worktree("/root2", true, cx)
4540            })
4541            .await
4542            .unwrap();
4543        assert_eq!(cx.window_title().as_deref(), Some("one.txt β€” root1, root2"));
4544
4545        // Remove a project folder
4546        project.update(cx, |project, cx| project.remove_worktree(worktree_id, cx));
4547        assert_eq!(cx.window_title().as_deref(), Some("one.txt β€” root2"));
4548    }
4549
4550    #[gpui::test]
4551    async fn test_close_window(cx: &mut TestAppContext) {
4552        init_test(cx);
4553
4554        let fs = FakeFs::new(cx.executor());
4555        fs.insert_tree("/root", json!({ "one": "" })).await;
4556
4557        let project = Project::test(fs, ["root".as_ref()], cx).await;
4558        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
4559
4560        // When there are no dirty items, there's nothing to do.
4561        let item1 = cx.build_view(|cx| TestItem::new(cx));
4562        workspace.update(cx, |w, cx| w.add_item(Box::new(item1.clone()), cx));
4563        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
4564        assert!(task.await.unwrap());
4565
4566        // When there are dirty untitled items, prompt to save each one. If the user
4567        // cancels any prompt, then abort.
4568        let item2 = cx.build_view(|cx| TestItem::new(cx).with_dirty(true));
4569        let item3 = cx.build_view(|cx| {
4570            TestItem::new(cx)
4571                .with_dirty(true)
4572                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4573        });
4574        workspace.update(cx, |w, cx| {
4575            w.add_item(Box::new(item2.clone()), cx);
4576            w.add_item(Box::new(item3.clone()), cx);
4577        });
4578        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
4579        cx.executor().run_until_parked();
4580        cx.simulate_prompt_answer(2); // cancel save all
4581        cx.executor().run_until_parked();
4582        cx.simulate_prompt_answer(2); // cancel save all
4583        cx.executor().run_until_parked();
4584        assert!(!cx.has_pending_prompt());
4585        assert!(!task.await.unwrap());
4586    }
4587
4588    #[gpui::test]
4589    async fn test_close_pane_items(cx: &mut TestAppContext) {
4590        init_test(cx);
4591
4592        let fs = FakeFs::new(cx.executor());
4593
4594        let project = Project::test(fs, None, cx).await;
4595        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
4596
4597        let item1 = cx.build_view(|cx| {
4598            TestItem::new(cx)
4599                .with_dirty(true)
4600                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4601        });
4602        let item2 = cx.build_view(|cx| {
4603            TestItem::new(cx)
4604                .with_dirty(true)
4605                .with_conflict(true)
4606                .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)])
4607        });
4608        let item3 = cx.build_view(|cx| {
4609            TestItem::new(cx)
4610                .with_dirty(true)
4611                .with_conflict(true)
4612                .with_project_items(&[TestProjectItem::new(3, "3.txt", cx)])
4613        });
4614        let item4 = cx.build_view(|cx| {
4615            TestItem::new(cx)
4616                .with_dirty(true)
4617                .with_project_items(&[TestProjectItem::new_untitled(cx)])
4618        });
4619        let pane = workspace.update(cx, |workspace, cx| {
4620            workspace.add_item(Box::new(item1.clone()), cx);
4621            workspace.add_item(Box::new(item2.clone()), cx);
4622            workspace.add_item(Box::new(item3.clone()), cx);
4623            workspace.add_item(Box::new(item4.clone()), cx);
4624            workspace.active_pane().clone()
4625        });
4626
4627        let close_items = pane.update(cx, |pane, cx| {
4628            pane.activate_item(1, true, true, cx);
4629            assert_eq!(pane.active_item().unwrap().item_id(), item2.item_id());
4630            let item1_id = item1.item_id();
4631            let item3_id = item3.item_id();
4632            let item4_id = item4.item_id();
4633            pane.close_items(cx, SaveIntent::Close, move |id| {
4634                [item1_id, item3_id, item4_id].contains(&id)
4635            })
4636        });
4637        cx.executor().run_until_parked();
4638
4639        assert!(cx.has_pending_prompt());
4640        // Ignore "Save all" prompt
4641        cx.simulate_prompt_answer(2);
4642        cx.executor().run_until_parked();
4643        // There's a prompt to save item 1.
4644        pane.update(cx, |pane, _| {
4645            assert_eq!(pane.items_len(), 4);
4646            assert_eq!(pane.active_item().unwrap().item_id(), item1.item_id());
4647        });
4648        // Confirm saving item 1.
4649        cx.simulate_prompt_answer(0);
4650        cx.executor().run_until_parked();
4651
4652        // Item 1 is saved. There's a prompt to save item 3.
4653        pane.update(cx, |pane, cx| {
4654            assert_eq!(item1.read(cx).save_count, 1);
4655            assert_eq!(item1.read(cx).save_as_count, 0);
4656            assert_eq!(item1.read(cx).reload_count, 0);
4657            assert_eq!(pane.items_len(), 3);
4658            assert_eq!(pane.active_item().unwrap().item_id(), item3.item_id());
4659        });
4660        assert!(cx.has_pending_prompt());
4661
4662        // Cancel saving item 3.
4663        cx.simulate_prompt_answer(1);
4664        cx.executor().run_until_parked();
4665
4666        // Item 3 is reloaded. There's a prompt to save item 4.
4667        pane.update(cx, |pane, cx| {
4668            assert_eq!(item3.read(cx).save_count, 0);
4669            assert_eq!(item3.read(cx).save_as_count, 0);
4670            assert_eq!(item3.read(cx).reload_count, 1);
4671            assert_eq!(pane.items_len(), 2);
4672            assert_eq!(pane.active_item().unwrap().item_id(), item4.item_id());
4673        });
4674        assert!(cx.has_pending_prompt());
4675
4676        // Confirm saving item 4.
4677        cx.simulate_prompt_answer(0);
4678        cx.executor().run_until_parked();
4679
4680        // There's a prompt for a path for item 4.
4681        cx.simulate_new_path_selection(|_| Some(Default::default()));
4682        close_items.await.unwrap();
4683
4684        // The requested items are closed.
4685        pane.update(cx, |pane, cx| {
4686            assert_eq!(item4.read(cx).save_count, 0);
4687            assert_eq!(item4.read(cx).save_as_count, 1);
4688            assert_eq!(item4.read(cx).reload_count, 0);
4689            assert_eq!(pane.items_len(), 1);
4690            assert_eq!(pane.active_item().unwrap().item_id(), item2.item_id());
4691        });
4692    }
4693
4694    #[gpui::test]
4695    async fn test_prompting_to_save_only_on_last_item_for_entry(cx: &mut TestAppContext) {
4696        init_test(cx);
4697
4698        let fs = FakeFs::new(cx.executor());
4699        let project = Project::test(fs, [], cx).await;
4700        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
4701
4702        // Create several workspace items with single project entries, and two
4703        // workspace items with multiple project entries.
4704        let single_entry_items = (0..=4)
4705            .map(|project_entry_id| {
4706                cx.build_view(|cx| {
4707                    TestItem::new(cx)
4708                        .with_dirty(true)
4709                        .with_project_items(&[TestProjectItem::new(
4710                            project_entry_id,
4711                            &format!("{project_entry_id}.txt"),
4712                            cx,
4713                        )])
4714                })
4715            })
4716            .collect::<Vec<_>>();
4717        let item_2_3 = cx.build_view(|cx| {
4718            TestItem::new(cx)
4719                .with_dirty(true)
4720                .with_singleton(false)
4721                .with_project_items(&[
4722                    single_entry_items[2].read(cx).project_items[0].clone(),
4723                    single_entry_items[3].read(cx).project_items[0].clone(),
4724                ])
4725        });
4726        let item_3_4 = cx.build_view(|cx| {
4727            TestItem::new(cx)
4728                .with_dirty(true)
4729                .with_singleton(false)
4730                .with_project_items(&[
4731                    single_entry_items[3].read(cx).project_items[0].clone(),
4732                    single_entry_items[4].read(cx).project_items[0].clone(),
4733                ])
4734        });
4735
4736        // Create two panes that contain the following project entries:
4737        //   left pane:
4738        //     multi-entry items:   (2, 3)
4739        //     single-entry items:  0, 1, 2, 3, 4
4740        //   right pane:
4741        //     single-entry items:  1
4742        //     multi-entry items:   (3, 4)
4743        let left_pane = workspace.update(cx, |workspace, cx| {
4744            let left_pane = workspace.active_pane().clone();
4745            workspace.add_item(Box::new(item_2_3.clone()), cx);
4746            for item in single_entry_items {
4747                workspace.add_item(Box::new(item), cx);
4748            }
4749            left_pane.update(cx, |pane, cx| {
4750                pane.activate_item(2, true, true, cx);
4751            });
4752
4753            let right_pane = workspace
4754                .split_and_clone(left_pane.clone(), SplitDirection::Right, cx)
4755                .unwrap();
4756
4757            right_pane.update(cx, |pane, cx| {
4758                pane.add_item(Box::new(item_3_4.clone()), true, true, None, cx);
4759            });
4760
4761            left_pane
4762        });
4763
4764        cx.focus_view(&left_pane);
4765
4766        // When closing all of the items in the left pane, we should be prompted twice:
4767        // once for project entry 0, and once for project entry 2. Project entries 1,
4768        // 3, and 4 are all still open in the other paten. After those two
4769        // prompts, the task should complete.
4770
4771        let close = left_pane.update(cx, |pane, cx| {
4772            pane.close_all_items(&CloseAllItems::default(), cx).unwrap()
4773        });
4774        cx.executor().run_until_parked();
4775
4776        // Discard "Save all" prompt
4777        cx.simulate_prompt_answer(2);
4778
4779        cx.executor().run_until_parked();
4780        left_pane.update(cx, |pane, cx| {
4781            assert_eq!(
4782                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
4783                &[ProjectEntryId::from_proto(0)]
4784            );
4785        });
4786        cx.simulate_prompt_answer(0);
4787
4788        cx.executor().run_until_parked();
4789        left_pane.update(cx, |pane, cx| {
4790            assert_eq!(
4791                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
4792                &[ProjectEntryId::from_proto(2)]
4793            );
4794        });
4795        cx.simulate_prompt_answer(0);
4796
4797        cx.executor().run_until_parked();
4798        close.await.unwrap();
4799        left_pane.update(cx, |pane, _| {
4800            assert_eq!(pane.items_len(), 0);
4801        });
4802    }
4803
4804    #[gpui::test]
4805    async fn test_autosave(cx: &mut gpui::TestAppContext) {
4806        init_test(cx);
4807
4808        let fs = FakeFs::new(cx.executor());
4809        let project = Project::test(fs, [], cx).await;
4810        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
4811        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
4812
4813        let item = cx.build_view(|cx| {
4814            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4815        });
4816        let item_id = item.entity_id();
4817        workspace.update(cx, |workspace, cx| {
4818            workspace.add_item(Box::new(item.clone()), cx);
4819        });
4820
4821        // Autosave on window change.
4822        item.update(cx, |item, cx| {
4823            cx.update_global(|settings: &mut SettingsStore, cx| {
4824                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4825                    settings.autosave = Some(AutosaveSetting::OnWindowChange);
4826                })
4827            });
4828            item.is_dirty = true;
4829        });
4830
4831        // Deactivating the window saves the file.
4832        cx.simulate_deactivation();
4833        cx.executor().run_until_parked();
4834        item.update(cx, |item, _| assert_eq!(item.save_count, 1));
4835
4836        // Autosave on focus change.
4837        item.update(cx, |item, cx| {
4838            cx.focus_self();
4839            cx.update_global(|settings: &mut SettingsStore, cx| {
4840                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4841                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
4842                })
4843            });
4844            item.is_dirty = true;
4845        });
4846
4847        // Blurring the item saves the file.
4848        item.update(cx, |_, cx| cx.blur());
4849        cx.executor().run_until_parked();
4850        item.update(cx, |item, _| assert_eq!(item.save_count, 2));
4851
4852        // Deactivating the window still saves the file.
4853        cx.simulate_activation();
4854        item.update(cx, |item, cx| {
4855            cx.focus_self();
4856            item.is_dirty = true;
4857        });
4858        cx.simulate_deactivation();
4859
4860        cx.executor().run_until_parked();
4861        item.update(cx, |item, _| assert_eq!(item.save_count, 3));
4862
4863        // Autosave after delay.
4864        item.update(cx, |item, cx| {
4865            cx.update_global(|settings: &mut SettingsStore, cx| {
4866                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4867                    settings.autosave = Some(AutosaveSetting::AfterDelay { milliseconds: 500 });
4868                })
4869            });
4870            item.is_dirty = true;
4871            cx.emit(ItemEvent::Edit);
4872        });
4873
4874        // Delay hasn't fully expired, so the file is still dirty and unsaved.
4875        cx.executor().advance_clock(Duration::from_millis(250));
4876        item.update(cx, |item, _| assert_eq!(item.save_count, 3));
4877
4878        // After delay expires, the file is saved.
4879        cx.executor().advance_clock(Duration::from_millis(250));
4880        item.update(cx, |item, _| assert_eq!(item.save_count, 4));
4881
4882        // Autosave on focus change, ensuring closing the tab counts as such.
4883        item.update(cx, |item, cx| {
4884            cx.update_global(|settings: &mut SettingsStore, cx| {
4885                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4886                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
4887                })
4888            });
4889            item.is_dirty = true;
4890        });
4891
4892        pane.update(cx, |pane, cx| {
4893            pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
4894        })
4895        .await
4896        .unwrap();
4897        assert!(!cx.has_pending_prompt());
4898        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
4899
4900        // Add the item again, ensuring autosave is prevented if the underlying file has been deleted.
4901        workspace.update(cx, |workspace, cx| {
4902            workspace.add_item(Box::new(item.clone()), cx);
4903        });
4904        item.update(cx, |item, cx| {
4905            item.project_items[0].update(cx, |item, _| {
4906                item.entry_id = None;
4907            });
4908            item.is_dirty = true;
4909            cx.blur();
4910        });
4911        cx.executor().run_until_parked();
4912        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
4913
4914        // Ensure autosave is prevented for deleted files also when closing the buffer.
4915        let _close_items = pane.update(cx, |pane, cx| {
4916            pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
4917        });
4918        cx.executor().run_until_parked();
4919        assert!(cx.has_pending_prompt());
4920        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
4921    }
4922
4923    #[gpui::test]
4924    async fn test_pane_navigation(cx: &mut gpui::TestAppContext) {
4925        init_test(cx);
4926
4927        let fs = FakeFs::new(cx.executor());
4928
4929        let project = Project::test(fs, [], cx).await;
4930        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
4931
4932        let item = cx.build_view(|cx| {
4933            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4934        });
4935        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
4936        let toolbar = pane.update(cx, |pane, _| pane.toolbar().clone());
4937        let toolbar_notify_count = Rc::new(RefCell::new(0));
4938
4939        workspace.update(cx, |workspace, cx| {
4940            workspace.add_item(Box::new(item.clone()), cx);
4941            let toolbar_notification_count = toolbar_notify_count.clone();
4942            cx.observe(&toolbar, move |_, _, _| {
4943                *toolbar_notification_count.borrow_mut() += 1
4944            })
4945            .detach();
4946        });
4947
4948        pane.update(cx, |pane, _| {
4949            assert!(!pane.can_navigate_backward());
4950            assert!(!pane.can_navigate_forward());
4951        });
4952
4953        item.update(cx, |item, cx| {
4954            item.set_state("one".to_string(), cx);
4955        });
4956
4957        // Toolbar must be notified to re-render the navigation buttons
4958        assert_eq!(*toolbar_notify_count.borrow(), 1);
4959
4960        pane.update(cx, |pane, _| {
4961            assert!(pane.can_navigate_backward());
4962            assert!(!pane.can_navigate_forward());
4963        });
4964
4965        workspace
4966            .update(cx, |workspace, cx| workspace.go_back(pane.downgrade(), cx))
4967            .await
4968            .unwrap();
4969
4970        assert_eq!(*toolbar_notify_count.borrow(), 2);
4971        pane.update(cx, |pane, _| {
4972            assert!(!pane.can_navigate_backward());
4973            assert!(pane.can_navigate_forward());
4974        });
4975    }
4976
4977    //     #[gpui::test]
4978    //     async fn test_toggle_docks_and_panels(cx: &mut gpui::TestAppContext) {
4979    //         init_test(cx);
4980    //         let fs = FakeFs::new(cx.executor());
4981
4982    //         let project = Project::test(fs, [], cx).await;
4983    //         let window = cx.add_window(|cx| Workspace::test_new(project, cx));
4984    //         let workspace = window.root(cx);
4985
4986    //         let panel = workspace.update(cx, |workspace, cx| {
4987    //             let panel = cx.build_view(|_| TestPanel::new(DockPosition::Right));
4988    //             workspace.add_panel(panel.clone(), cx);
4989
4990    //             workspace
4991    //                 .right_dock()
4992    //                 .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
4993
4994    //             panel
4995    //         });
4996
4997    //         let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
4998    //         pane.update(cx, |pane, cx| {
4999    //             let item = cx.build_view(|_| TestItem::new(cx));
5000    //             pane.add_item(Box::new(item), true, true, None, cx);
5001    //         });
5002
5003    //         // Transfer focus from center to panel
5004    //         workspace.update(cx, |workspace, cx| {
5005    //             workspace.toggle_panel_focus::<TestPanel>(cx);
5006    //         });
5007
5008    //         workspace.update(cx, |workspace, cx| {
5009    //             assert!(workspace.right_dock().read(cx).is_open());
5010    //             assert!(!panel.is_zoomed(cx));
5011    //             assert!(panel.has_focus(cx));
5012    //         });
5013
5014    //         // Transfer focus from panel to center
5015    //         workspace.update(cx, |workspace, cx| {
5016    //             workspace.toggle_panel_focus::<TestPanel>(cx);
5017    //         });
5018
5019    //         workspace.update(cx, |workspace, cx| {
5020    //             assert!(workspace.right_dock().read(cx).is_open());
5021    //             assert!(!panel.is_zoomed(cx));
5022    //             assert!(!panel.has_focus(cx));
5023    //         });
5024
5025    //         // Close the dock
5026    //         workspace.update(cx, |workspace, cx| {
5027    //             workspace.toggle_dock(DockPosition::Right, cx);
5028    //         });
5029
5030    //         workspace.update(cx, |workspace, cx| {
5031    //             assert!(!workspace.right_dock().read(cx).is_open());
5032    //             assert!(!panel.is_zoomed(cx));
5033    //             assert!(!panel.has_focus(cx));
5034    //         });
5035
5036    //         // Open the dock
5037    //         workspace.update(cx, |workspace, cx| {
5038    //             workspace.toggle_dock(DockPosition::Right, cx);
5039    //         });
5040
5041    //         workspace.update(cx, |workspace, cx| {
5042    //             assert!(workspace.right_dock().read(cx).is_open());
5043    //             assert!(!panel.is_zoomed(cx));
5044    //             assert!(panel.has_focus(cx));
5045    //         });
5046
5047    //         // Focus and zoom panel
5048    //         panel.update(cx, |panel, cx| {
5049    //             cx.focus_self();
5050    //             panel.set_zoomed(true, cx)
5051    //         });
5052
5053    //         workspace.update(cx, |workspace, cx| {
5054    //             assert!(workspace.right_dock().read(cx).is_open());
5055    //             assert!(panel.is_zoomed(cx));
5056    //             assert!(panel.has_focus(cx));
5057    //         });
5058
5059    //         // Transfer focus to the center closes the dock
5060    //         workspace.update(cx, |workspace, cx| {
5061    //             workspace.toggle_panel_focus::<TestPanel>(cx);
5062    //         });
5063
5064    //         workspace.update(cx, |workspace, cx| {
5065    //             assert!(!workspace.right_dock().read(cx).is_open());
5066    //             assert!(panel.is_zoomed(cx));
5067    //             assert!(!panel.has_focus(cx));
5068    //         });
5069
5070    //         // Transferring focus back to the panel keeps it zoomed
5071    //         workspace.update(cx, |workspace, cx| {
5072    //             workspace.toggle_panel_focus::<TestPanel>(cx);
5073    //         });
5074
5075    //         workspace.update(cx, |workspace, cx| {
5076    //             assert!(workspace.right_dock().read(cx).is_open());
5077    //             assert!(panel.is_zoomed(cx));
5078    //             assert!(panel.has_focus(cx));
5079    //         });
5080
5081    //         // Close the dock while it is zoomed
5082    //         workspace.update(cx, |workspace, cx| {
5083    //             workspace.toggle_dock(DockPosition::Right, cx)
5084    //         });
5085
5086    //         workspace.update(cx, |workspace, cx| {
5087    //             assert!(!workspace.right_dock().read(cx).is_open());
5088    //             assert!(panel.is_zoomed(cx));
5089    //             assert!(workspace.zoomed.is_none());
5090    //             assert!(!panel.has_focus(cx));
5091    //         });
5092
5093    //         // Opening the dock, when it's zoomed, retains focus
5094    //         workspace.update(cx, |workspace, cx| {
5095    //             workspace.toggle_dock(DockPosition::Right, cx)
5096    //         });
5097
5098    //         workspace.update(cx, |workspace, cx| {
5099    //             assert!(workspace.right_dock().read(cx).is_open());
5100    //             assert!(panel.is_zoomed(cx));
5101    //             assert!(workspace.zoomed.is_some());
5102    //             assert!(panel.has_focus(cx));
5103    //         });
5104
5105    //         // Unzoom and close the panel, zoom the active pane.
5106    //         panel.update(cx, |panel, cx| panel.set_zoomed(false, cx));
5107    //         workspace.update(cx, |workspace, cx| {
5108    //             workspace.toggle_dock(DockPosition::Right, cx)
5109    //         });
5110    //         pane.update(cx, |pane, cx| pane.toggle_zoom(&Default::default(), cx));
5111
5112    //         // Opening a dock unzooms the pane.
5113    //         workspace.update(cx, |workspace, cx| {
5114    //             workspace.toggle_dock(DockPosition::Right, cx)
5115    //         });
5116    //         workspace.update(cx, |workspace, cx| {
5117    //             let pane = pane.read(cx);
5118    //             assert!(!pane.is_zoomed());
5119    //             assert!(!pane.has_focus());
5120    //             assert!(workspace.right_dock().read(cx).is_open());
5121    //             assert!(workspace.zoomed.is_none());
5122    //         });
5123    //     }
5124
5125    //     #[gpui::test]
5126    //     async fn test_panels(cx: &mut gpui::TestAppContext) {
5127    //         init_test(cx);
5128    //         let fs = FakeFs::new(cx.executor());
5129
5130    //         let project = Project::test(fs, [], cx).await;
5131    //         let window = cx.add_window(|cx| Workspace::test_new(project, cx));
5132    //         let workspace = window.root(cx);
5133
5134    //         let (panel_1, panel_2) = workspace.update(cx, |workspace, cx| {
5135    //             // Add panel_1 on the left, panel_2 on the right.
5136    //             let panel_1 = cx.build_view(|_| TestPanel::new(DockPosition::Left));
5137    //             workspace.add_panel(panel_1.clone(), cx);
5138    //             workspace
5139    //                 .left_dock()
5140    //                 .update(cx, |left_dock, cx| left_dock.set_open(true, cx));
5141    //             let panel_2 = cx.build_view(|_| TestPanel::new(DockPosition::Right));
5142    //             workspace.add_panel(panel_2.clone(), cx);
5143    //             workspace
5144    //                 .right_dock()
5145    //                 .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
5146
5147    //             let left_dock = workspace.left_dock();
5148    //             assert_eq!(
5149    //                 left_dock.read(cx).visible_panel().unwrap().id(),
5150    //                 panel_1.id()
5151    //             );
5152    //             assert_eq!(
5153    //                 left_dock.read(cx).active_panel_size(cx).unwrap(),
5154    //                 panel_1.size(cx)
5155    //             );
5156
5157    //             left_dock.update(cx, |left_dock, cx| {
5158    //                 left_dock.resize_active_panel(Some(1337.), cx)
5159    //             });
5160    //             assert_eq!(
5161    //                 workspace
5162    //                     .right_dock()
5163    //                     .read(cx)
5164    //                     .visible_panel()
5165    //                     .unwrap()
5166    //                     .id(),
5167    //                 panel_2.id()
5168    //             );
5169
5170    //             (panel_1, panel_2)
5171    //         });
5172
5173    //         // Move panel_1 to the right
5174    //         panel_1.update(cx, |panel_1, cx| {
5175    //             panel_1.set_position(DockPosition::Right, cx)
5176    //         });
5177
5178    //         workspace.update(cx, |workspace, cx| {
5179    //             // Since panel_1 was visible on the left, it should now be visible now that it's been moved to the right.
5180    //             // Since it was the only panel on the left, the left dock should now be closed.
5181    //             assert!(!workspace.left_dock().read(cx).is_open());
5182    //             assert!(workspace.left_dock().read(cx).visible_panel().is_none());
5183    //             let right_dock = workspace.right_dock();
5184    //             assert_eq!(
5185    //                 right_dock.read(cx).visible_panel().unwrap().id(),
5186    //                 panel_1.id()
5187    //             );
5188    //             assert_eq!(right_dock.read(cx).active_panel_size(cx).unwrap(), 1337.);
5189
5190    //             // Now we move panel_2Β to the left
5191    //             panel_2.set_position(DockPosition::Left, cx);
5192    //         });
5193
5194    //         workspace.update(cx, |workspace, cx| {
5195    //             // Since panel_2 was not visible on the right, we don't open the left dock.
5196    //             assert!(!workspace.left_dock().read(cx).is_open());
5197    //             // And the right dock is unaffected in it's displaying of panel_1
5198    //             assert!(workspace.right_dock().read(cx).is_open());
5199    //             assert_eq!(
5200    //                 workspace
5201    //                     .right_dock()
5202    //                     .read(cx)
5203    //                     .visible_panel()
5204    //                     .unwrap()
5205    //                     .id(),
5206    //                 panel_1.id()
5207    //             );
5208    //         });
5209
5210    //         // Move panel_1 back to the left
5211    //         panel_1.update(cx, |panel_1, cx| {
5212    //             panel_1.set_position(DockPosition::Left, cx)
5213    //         });
5214
5215    //         workspace.update(cx, |workspace, cx| {
5216    //             // Since panel_1 was visible on the right, we open the left dock and make panel_1 active.
5217    //             let left_dock = workspace.left_dock();
5218    //             assert!(left_dock.read(cx).is_open());
5219    //             assert_eq!(
5220    //                 left_dock.read(cx).visible_panel().unwrap().id(),
5221    //                 panel_1.id()
5222    //             );
5223    //             assert_eq!(left_dock.read(cx).active_panel_size(cx).unwrap(), 1337.);
5224    //             // And right the dock should be closed as it no longer has any panels.
5225    //             assert!(!workspace.right_dock().read(cx).is_open());
5226
5227    //             // Now we move panel_1 to the bottom
5228    //             panel_1.set_position(DockPosition::Bottom, cx);
5229    //         });
5230
5231    //         workspace.update(cx, |workspace, cx| {
5232    //             // Since panel_1 was visible on the left, we close the left dock.
5233    //             assert!(!workspace.left_dock().read(cx).is_open());
5234    //             // The bottom dock is sized based on the panel's default size,
5235    //             // since the panel orientation changed from vertical to horizontal.
5236    //             let bottom_dock = workspace.bottom_dock();
5237    //             assert_eq!(
5238    //                 bottom_dock.read(cx).active_panel_size(cx).unwrap(),
5239    //                 panel_1.size(cx),
5240    //             );
5241    //             // Close bottom dock and move panel_1 back to the left.
5242    //             bottom_dock.update(cx, |bottom_dock, cx| bottom_dock.set_open(false, cx));
5243    //             panel_1.set_position(DockPosition::Left, cx);
5244    //         });
5245
5246    //         // Emit activated event on panel 1
5247    //         panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::Activated));
5248
5249    //         // Now the left dock is open and panel_1 is active and focused.
5250    //         workspace.update(cx, |workspace, cx| {
5251    //             let left_dock = workspace.left_dock();
5252    //             assert!(left_dock.read(cx).is_open());
5253    //             assert_eq!(
5254    //                 left_dock.read(cx).visible_panel().unwrap().id(),
5255    //                 panel_1.id()
5256    //             );
5257    //             assert!(panel_1.is_focused(cx));
5258    //         });
5259
5260    //         // Emit closed event on panel 2, which is not active
5261    //         panel_2.update(cx, |_, cx| cx.emit(TestPanelEvent::Closed));
5262
5263    //         // Wo don't close the left dock, because panel_2 wasn't the active panel
5264    //         workspace.update(cx, |workspace, cx| {
5265    //             let left_dock = workspace.left_dock();
5266    //             assert!(left_dock.read(cx).is_open());
5267    //             assert_eq!(
5268    //                 left_dock.read(cx).visible_panel().unwrap().id(),
5269    //                 panel_1.id()
5270    //             );
5271    //         });
5272
5273    //         // Emitting a ZoomIn event shows the panel as zoomed.
5274    //         panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::ZoomIn));
5275    //         workspace.update(cx, |workspace, _| {
5276    //             assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
5277    //             assert_eq!(workspace.zoomed_position, Some(DockPosition::Left));
5278    //         });
5279
5280    //         // Move panel to another dock while it is zoomed
5281    //         panel_1.update(cx, |panel, cx| panel.set_position(DockPosition::Right, cx));
5282    //         workspace.update(cx, |workspace, _| {
5283    //             assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
5284    //             assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5285    //         });
5286
5287    //         // If focus is transferred to another view that's not a panel or another pane, we still show
5288    //         // the panel as zoomed.
5289    //         let focus_receiver = cx.build_view(|_| EmptyView);
5290    //         focus_receiver.update(cx, |_, cx| cx.focus_self());
5291    //         workspace.update(cx, |workspace, _| {
5292    //             assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
5293    //             assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5294    //         });
5295
5296    //         // If focus is transferred elsewhere in the workspace, the panel is no longer zoomed.
5297    //         workspace.update(cx, |_, cx| cx.focus_self());
5298    //         workspace.update(cx, |workspace, _| {
5299    //             assert_eq!(workspace.zoomed, None);
5300    //             assert_eq!(workspace.zoomed_position, None);
5301    //         });
5302
5303    //         // If focus is transferred again to another view that's not a panel or a pane, we won't
5304    //         // show the panel as zoomed because it wasn't zoomed before.
5305    //         focus_receiver.update(cx, |_, cx| cx.focus_self());
5306    //         workspace.update(cx, |workspace, _| {
5307    //             assert_eq!(workspace.zoomed, None);
5308    //             assert_eq!(workspace.zoomed_position, None);
5309    //         });
5310
5311    //         // When focus is transferred back to the panel, it is zoomed again.
5312    //         panel_1.update(cx, |_, cx| cx.focus_self());
5313    //         workspace.update(cx, |workspace, _| {
5314    //             assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
5315    //             assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5316    //         });
5317
5318    //         // Emitting a ZoomOut event unzooms the panel.
5319    //         panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::ZoomOut));
5320    //         workspace.update(cx, |workspace, _| {
5321    //             assert_eq!(workspace.zoomed, None);
5322    //             assert_eq!(workspace.zoomed_position, None);
5323    //         });
5324
5325    //         // Emit closed event on panel 1, which is active
5326    //         panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::Closed));
5327
5328    //         // Now the left dock is closed, because panel_1 was the active panel
5329    //         workspace.update(cx, |workspace, cx| {
5330    //             let right_dock = workspace.right_dock();
5331    //             assert!(!right_dock.read(cx).is_open());
5332    //         });
5333    //     }
5334
5335    pub fn init_test(cx: &mut TestAppContext) {
5336        cx.update(|cx| {
5337            let settings_store = SettingsStore::test(cx);
5338            cx.set_global(settings_store);
5339            theme::init(theme::LoadThemes::JustBase, cx);
5340            language::init(cx);
5341            crate::init_settings(cx);
5342            Project::init_settings(cx);
5343        });
5344    }
5345}