workspace.rs

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