workspace2.rs

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