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