workspace2.rs

   1pub mod dock;
   2pub mod item;
   3mod modal_layer;
   4pub mod notifications;
   5pub mod pane;
   6pub mod pane_group;
   7mod persistence;
   8pub mod searchable;
   9pub mod shared_screen;
  10mod status_bar;
  11mod toolbar;
  12mod workspace_settings;
  13
  14use anyhow::{anyhow, Context as _, Result};
  15use call::ActiveCall;
  16use client::{
  17    proto::{self, PeerId},
  18    Client, Status, TypedEnvelope, UserStore,
  19};
  20use collections::{hash_map, HashMap, HashSet};
  21use dock::{Dock, DockPosition, Panel, PanelButtons, PanelHandle};
  22use futures::{
  23    channel::{mpsc, oneshot},
  24    future::try_join_all,
  25    Future, FutureExt, StreamExt,
  26};
  27use gpui::{
  28    actions, canvas, div, impl_actions, point, size, Action, AnyModel, AnyView, AnyWeakView,
  29    AnyWindowHandle, AppContext, AsyncAppContext, AsyncWindowContext, Bounds, Context, Div,
  30    DragMoveEvent, Entity, EntityId, EventEmitter, FocusHandle, FocusableView, GlobalPixels,
  31    InteractiveElement, KeyContext, ManagedView, Model, ModelContext, ParentElement,
  32    PathPromptOptions, Pixels, Point, PromptLevel, Render, Size, Styled, Subscription, Task, View,
  33    ViewContext, VisualContext, WeakView, WindowBounds, WindowContext, WindowHandle, WindowOptions,
  34};
  35use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ItemSettings, ProjectItem};
  36use itertools::Itertools;
  37use language::{LanguageRegistry, Rope};
  38use lazy_static::lazy_static;
  39pub use modal_layer::*;
  40use node_runtime::NodeRuntime;
  41use notifications::{simple_message_notification::MessageNotification, NotificationHandle};
  42pub use pane::*;
  43pub use pane_group::*;
  44use persistence::DB;
  45pub use persistence::{
  46    model::{ItemId, SerializedWorkspace, WorkspaceLocation},
  47    WorkspaceDb, DB as WORKSPACE_DB,
  48};
  49use postage::stream::Stream;
  50use project::{Project, ProjectEntryId, ProjectPath, Worktree, WorktreeId};
  51use serde::Deserialize;
  52use settings::Settings;
  53use shared_screen::SharedScreen;
  54use status_bar::StatusBar;
  55pub use status_bar::StatusItemView;
  56use std::{
  57    any::TypeId,
  58    borrow::Cow,
  59    cmp, env,
  60    path::{Path, PathBuf},
  61    sync::{atomic::AtomicUsize, Arc},
  62    time::Duration,
  63};
  64use theme::{ActiveTheme, ThemeSettings};
  65pub use toolbar::{Toolbar, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView};
  66pub use ui;
  67use util::ResultExt;
  68use uuid::Uuid;
  69pub use workspace_settings::{AutosaveSetting, WorkspaceSettings};
  70
  71use crate::persistence::model::{
  72    DockData, DockStructure, SerializedItem, SerializedPane, SerializedPaneGroup,
  73};
  74
  75lazy_static! {
  76    static ref ZED_WINDOW_SIZE: Option<Size<GlobalPixels>> = env::var("ZED_WINDOW_SIZE")
  77        .ok()
  78        .as_deref()
  79        .and_then(parse_pixel_size_env_var);
  80    static ref ZED_WINDOW_POSITION: Option<Point<GlobalPixels>> = env::var("ZED_WINDOW_POSITION")
  81        .ok()
  82        .as_deref()
  83        .and_then(parse_pixel_position_env_var);
  84}
  85
  86#[derive(Clone, PartialEq)]
  87pub struct RemoveWorktreeFromProject(pub WorktreeId);
  88
  89actions!(
  90    workspace,
  91    [
  92        Open,
  93        NewFile,
  94        NewWindow,
  95        CloseWindow,
  96        CloseInactiveTabsAndPanes,
  97        AddFolderToProject,
  98        Unfollow,
  99        SaveAs,
 100        ReloadActiveItem,
 101        ActivatePreviousPane,
 102        ActivateNextPane,
 103        FollowNextCollaborator,
 104        NewTerminal,
 105        NewCenterTerminal,
 106        ToggleTerminalFocus,
 107        NewSearch,
 108        Feedback,
 109        Restart,
 110        Welcome,
 111        ToggleZoom,
 112        ToggleLeftDock,
 113        ToggleRightDock,
 114        ToggleBottomDock,
 115        CloseAllDocks,
 116    ]
 117);
 118
 119#[derive(Clone, PartialEq)]
 120pub struct OpenPaths {
 121    pub paths: Vec<PathBuf>,
 122}
 123
 124#[derive(Clone, Deserialize, PartialEq)]
 125pub struct ActivatePane(pub usize);
 126
 127#[derive(Clone, Deserialize, PartialEq)]
 128pub struct ActivatePaneInDirection(pub SplitDirection);
 129
 130#[derive(Clone, Deserialize, PartialEq)]
 131pub struct SwapPaneInDirection(pub SplitDirection);
 132
 133#[derive(Clone, Deserialize, PartialEq)]
 134pub struct NewFileInDirection(pub SplitDirection);
 135
 136#[derive(Clone, PartialEq, Debug, Deserialize)]
 137#[serde(rename_all = "camelCase")]
 138pub struct SaveAll {
 139    pub save_intent: Option<SaveIntent>,
 140}
 141
 142#[derive(Clone, PartialEq, Debug, Deserialize)]
 143#[serde(rename_all = "camelCase")]
 144pub struct Save {
 145    pub save_intent: Option<SaveIntent>,
 146}
 147
 148#[derive(Clone, PartialEq, Debug, Deserialize, Default)]
 149#[serde(rename_all = "camelCase")]
 150pub struct CloseAllItemsAndPanes {
 151    pub save_intent: Option<SaveIntent>,
 152}
 153
 154impl_actions!(
 155    workspace,
 156    [
 157        ActivatePane,
 158        ActivatePaneInDirection,
 159        CloseAllItemsAndPanes,
 160        NewFileInDirection,
 161        OpenTerminal,
 162        Save,
 163        SaveAll,
 164        SwapPaneInDirection,
 165    ]
 166);
 167
 168#[derive(Deserialize)]
 169pub struct Toast {
 170    id: usize,
 171    msg: Cow<'static, str>,
 172    #[serde(skip)]
 173    on_click: Option<(Cow<'static, str>, Arc<dyn Fn(&mut WindowContext)>)>,
 174}
 175
 176impl Toast {
 177    pub fn new<I: Into<Cow<'static, str>>>(id: usize, msg: I) -> Self {
 178        Toast {
 179            id,
 180            msg: msg.into(),
 181            on_click: None,
 182        }
 183    }
 184
 185    pub fn on_click<F, M>(mut self, message: M, on_click: F) -> Self
 186    where
 187        M: Into<Cow<'static, str>>,
 188        F: Fn(&mut WindowContext) + 'static,
 189    {
 190        self.on_click = Some((message.into(), Arc::new(on_click)));
 191        self
 192    }
 193}
 194
 195impl PartialEq for Toast {
 196    fn eq(&self, other: &Self) -> bool {
 197        self.id == other.id
 198            && self.msg == other.msg
 199            && self.on_click.is_some() == other.on_click.is_some()
 200    }
 201}
 202
 203impl Clone for Toast {
 204    fn clone(&self) -> Self {
 205        Toast {
 206            id: self.id,
 207            msg: self.msg.to_owned(),
 208            on_click: self.on_click.clone(),
 209        }
 210    }
 211}
 212
 213#[derive(Debug, Default, Clone, Deserialize, PartialEq)]
 214pub struct OpenTerminal {
 215    pub working_directory: PathBuf,
 216}
 217
 218pub type WorkspaceId = i64;
 219
 220pub fn init_settings(cx: &mut AppContext) {
 221    WorkspaceSettings::register(cx);
 222    ItemSettings::register(cx);
 223}
 224
 225pub fn init(app_state: Arc<AppState>, cx: &mut AppContext) {
 226    init_settings(cx);
 227    notifications::init(cx);
 228
 229    cx.on_action(Workspace::close_global);
 230    cx.on_action(restart);
 231
 232    cx.on_action({
 233        let app_state = Arc::downgrade(&app_state);
 234        move |_: &Open, cx: &mut AppContext| {
 235            let paths = cx.prompt_for_paths(PathPromptOptions {
 236                files: true,
 237                directories: true,
 238                multiple: true,
 239            });
 240
 241            if let Some(app_state) = app_state.upgrade() {
 242                cx.spawn(move |cx| async move {
 243                    if let Some(paths) = paths.await.log_err().flatten() {
 244                        cx.update(|cx| {
 245                            open_paths(&paths, &app_state, None, cx).detach_and_log_err(cx)
 246                        })
 247                        .ok();
 248                    }
 249                })
 250                .detach();
 251            }
 252        }
 253    });
 254}
 255
 256type ProjectItemBuilders =
 257    HashMap<TypeId, fn(Model<Project>, AnyModel, &mut ViewContext<Pane>) -> Box<dyn ItemHandle>>;
 258pub fn register_project_item<I: ProjectItem>(cx: &mut AppContext) {
 259    let builders = cx.default_global::<ProjectItemBuilders>();
 260    builders.insert(TypeId::of::<I::Item>(), |project, model, cx| {
 261        let item = model.downcast::<I::Item>().unwrap();
 262        Box::new(cx.build_view(|cx| I::for_project_item(project, item, cx)))
 263    });
 264}
 265
 266type FollowableItemBuilder = fn(
 267    View<Pane>,
 268    View<Workspace>,
 269    ViewId,
 270    &mut Option<proto::view::Variant>,
 271    &mut WindowContext,
 272) -> Option<Task<Result<Box<dyn FollowableItemHandle>>>>;
 273type FollowableItemBuilders = HashMap<
 274    TypeId,
 275    (
 276        FollowableItemBuilder,
 277        fn(&AnyView) -> Box<dyn FollowableItemHandle>,
 278    ),
 279>;
 280pub fn register_followable_item<I: FollowableItem>(cx: &mut AppContext) {
 281    let builders = cx.default_global::<FollowableItemBuilders>();
 282    builders.insert(
 283        TypeId::of::<I>(),
 284        (
 285            |pane, workspace, id, state, cx| {
 286                I::from_state_proto(pane, workspace, id, state, cx).map(|task| {
 287                    cx.foreground_executor()
 288                        .spawn(async move { Ok(Box::new(task.await?) as Box<_>) })
 289                })
 290            },
 291            |this| Box::new(this.clone().downcast::<I>().unwrap()),
 292        ),
 293    );
 294}
 295
 296type ItemDeserializers = HashMap<
 297    Arc<str>,
 298    fn(
 299        Model<Project>,
 300        WeakView<Workspace>,
 301        WorkspaceId,
 302        ItemId,
 303        &mut ViewContext<Pane>,
 304    ) -> Task<Result<Box<dyn ItemHandle>>>,
 305>;
 306pub fn register_deserializable_item<I: Item>(cx: &mut AppContext) {
 307    if let Some(serialized_item_kind) = I::serialized_item_kind() {
 308        let deserializers = cx.default_global::<ItemDeserializers>();
 309        deserializers.insert(
 310            Arc::from(serialized_item_kind),
 311            |project, workspace, workspace_id, item_id, cx| {
 312                let task = I::deserialize(project, workspace, workspace_id, item_id, cx);
 313                cx.foreground_executor()
 314                    .spawn(async { Ok(Box::new(task.await?) as Box<_>) })
 315            },
 316        );
 317    }
 318}
 319
 320pub struct AppState {
 321    pub languages: Arc<LanguageRegistry>,
 322    pub client: Arc<Client>,
 323    pub user_store: Model<UserStore>,
 324    pub workspace_store: Model<WorkspaceStore>,
 325    pub fs: Arc<dyn fs::Fs>,
 326    pub build_window_options:
 327        fn(Option<WindowBounds>, Option<Uuid>, &mut AppContext) -> WindowOptions,
 328    pub node_runtime: Arc<dyn NodeRuntime>,
 329}
 330
 331pub struct WorkspaceStore {
 332    workspaces: HashSet<WindowHandle<Workspace>>,
 333    followers: Vec<Follower>,
 334    client: Arc<Client>,
 335    _subscriptions: Vec<client::Subscription>,
 336}
 337
 338#[derive(PartialEq, Eq, PartialOrd, Ord, Debug)]
 339struct Follower {
 340    project_id: Option<u64>,
 341    peer_id: PeerId,
 342}
 343
 344impl AppState {
 345    #[cfg(any(test, feature = "test-support"))]
 346    pub fn test(cx: &mut AppContext) -> Arc<Self> {
 347        use node_runtime::FakeNodeRuntime;
 348        use settings::SettingsStore;
 349
 350        if !cx.has_global::<SettingsStore>() {
 351            let settings_store = SettingsStore::test(cx);
 352            cx.set_global(settings_store);
 353        }
 354
 355        let fs = fs::FakeFs::new(cx.background_executor().clone());
 356        let languages = Arc::new(LanguageRegistry::test());
 357        let http_client = util::http::FakeHttpClient::with_404_response();
 358        let client = Client::new(http_client.clone(), cx);
 359        let user_store = cx.build_model(|cx| UserStore::new(client.clone(), cx));
 360        let workspace_store = cx.build_model(|cx| WorkspaceStore::new(client.clone(), cx));
 361
 362        theme::init(theme::LoadThemes::JustBase, cx);
 363        client::init(&client, cx);
 364        crate::init_settings(cx);
 365
 366        Arc::new(Self {
 367            client,
 368            fs,
 369            languages,
 370            user_store,
 371            workspace_store,
 372            node_runtime: FakeNodeRuntime::new(),
 373            build_window_options: |_, _, _| Default::default(),
 374        })
 375    }
 376}
 377
 378struct DelayedDebouncedEditAction {
 379    task: Option<Task<()>>,
 380    cancel_channel: Option<oneshot::Sender<()>>,
 381}
 382
 383impl DelayedDebouncedEditAction {
 384    fn new() -> DelayedDebouncedEditAction {
 385        DelayedDebouncedEditAction {
 386            task: None,
 387            cancel_channel: None,
 388        }
 389    }
 390
 391    fn fire_new<F>(&mut self, delay: Duration, cx: &mut ViewContext<Workspace>, func: F)
 392    where
 393        F: 'static + Send + FnOnce(&mut Workspace, &mut ViewContext<Workspace>) -> Task<Result<()>>,
 394    {
 395        if let Some(channel) = self.cancel_channel.take() {
 396            _ = channel.send(());
 397        }
 398
 399        let (sender, mut receiver) = oneshot::channel::<()>();
 400        self.cancel_channel = Some(sender);
 401
 402        let previous_task = self.task.take();
 403        self.task = Some(cx.spawn(move |workspace, mut cx| async move {
 404            let mut timer = cx.background_executor().timer(delay).fuse();
 405            if let Some(previous_task) = previous_task {
 406                previous_task.await;
 407            }
 408
 409            futures::select_biased! {
 410                _ = receiver => return,
 411                    _ = timer => {}
 412            }
 413
 414            if let Some(result) = workspace
 415                .update(&mut cx, |workspace, cx| (func)(workspace, cx))
 416                .log_err()
 417            {
 418                result.await.log_err();
 419            }
 420        }));
 421    }
 422}
 423
 424pub enum Event {
 425    PaneAdded(View<Pane>),
 426    ContactRequestedJoin(u64),
 427    WorkspaceCreated(WeakView<Workspace>),
 428}
 429
 430pub struct Workspace {
 431    window_self: WindowHandle<Self>,
 432    weak_self: WeakView<Self>,
 433    workspace_actions: Vec<Box<dyn Fn(Div, &mut ViewContext<Self>) -> Div>>,
 434    zoomed: Option<AnyWeakView>,
 435    zoomed_position: Option<DockPosition>,
 436    center: PaneGroup,
 437    left_dock: View<Dock>,
 438    bottom_dock: View<Dock>,
 439    right_dock: View<Dock>,
 440    panes: Vec<View<Pane>>,
 441    panes_by_item: HashMap<EntityId, WeakView<Pane>>,
 442    active_pane: View<Pane>,
 443    last_active_center_pane: Option<WeakView<Pane>>,
 444    last_active_view_id: Option<proto::ViewId>,
 445    status_bar: View<StatusBar>,
 446    modal_layer: View<ModalLayer>,
 447    titlebar_item: Option<AnyView>,
 448    notifications: Vec<(TypeId, usize, Box<dyn NotificationHandle>)>,
 449    project: Model<Project>,
 450    follower_states: HashMap<View<Pane>, FollowerState>,
 451    last_leaders_by_pane: HashMap<WeakView<Pane>, PeerId>,
 452    window_edited: bool,
 453    active_call: Option<(Model<ActiveCall>, Vec<Subscription>)>,
 454    leader_updates_tx: mpsc::UnboundedSender<(PeerId, proto::UpdateFollowers)>,
 455    database_id: WorkspaceId,
 456    app_state: Arc<AppState>,
 457    _subscriptions: Vec<Subscription>,
 458    _apply_leader_updates: Task<Result<()>>,
 459    _observe_current_user: Task<Result<()>>,
 460    _schedule_serialize: Option<Task<()>>,
 461    pane_history_timestamp: Arc<AtomicUsize>,
 462    bounds: Bounds<Pixels>,
 463}
 464
 465impl EventEmitter<Event> for Workspace {}
 466
 467#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
 468pub struct ViewId {
 469    pub creator: PeerId,
 470    pub id: u64,
 471}
 472
 473#[derive(Default)]
 474struct FollowerState {
 475    leader_id: PeerId,
 476    active_view_id: Option<ViewId>,
 477    items_by_leader_view_id: HashMap<ViewId, Box<dyn FollowableItemHandle>>,
 478}
 479
 480impl Workspace {
 481    pub fn new(
 482        workspace_id: WorkspaceId,
 483        project: Model<Project>,
 484        app_state: Arc<AppState>,
 485        cx: &mut ViewContext<Self>,
 486    ) -> Self {
 487        cx.observe(&project, |_, _, cx| cx.notify()).detach();
 488        cx.subscribe(&project, move |this, _, event, cx| {
 489            match event {
 490                project::Event::RemoteIdChanged(_) => {
 491                    this.update_window_title(cx);
 492                }
 493
 494                project::Event::CollaboratorLeft(peer_id) => {
 495                    this.collaborator_left(*peer_id, cx);
 496                }
 497
 498                project::Event::WorktreeRemoved(_) | project::Event::WorktreeAdded => {
 499                    this.update_window_title(cx);
 500                    this.serialize_workspace(cx);
 501                }
 502
 503                project::Event::DisconnectedFromHost => {
 504                    this.update_window_edited(cx);
 505                    cx.blur();
 506                }
 507
 508                project::Event::Closed => {
 509                    cx.remove_window();
 510                }
 511
 512                project::Event::DeletedEntry(entry_id) => {
 513                    for pane in this.panes.iter() {
 514                        pane.update(cx, |pane, cx| {
 515                            pane.handle_deleted_project_item(*entry_id, cx)
 516                        });
 517                    }
 518                }
 519
 520                project::Event::Notification(message) => this.show_notification(0, cx, |cx| {
 521                    cx.build_view(|_| MessageNotification::new(message.clone()))
 522                }),
 523
 524                _ => {}
 525            }
 526            cx.notify()
 527        })
 528        .detach();
 529        cx.on_blur_window(|this, cx| {
 530            let focus_handle = this.focus_handle(cx);
 531            cx.focus(&focus_handle);
 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 = cx.build_view(|cx| PanelButtons::new(left_dock.clone(), cx));
 590        let bottom_dock_buttons = cx.build_view(|cx| PanelButtons::new(bottom_dock.clone(), cx));
 591        let right_dock_buttons = cx.build_view(|cx| PanelButtons::new(right_dock.clone(), cx));
 592        let status_bar = cx.build_view(|cx| {
 593            let mut status_bar = StatusBar::new(&center_pane.clone(), cx);
 594            status_bar.add_left_item(left_dock_buttons, cx);
 595            status_bar.add_right_item(right_dock_buttons, cx);
 596            status_bar.add_right_item(bottom_dock_buttons, cx);
 597            status_bar
 598        });
 599
 600        let modal_layer = cx.build_view(|_| ModalLayer::new());
 601
 602        // todo!()
 603        // cx.update_default_global::<DragAndDrop<Workspace>, _, _>(|drag_and_drop, _| {
 604        //     drag_and_drop.register_container(weak_handle.clone());
 605        // });
 606
 607        let mut active_call = None;
 608        if cx.has_global::<Model<ActiveCall>>() {
 609            let call = cx.global::<Model<ActiveCall>>().clone();
 610            let mut subscriptions = Vec::new();
 611            subscriptions.push(cx.subscribe(&call, Self::on_active_call_event));
 612            active_call = Some((call, subscriptions));
 613        }
 614
 615        let subscriptions = vec![
 616            cx.observe_window_activation(Self::on_window_activation_changed),
 617            cx.observe_window_bounds(move |_, cx| {
 618                if let Some(display) = cx.display() {
 619                    // Transform fixed bounds to be stored in terms of the containing display
 620                    let mut bounds = cx.window_bounds();
 621                    if let WindowBounds::Fixed(window_bounds) = &mut bounds {
 622                        let display_bounds = display.bounds();
 623                        window_bounds.origin.x -= display_bounds.origin.x;
 624                        window_bounds.origin.y -= display_bounds.origin.y;
 625                    }
 626
 627                    if let Some(display_uuid) = display.uuid().log_err() {
 628                        cx.background_executor()
 629                            .spawn(DB.set_window_bounds(workspace_id, bounds, display_uuid))
 630                            .detach_and_log_err(cx);
 631                    }
 632                }
 633                cx.notify();
 634            }),
 635            cx.observe(&left_dock, |this, _, cx| {
 636                this.serialize_workspace(cx);
 637                cx.notify();
 638            }),
 639            cx.observe(&bottom_dock, |this, _, cx| {
 640                this.serialize_workspace(cx);
 641                cx.notify();
 642            }),
 643            cx.observe(&right_dock, |this, _, cx| {
 644                this.serialize_workspace(cx);
 645                cx.notify();
 646            }),
 647            cx.on_release(|this, cx| {
 648                this.app_state.workspace_store.update(cx, |store, _| {
 649                    store.workspaces.remove(&this.window_self);
 650                })
 651            }),
 652        ];
 653
 654        cx.defer(|this, cx| {
 655            this.update_window_title(cx);
 656            // todo! @nate - these are useful for testing notifications
 657            // this.show_error(
 658            //     &anyhow::anyhow!("what happens if this message is very very very very very long"),
 659            //     cx,
 660            // );
 661
 662            // this.show_notification(1, cx, |cx| {
 663            //     cx.build_view(|_cx| {
 664            //         simple_message_notification::MessageNotification::new(format!("Error:"))
 665            //             .with_click_message("click here because!")
 666            //     })
 667            // });
 668        });
 669        Workspace {
 670            window_self: window_handle,
 671            weak_self: weak_handle.clone(),
 672            zoomed: None,
 673            zoomed_position: None,
 674            center: PaneGroup::new(center_pane.clone()),
 675            panes: vec![center_pane.clone()],
 676            panes_by_item: Default::default(),
 677            active_pane: center_pane.clone(),
 678            last_active_center_pane: Some(center_pane.downgrade()),
 679            last_active_view_id: None,
 680            status_bar,
 681            modal_layer,
 682            titlebar_item: None,
 683            notifications: Default::default(),
 684            left_dock,
 685            bottom_dock,
 686            right_dock,
 687            project: project.clone(),
 688            follower_states: Default::default(),
 689            last_leaders_by_pane: Default::default(),
 690            window_edited: false,
 691            active_call,
 692            database_id: workspace_id,
 693            app_state,
 694            _observe_current_user,
 695            _apply_leader_updates,
 696            _schedule_serialize: None,
 697            leader_updates_tx,
 698            _subscriptions: subscriptions,
 699            pane_history_timestamp,
 700            workspace_actions: Default::default(),
 701            // This data will be incorrect, but it will be overwritten by the time it needs to be used.
 702            bounds: Default::default(),
 703        }
 704    }
 705
 706    fn new_local(
 707        abs_paths: Vec<PathBuf>,
 708        app_state: Arc<AppState>,
 709        requesting_window: Option<WindowHandle<Workspace>>,
 710        cx: &mut AppContext,
 711    ) -> Task<
 712        anyhow::Result<(
 713            WindowHandle<Workspace>,
 714            Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
 715        )>,
 716    > {
 717        let project_handle = Project::local(
 718            app_state.client.clone(),
 719            app_state.node_runtime.clone(),
 720            app_state.user_store.clone(),
 721            app_state.languages.clone(),
 722            app_state.fs.clone(),
 723            cx,
 724        );
 725
 726        cx.spawn(|mut cx| async move {
 727            let serialized_workspace: Option<SerializedWorkspace> =
 728                persistence::DB.workspace_for_roots(&abs_paths.as_slice());
 729
 730            let paths_to_open = Arc::new(abs_paths);
 731
 732            // Get project paths for all of the abs_paths
 733            let mut worktree_roots: HashSet<Arc<Path>> = Default::default();
 734            let mut project_paths: Vec<(PathBuf, Option<ProjectPath>)> =
 735                Vec::with_capacity(paths_to_open.len());
 736            for path in paths_to_open.iter().cloned() {
 737                if let Some((worktree, project_entry)) = cx
 738                    .update(|cx| {
 739                        Workspace::project_path_for_path(project_handle.clone(), &path, true, cx)
 740                    })?
 741                    .await
 742                    .log_err()
 743                {
 744                    worktree_roots.extend(worktree.update(&mut cx, |tree, _| tree.abs_path()).ok());
 745                    project_paths.push((path, Some(project_entry)));
 746                } else {
 747                    project_paths.push((path, None));
 748                }
 749            }
 750
 751            let workspace_id = if let Some(serialized_workspace) = serialized_workspace.as_ref() {
 752                serialized_workspace.id
 753            } else {
 754                DB.next_id().await.unwrap_or(0)
 755            };
 756
 757            let window = if let Some(window) = requesting_window {
 758                cx.update_window(window.into(), |_, cx| {
 759                    cx.replace_root_view(|cx| {
 760                        Workspace::new(workspace_id, project_handle.clone(), app_state.clone(), cx)
 761                    });
 762                })?;
 763                window
 764            } else {
 765                let window_bounds_override = window_bounds_env_override(&cx);
 766                let (bounds, display) = if let Some(bounds) = window_bounds_override {
 767                    (Some(bounds), None)
 768                } else {
 769                    serialized_workspace
 770                        .as_ref()
 771                        .and_then(|serialized_workspace| {
 772                            let serialized_display = serialized_workspace.display?;
 773                            let mut bounds = serialized_workspace.bounds?;
 774
 775                            // Stored bounds are relative to the containing display.
 776                            // So convert back to global coordinates if that screen still exists
 777                            if let WindowBounds::Fixed(mut window_bounds) = bounds {
 778                                let screen = cx
 779                                    .update(|cx| {
 780                                        cx.displays().into_iter().find(|display| {
 781                                            display.uuid().ok() == Some(serialized_display)
 782                                        })
 783                                    })
 784                                    .ok()??;
 785                                let screen_bounds = screen.bounds();
 786                                window_bounds.origin.x += screen_bounds.origin.x;
 787                                window_bounds.origin.y += screen_bounds.origin.y;
 788                                bounds = WindowBounds::Fixed(window_bounds);
 789                            }
 790
 791                            Some((bounds, serialized_display))
 792                        })
 793                        .unzip()
 794                };
 795
 796                // Use the serialized workspace to construct the new window
 797                let options =
 798                    cx.update(|cx| (app_state.build_window_options)(bounds, display, cx))?;
 799
 800                cx.open_window(options, {
 801                    let app_state = app_state.clone();
 802                    let workspace_id = workspace_id.clone();
 803                    let project_handle = project_handle.clone();
 804                    move |cx| {
 805                        cx.build_view(|cx| {
 806                            Workspace::new(workspace_id, project_handle, app_state, cx)
 807                        })
 808                    }
 809                })?
 810            };
 811
 812            window
 813                .update(&mut cx, |_, cx| cx.activate_window())
 814                .log_err();
 815
 816            notify_if_database_failed(window, &mut cx);
 817            let opened_items = window
 818                .update(&mut cx, |_workspace, cx| {
 819                    open_items(serialized_workspace, project_paths, app_state, cx)
 820                })?
 821                .await
 822                .unwrap_or_default();
 823
 824            Ok((window, opened_items))
 825        })
 826    }
 827
 828    pub fn weak_handle(&self) -> WeakView<Self> {
 829        self.weak_self.clone()
 830    }
 831
 832    pub fn left_dock(&self) -> &View<Dock> {
 833        &self.left_dock
 834    }
 835
 836    pub fn bottom_dock(&self) -> &View<Dock> {
 837        &self.bottom_dock
 838    }
 839
 840    pub fn right_dock(&self) -> &View<Dock> {
 841        &self.right_dock
 842    }
 843
 844    pub fn add_panel<T: Panel>(&mut self, panel: View<T>, cx: &mut ViewContext<Self>) {
 845        let dock = match panel.position(cx) {
 846            DockPosition::Left => &self.left_dock,
 847            DockPosition::Bottom => &self.bottom_dock,
 848            DockPosition::Right => &self.right_dock,
 849        };
 850
 851        dock.update(cx, |dock, cx| {
 852            dock.add_panel(panel, self.weak_self.clone(), cx)
 853        });
 854    }
 855
 856    pub fn status_bar(&self) -> &View<StatusBar> {
 857        &self.status_bar
 858    }
 859
 860    pub fn app_state(&self) -> &Arc<AppState> {
 861        &self.app_state
 862    }
 863
 864    pub fn user_store(&self) -> &Model<UserStore> {
 865        &self.app_state.user_store
 866    }
 867
 868    pub fn project(&self) -> &Model<Project> {
 869        &self.project
 870    }
 871
 872    pub fn recent_navigation_history(
 873        &self,
 874        limit: Option<usize>,
 875        cx: &AppContext,
 876    ) -> Vec<(ProjectPath, Option<PathBuf>)> {
 877        let mut abs_paths_opened: HashMap<PathBuf, HashSet<ProjectPath>> = HashMap::default();
 878        let mut history: HashMap<ProjectPath, (Option<PathBuf>, usize)> = HashMap::default();
 879        for pane in &self.panes {
 880            let pane = pane.read(cx);
 881            pane.nav_history()
 882                .for_each_entry(cx, |entry, (project_path, fs_path)| {
 883                    if let Some(fs_path) = &fs_path {
 884                        abs_paths_opened
 885                            .entry(fs_path.clone())
 886                            .or_default()
 887                            .insert(project_path.clone());
 888                    }
 889                    let timestamp = entry.timestamp;
 890                    match history.entry(project_path) {
 891                        hash_map::Entry::Occupied(mut entry) => {
 892                            let (_, old_timestamp) = entry.get();
 893                            if &timestamp > old_timestamp {
 894                                entry.insert((fs_path, timestamp));
 895                            }
 896                        }
 897                        hash_map::Entry::Vacant(entry) => {
 898                            entry.insert((fs_path, timestamp));
 899                        }
 900                    }
 901                });
 902        }
 903
 904        history
 905            .into_iter()
 906            .sorted_by_key(|(_, (_, timestamp))| *timestamp)
 907            .map(|(project_path, (fs_path, _))| (project_path, fs_path))
 908            .rev()
 909            .filter(|(history_path, abs_path)| {
 910                let latest_project_path_opened = abs_path
 911                    .as_ref()
 912                    .and_then(|abs_path| abs_paths_opened.get(abs_path))
 913                    .and_then(|project_paths| {
 914                        project_paths
 915                            .iter()
 916                            .max_by(|b1, b2| b1.worktree_id.cmp(&b2.worktree_id))
 917                    });
 918
 919                match latest_project_path_opened {
 920                    Some(latest_project_path_opened) => latest_project_path_opened == history_path,
 921                    None => true,
 922                }
 923            })
 924            .take(limit.unwrap_or(usize::MAX))
 925            .collect()
 926    }
 927
 928    fn navigate_history(
 929        &mut self,
 930        pane: WeakView<Pane>,
 931        mode: NavigationMode,
 932        cx: &mut ViewContext<Workspace>,
 933    ) -> Task<Result<()>> {
 934        let to_load = if let Some(pane) = pane.upgrade() {
 935            // todo!("focus")
 936            // cx.focus(&pane);
 937
 938            pane.update(cx, |pane, cx| {
 939                loop {
 940                    // Retrieve the weak item handle from the history.
 941                    let entry = pane.nav_history_mut().pop(mode, cx)?;
 942
 943                    // If the item is still present in this pane, then activate it.
 944                    if let Some(index) = entry
 945                        .item
 946                        .upgrade()
 947                        .and_then(|v| pane.index_for_item(v.as_ref()))
 948                    {
 949                        let prev_active_item_index = pane.active_item_index();
 950                        pane.nav_history_mut().set_mode(mode);
 951                        pane.activate_item(index, true, true, cx);
 952                        pane.nav_history_mut().set_mode(NavigationMode::Normal);
 953
 954                        let mut navigated = prev_active_item_index != pane.active_item_index();
 955                        if let Some(data) = entry.data {
 956                            navigated |= pane.active_item()?.navigate(data, cx);
 957                        }
 958
 959                        if navigated {
 960                            break None;
 961                        }
 962                    }
 963                    // If the item is no longer present in this pane, then retrieve its
 964                    // project path in order to reopen it.
 965                    else {
 966                        break pane
 967                            .nav_history()
 968                            .path_for_item(entry.item.id())
 969                            .map(|(project_path, _)| (project_path, entry));
 970                    }
 971                }
 972            })
 973        } else {
 974            None
 975        };
 976
 977        if let Some((project_path, entry)) = to_load {
 978            // If the item was no longer present, then load it again from its previous path.
 979            let task = self.load_path(project_path, cx);
 980            cx.spawn(|workspace, mut cx| async move {
 981                let task = task.await;
 982                let mut navigated = false;
 983                if let Some((project_entry_id, build_item)) = task.log_err() {
 984                    let prev_active_item_id = pane.update(&mut cx, |pane, _| {
 985                        pane.nav_history_mut().set_mode(mode);
 986                        pane.active_item().map(|p| p.item_id())
 987                    })?;
 988
 989                    pane.update(&mut cx, |pane, cx| {
 990                        let item = pane.open_item(project_entry_id, true, cx, build_item);
 991                        navigated |= Some(item.item_id()) != prev_active_item_id;
 992                        pane.nav_history_mut().set_mode(NavigationMode::Normal);
 993                        if let Some(data) = entry.data {
 994                            navigated |= item.navigate(data, cx);
 995                        }
 996                    })?;
 997                }
 998
 999                if !navigated {
1000                    workspace
1001                        .update(&mut cx, |workspace, cx| {
1002                            Self::navigate_history(workspace, pane, mode, cx)
1003                        })?
1004                        .await?;
1005                }
1006
1007                Ok(())
1008            })
1009        } else {
1010            Task::ready(Ok(()))
1011        }
1012    }
1013
1014    pub fn go_back(
1015        &mut self,
1016        pane: WeakView<Pane>,
1017        cx: &mut ViewContext<Workspace>,
1018    ) -> Task<Result<()>> {
1019        self.navigate_history(pane, NavigationMode::GoingBack, cx)
1020    }
1021
1022    pub fn go_forward(
1023        &mut self,
1024        pane: WeakView<Pane>,
1025        cx: &mut ViewContext<Workspace>,
1026    ) -> Task<Result<()>> {
1027        self.navigate_history(pane, NavigationMode::GoingForward, cx)
1028    }
1029
1030    pub fn reopen_closed_item(&mut self, cx: &mut ViewContext<Workspace>) -> Task<Result<()>> {
1031        self.navigate_history(
1032            self.active_pane().downgrade(),
1033            NavigationMode::ReopeningClosedItem,
1034            cx,
1035        )
1036    }
1037
1038    pub fn client(&self) -> &Client {
1039        &self.app_state.client
1040    }
1041
1042    pub fn set_titlebar_item(&mut self, item: AnyView, cx: &mut ViewContext<Self>) {
1043        self.titlebar_item = Some(item);
1044        cx.notify();
1045    }
1046
1047    pub fn titlebar_item(&self) -> Option<AnyView> {
1048        self.titlebar_item.clone()
1049    }
1050
1051    /// Call the given callback with a workspace whose project is local.
1052    ///
1053    /// If the given workspace has a local project, then it will be passed
1054    /// to the callback. Otherwise, a new empty window will be created.
1055    pub fn with_local_workspace<T, F>(
1056        &mut self,
1057        cx: &mut ViewContext<Self>,
1058        callback: F,
1059    ) -> Task<Result<T>>
1060    where
1061        T: 'static,
1062        F: 'static + FnOnce(&mut Workspace, &mut ViewContext<Workspace>) -> T,
1063    {
1064        if self.project.read(cx).is_local() {
1065            Task::Ready(Some(Ok(callback(self, cx))))
1066        } else {
1067            let task = Self::new_local(Vec::new(), self.app_state.clone(), None, cx);
1068            cx.spawn(|_vh, mut cx| async move {
1069                let (workspace, _) = task.await?;
1070                workspace.update(&mut cx, callback)
1071            })
1072        }
1073    }
1074
1075    pub fn worktrees<'a>(&self, cx: &'a AppContext) -> impl 'a + Iterator<Item = Model<Worktree>> {
1076        self.project.read(cx).worktrees()
1077    }
1078
1079    pub fn visible_worktrees<'a>(
1080        &self,
1081        cx: &'a AppContext,
1082    ) -> impl 'a + Iterator<Item = Model<Worktree>> {
1083        self.project.read(cx).visible_worktrees(cx)
1084    }
1085
1086    pub fn worktree_scans_complete(&self, cx: &AppContext) -> impl Future<Output = ()> + 'static {
1087        let futures = self
1088            .worktrees(cx)
1089            .filter_map(|worktree| worktree.read(cx).as_local())
1090            .map(|worktree| worktree.scan_complete())
1091            .collect::<Vec<_>>();
1092        async move {
1093            for future in futures {
1094                future.await;
1095            }
1096        }
1097    }
1098
1099    pub fn close_global(_: &CloseWindow, cx: &mut AppContext) {
1100        cx.windows().iter().find(|window| {
1101            window
1102                .update(cx, |_, window| {
1103                    if window.is_window_active() {
1104                        //This can only get called when the window's project connection has been lost
1105                        //so we don't need to prompt the user for anything and instead just close the window
1106                        window.remove_window();
1107                        true
1108                    } else {
1109                        false
1110                    }
1111                })
1112                .unwrap_or(false)
1113        });
1114    }
1115
1116    pub fn close_window(&mut self, _: &CloseWindow, cx: &mut ViewContext<Self>) {
1117        let window = cx.window_handle();
1118        let prepare = self.prepare_to_close(false, cx);
1119        cx.spawn(|_, mut cx| async move {
1120            if prepare.await? {
1121                window.update(&mut cx, |_, cx| {
1122                    cx.remove_window();
1123                })?;
1124            }
1125            anyhow::Ok(())
1126        })
1127        .detach_and_log_err(cx)
1128    }
1129
1130    pub fn prepare_to_close(
1131        &mut self,
1132        quitting: bool,
1133        cx: &mut ViewContext<Self>,
1134    ) -> Task<Result<bool>> {
1135        //todo!(saveing)
1136        let active_call = self.active_call().cloned();
1137        let window = cx.window_handle();
1138
1139        cx.spawn(|this, mut cx| async move {
1140            let workspace_count = (*cx).update(|cx| {
1141                cx.windows()
1142                    .iter()
1143                    .filter(|window| window.downcast::<Workspace>().is_some())
1144                    .count()
1145            })?;
1146
1147            if let Some(active_call) = active_call {
1148                if !quitting
1149                    && workspace_count == 1
1150                    && active_call.read_with(&cx, |call, _| call.room().is_some())?
1151                {
1152                    let answer = window.update(&mut cx, |_, cx| {
1153                        cx.prompt(
1154                            PromptLevel::Warning,
1155                            "Do you want to leave the current call?",
1156                            &["Close window and hang up", "Cancel"],
1157                        )
1158                    })?;
1159
1160                    if answer.await.log_err() == Some(1) {
1161                        return anyhow::Ok(false);
1162                    } else {
1163                        active_call
1164                            .update(&mut cx, |call, cx| call.hang_up(cx))?
1165                            .await
1166                            .log_err();
1167                    }
1168                }
1169            }
1170
1171            Ok(this
1172                .update(&mut cx, |this, cx| {
1173                    this.save_all_internal(SaveIntent::Close, cx)
1174                })?
1175                .await?)
1176        })
1177    }
1178
1179    fn save_all(&mut self, action: &SaveAll, cx: &mut ViewContext<Self>) {
1180        self.save_all_internal(action.save_intent.unwrap_or(SaveIntent::SaveAll), cx)
1181            .detach_and_log_err(cx);
1182    }
1183
1184    fn save_all_internal(
1185        &mut self,
1186        mut save_intent: SaveIntent,
1187        cx: &mut ViewContext<Self>,
1188    ) -> Task<Result<bool>> {
1189        if self.project.read(cx).is_read_only() {
1190            return Task::ready(Ok(true));
1191        }
1192        let dirty_items = self
1193            .panes
1194            .iter()
1195            .flat_map(|pane| {
1196                pane.read(cx).items().filter_map(|item| {
1197                    if item.is_dirty(cx) {
1198                        Some((pane.downgrade(), item.boxed_clone()))
1199                    } else {
1200                        None
1201                    }
1202                })
1203            })
1204            .collect::<Vec<_>>();
1205
1206        let project = self.project.clone();
1207        cx.spawn(|workspace, mut cx| async move {
1208            // Override save mode and display "Save all files" prompt
1209            if save_intent == SaveIntent::Close && dirty_items.len() > 1 {
1210                let answer = workspace.update(&mut cx, |_, cx| {
1211                    let prompt = Pane::file_names_for_prompt(
1212                        &mut dirty_items.iter().map(|(_, handle)| handle),
1213                        dirty_items.len(),
1214                        cx,
1215                    );
1216                    cx.prompt(
1217                        PromptLevel::Warning,
1218                        &prompt,
1219                        &["Save all", "Discard all", "Cancel"],
1220                    )
1221                })?;
1222                match answer.await.log_err() {
1223                    Some(0) => save_intent = SaveIntent::SaveAll,
1224                    Some(1) => save_intent = SaveIntent::Skip,
1225                    _ => {}
1226                }
1227            }
1228            for (pane, item) in dirty_items {
1229                let (singleton, project_entry_ids) =
1230                    cx.update(|_, cx| (item.is_singleton(cx), item.project_entry_ids(cx)))?;
1231                if singleton || !project_entry_ids.is_empty() {
1232                    if let Some(ix) =
1233                        pane.update(&mut cx, |pane, _| pane.index_for_item(item.as_ref()))?
1234                    {
1235                        if !Pane::save_item(
1236                            project.clone(),
1237                            &pane,
1238                            ix,
1239                            &*item,
1240                            save_intent,
1241                            &mut cx,
1242                        )
1243                        .await?
1244                        {
1245                            return Ok(false);
1246                        }
1247                    }
1248                }
1249            }
1250            Ok(true)
1251        })
1252    }
1253
1254    pub fn open(&mut self, _: &Open, cx: &mut ViewContext<Self>) {
1255        let paths = cx.prompt_for_paths(PathPromptOptions {
1256            files: true,
1257            directories: true,
1258            multiple: true,
1259        });
1260
1261        cx.spawn(|this, mut cx| async move {
1262            let Some(paths) = paths.await.log_err().flatten() else {
1263                return;
1264            };
1265
1266            if let Some(task) = this
1267                .update(&mut cx, |this, cx| this.open_workspace_for_paths(paths, cx))
1268                .log_err()
1269            {
1270                task.await.log_err();
1271            }
1272        })
1273        .detach()
1274    }
1275
1276    pub fn open_workspace_for_paths(
1277        &mut self,
1278        paths: Vec<PathBuf>,
1279        cx: &mut ViewContext<Self>,
1280    ) -> Task<Result<()>> {
1281        let window = cx.window_handle().downcast::<Self>();
1282        let is_remote = self.project.read(cx).is_remote();
1283        let has_worktree = self.project.read(cx).worktrees().next().is_some();
1284        let has_dirty_items = self.items(cx).any(|item| item.is_dirty(cx));
1285        let close_task = if is_remote || has_worktree || has_dirty_items {
1286            None
1287        } else {
1288            Some(self.prepare_to_close(false, cx))
1289        };
1290        let app_state = self.app_state.clone();
1291
1292        cx.spawn(|_, mut cx| async move {
1293            let window_to_replace = if let Some(close_task) = close_task {
1294                if !close_task.await? {
1295                    return Ok(());
1296                }
1297                window
1298            } else {
1299                None
1300            };
1301            cx.update(|_, cx| open_paths(&paths, &app_state, window_to_replace, cx))?
1302                .await?;
1303            Ok(())
1304        })
1305    }
1306
1307    #[allow(clippy::type_complexity)]
1308    pub fn open_paths(
1309        &mut self,
1310        mut abs_paths: Vec<PathBuf>,
1311        visible: bool,
1312        cx: &mut ViewContext<Self>,
1313    ) -> Task<Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>> {
1314        log::info!("open paths {abs_paths:?}");
1315
1316        let fs = self.app_state.fs.clone();
1317
1318        // Sort the paths to ensure we add worktrees for parents before their children.
1319        abs_paths.sort_unstable();
1320        cx.spawn(move |this, mut cx| async move {
1321            let mut tasks = Vec::with_capacity(abs_paths.len());
1322            for abs_path in &abs_paths {
1323                let project_path = match this
1324                    .update(&mut cx, |this, cx| {
1325                        Workspace::project_path_for_path(
1326                            this.project.clone(),
1327                            abs_path,
1328                            visible,
1329                            cx,
1330                        )
1331                    })
1332                    .log_err()
1333                {
1334                    Some(project_path) => project_path.await.log_err(),
1335                    None => None,
1336                };
1337
1338                let this = this.clone();
1339                let abs_path = abs_path.clone();
1340                let fs = fs.clone();
1341                let task = cx.spawn(move |mut cx| async move {
1342                    let (worktree, project_path) = project_path?;
1343                    if fs.is_file(&abs_path).await {
1344                        Some(
1345                            this.update(&mut cx, |this, cx| {
1346                                this.open_path(project_path, None, true, cx)
1347                            })
1348                            .log_err()?
1349                            .await,
1350                        )
1351                    } else {
1352                        this.update(&mut cx, |workspace, cx| {
1353                            let worktree = worktree.read(cx);
1354                            let worktree_abs_path = worktree.abs_path();
1355                            let entry_id = if abs_path == worktree_abs_path.as_ref() {
1356                                worktree.root_entry()
1357                            } else {
1358                                abs_path
1359                                    .strip_prefix(worktree_abs_path.as_ref())
1360                                    .ok()
1361                                    .and_then(|relative_path| {
1362                                        worktree.entry_for_path(relative_path)
1363                                    })
1364                            }
1365                            .map(|entry| entry.id);
1366                            if let Some(entry_id) = entry_id {
1367                                workspace.project.update(cx, |_, cx| {
1368                                    cx.emit(project::Event::ActiveEntryChanged(Some(entry_id)));
1369                                })
1370                            }
1371                        })
1372                        .log_err()?;
1373                        None
1374                    }
1375                });
1376                tasks.push(task);
1377            }
1378
1379            futures::future::join_all(tasks).await
1380        })
1381    }
1382
1383    fn add_folder_to_project(&mut self, _: &AddFolderToProject, cx: &mut ViewContext<Self>) {
1384        let paths = cx.prompt_for_paths(PathPromptOptions {
1385            files: false,
1386            directories: true,
1387            multiple: true,
1388        });
1389        cx.spawn(|this, mut cx| async move {
1390            if let Some(paths) = paths.await.log_err().flatten() {
1391                let results = this
1392                    .update(&mut cx, |this, cx| this.open_paths(paths, true, cx))?
1393                    .await;
1394                for result in results.into_iter().flatten() {
1395                    result.log_err();
1396                }
1397            }
1398            anyhow::Ok(())
1399        })
1400        .detach_and_log_err(cx);
1401    }
1402
1403    fn project_path_for_path(
1404        project: Model<Project>,
1405        abs_path: &Path,
1406        visible: bool,
1407        cx: &mut AppContext,
1408    ) -> Task<Result<(Model<Worktree>, ProjectPath)>> {
1409        let entry = project.update(cx, |project, cx| {
1410            project.find_or_create_local_worktree(abs_path, visible, cx)
1411        });
1412        cx.spawn(|mut cx| async move {
1413            let (worktree, path) = entry.await?;
1414            let worktree_id = worktree.update(&mut cx, |t, _| t.id())?;
1415            Ok((
1416                worktree,
1417                ProjectPath {
1418                    worktree_id,
1419                    path: path.into(),
1420                },
1421            ))
1422        })
1423    }
1424
1425    pub fn items<'a>(
1426        &'a self,
1427        cx: &'a AppContext,
1428    ) -> impl 'a + Iterator<Item = &Box<dyn ItemHandle>> {
1429        self.panes.iter().flat_map(|pane| pane.read(cx).items())
1430    }
1431
1432    pub fn item_of_type<T: Item>(&self, cx: &AppContext) -> Option<View<T>> {
1433        self.items_of_type(cx).max_by_key(|item| item.item_id())
1434    }
1435
1436    pub fn items_of_type<'a, T: Item>(
1437        &'a self,
1438        cx: &'a AppContext,
1439    ) -> impl 'a + Iterator<Item = View<T>> {
1440        self.panes
1441            .iter()
1442            .flat_map(|pane| pane.read(cx).items_of_type())
1443    }
1444
1445    pub fn active_item(&self, cx: &AppContext) -> Option<Box<dyn ItemHandle>> {
1446        self.active_pane().read(cx).active_item()
1447    }
1448
1449    pub fn active_item_as<I: 'static>(&self, cx: &AppContext) -> Option<View<I>> {
1450        let item = self.active_item(cx)?;
1451        item.to_any().downcast::<I>().ok()
1452    }
1453
1454    fn active_project_path(&self, cx: &ViewContext<Self>) -> Option<ProjectPath> {
1455        self.active_item(cx).and_then(|item| item.project_path(cx))
1456    }
1457
1458    pub fn save_active_item(
1459        &mut self,
1460        save_intent: SaveIntent,
1461        cx: &mut ViewContext<Self>,
1462    ) -> Task<Result<()>> {
1463        let project = self.project.clone();
1464        let pane = self.active_pane();
1465        let item_ix = pane.read(cx).active_item_index();
1466        let item = pane.read(cx).active_item();
1467        let pane = pane.downgrade();
1468
1469        cx.spawn(|_, mut cx| async move {
1470            if let Some(item) = item {
1471                Pane::save_item(project, &pane, item_ix, item.as_ref(), save_intent, &mut cx)
1472                    .await
1473                    .map(|_| ())
1474            } else {
1475                Ok(())
1476            }
1477        })
1478    }
1479
1480    pub fn close_inactive_items_and_panes(
1481        &mut self,
1482        _: &CloseInactiveTabsAndPanes,
1483        cx: &mut ViewContext<Self>,
1484    ) {
1485        self.close_all_internal(true, SaveIntent::Close, cx)
1486            .map(|task| task.detach_and_log_err(cx));
1487    }
1488
1489    pub fn close_all_items_and_panes(
1490        &mut self,
1491        action: &CloseAllItemsAndPanes,
1492        cx: &mut ViewContext<Self>,
1493    ) {
1494        self.close_all_internal(false, action.save_intent.unwrap_or(SaveIntent::Close), cx)
1495            .map(|task| task.detach_and_log_err(cx));
1496    }
1497
1498    fn close_all_internal(
1499        &mut self,
1500        retain_active_pane: bool,
1501        save_intent: SaveIntent,
1502        cx: &mut ViewContext<Self>,
1503    ) -> Option<Task<Result<()>>> {
1504        let current_pane = self.active_pane();
1505
1506        let mut tasks = Vec::new();
1507
1508        if retain_active_pane {
1509            if let Some(current_pane_close) = current_pane.update(cx, |pane, cx| {
1510                pane.close_inactive_items(&CloseInactiveItems, cx)
1511            }) {
1512                tasks.push(current_pane_close);
1513            };
1514        }
1515
1516        for pane in self.panes() {
1517            if retain_active_pane && pane.entity_id() == current_pane.entity_id() {
1518                continue;
1519            }
1520
1521            if let Some(close_pane_items) = pane.update(cx, |pane: &mut Pane, cx| {
1522                pane.close_all_items(
1523                    &CloseAllItems {
1524                        save_intent: Some(save_intent),
1525                    },
1526                    cx,
1527                )
1528            }) {
1529                tasks.push(close_pane_items)
1530            }
1531        }
1532
1533        if tasks.is_empty() {
1534            None
1535        } else {
1536            Some(cx.spawn(|_, _| async move {
1537                for task in tasks {
1538                    task.await?
1539                }
1540                Ok(())
1541            }))
1542        }
1543    }
1544
1545    pub fn toggle_dock(&mut self, dock_side: DockPosition, cx: &mut ViewContext<Self>) {
1546        let dock = match dock_side {
1547            DockPosition::Left => &self.left_dock,
1548            DockPosition::Bottom => &self.bottom_dock,
1549            DockPosition::Right => &self.right_dock,
1550        };
1551        let mut focus_center = false;
1552        let mut reveal_dock = false;
1553        dock.update(cx, |dock, cx| {
1554            let other_is_zoomed = self.zoomed.is_some() && self.zoomed_position != Some(dock_side);
1555            let was_visible = dock.is_open() && !other_is_zoomed;
1556            dock.set_open(!was_visible, cx);
1557
1558            if let Some(active_panel) = dock.active_panel() {
1559                if was_visible {
1560                    if active_panel.focus_handle(cx).contains_focused(cx) {
1561                        focus_center = true;
1562                    }
1563                } else {
1564                    let focus_handle = &active_panel.focus_handle(cx);
1565                    cx.focus(focus_handle);
1566                    reveal_dock = true;
1567                }
1568            }
1569        });
1570
1571        if reveal_dock {
1572            self.dismiss_zoomed_items_to_reveal(Some(dock_side), cx);
1573        }
1574
1575        if focus_center {
1576            self.active_pane.update(cx, |pane, cx| pane.focus(cx))
1577        }
1578
1579        cx.notify();
1580        self.serialize_workspace(cx);
1581    }
1582
1583    pub fn close_all_docks(&mut self, cx: &mut ViewContext<Self>) {
1584        let docks = [&self.left_dock, &self.bottom_dock, &self.right_dock];
1585
1586        for dock in docks {
1587            dock.update(cx, |dock, cx| {
1588                dock.set_open(false, cx);
1589            });
1590        }
1591
1592        // todo!("focus")
1593        // cx.focus_self();
1594        cx.notify();
1595        self.serialize_workspace(cx);
1596    }
1597
1598    /// Transfer focus to the panel of the given type.
1599    pub fn focus_panel<T: Panel>(&mut self, cx: &mut ViewContext<Self>) -> Option<View<T>> {
1600        let panel = self.focus_or_unfocus_panel::<T>(cx, |_, _| true)?;
1601        panel.to_any().downcast().ok()
1602    }
1603
1604    /// Focus the panel of the given type if it isn't already focused. If it is
1605    /// already focused, then transfer focus back to the workspace center.
1606    pub fn toggle_panel_focus<T: Panel>(&mut self, cx: &mut ViewContext<Self>) {
1607        self.focus_or_unfocus_panel::<T>(cx, |panel, cx| {
1608            !panel.focus_handle(cx).contains_focused(cx)
1609        });
1610    }
1611
1612    /// Focus or unfocus the given panel type, depending on the given callback.
1613    fn focus_or_unfocus_panel<T: Panel>(
1614        &mut self,
1615        cx: &mut ViewContext<Self>,
1616        should_focus: impl Fn(&dyn PanelHandle, &mut ViewContext<Dock>) -> bool,
1617    ) -> Option<Arc<dyn PanelHandle>> {
1618        for dock in [&self.left_dock, &self.bottom_dock, &self.right_dock] {
1619            if let Some(panel_index) = dock.read(cx).panel_index_for_type::<T>() {
1620                let mut focus_center = false;
1621                let mut reveal_dock = false;
1622                let panel = dock.update(cx, |dock, cx| {
1623                    dock.activate_panel(panel_index, cx);
1624
1625                    let panel = dock.active_panel().cloned();
1626                    if let Some(panel) = panel.as_ref() {
1627                        if should_focus(&**panel, cx) {
1628                            dock.set_open(true, cx);
1629                            panel.focus_handle(cx).focus(cx);
1630                            reveal_dock = true;
1631                        } else {
1632                            // todo!()
1633                            // if panel.is_zoomed(cx) {
1634                            //     dock.set_open(false, cx);
1635                            // }
1636                            focus_center = true;
1637                        }
1638                    }
1639                    panel
1640                });
1641
1642                if focus_center {
1643                    self.active_pane.update(cx, |pane, cx| pane.focus(cx))
1644                }
1645
1646                self.serialize_workspace(cx);
1647                cx.notify();
1648                return panel;
1649            }
1650        }
1651        None
1652    }
1653
1654    pub fn panel<T: Panel>(&self, cx: &WindowContext) -> Option<View<T>> {
1655        for dock in [&self.left_dock, &self.bottom_dock, &self.right_dock] {
1656            let dock = dock.read(cx);
1657            if let Some(panel) = dock.panel::<T>() {
1658                return Some(panel);
1659            }
1660        }
1661        None
1662    }
1663
1664    // todo!("implement zoom")
1665    #[allow(unused)]
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(&cursor) => cursor,
2036            _ => bounding_box.center(),
2037        };
2038
2039        let distance_to_next = 8.; //todo(pane dividers styling)
2040
2041        let target = match direction {
2042            SplitDirection::Left => {
2043                Point::new(bounding_box.left() - 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.top() - distance_to_next.into())
2050            }
2051            SplitDirection::Down => {
2052                Point::new(center.x, bounding_box.bottom() + 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    #[allow(unused)]
3002    fn schedule_serialize(&mut self, cx: &mut ViewContext<Self>) {
3003        self._schedule_serialize = Some(cx.spawn(|this, mut cx| async move {
3004            cx.background_executor()
3005                .timer(Duration::from_millis(100))
3006                .await;
3007            this.update(&mut cx, |this, cx| this.serialize_workspace(cx))
3008                .log_err();
3009        }));
3010    }
3011
3012    fn serialize_workspace(&self, cx: &mut ViewContext<Self>) {
3013        fn serialize_pane_handle(pane_handle: &View<Pane>, cx: &WindowContext) -> SerializedPane {
3014            let (items, active) = {
3015                let pane = pane_handle.read(cx);
3016                let active_item_id = pane.active_item().map(|item| item.item_id());
3017                (
3018                    pane.items()
3019                        .filter_map(|item_handle| {
3020                            Some(SerializedItem {
3021                                kind: Arc::from(item_handle.serialized_item_kind()?),
3022                                item_id: item_handle.item_id().as_u64(),
3023                                active: Some(item_handle.item_id()) == active_item_id,
3024                            })
3025                        })
3026                        .collect::<Vec<_>>(),
3027                    pane.has_focus(cx),
3028                )
3029            };
3030
3031            SerializedPane::new(items, active)
3032        }
3033
3034        fn build_serialized_pane_group(
3035            pane_group: &Member,
3036            cx: &WindowContext,
3037        ) -> SerializedPaneGroup {
3038            match pane_group {
3039                Member::Axis(PaneAxis {
3040                    axis,
3041                    members,
3042                    flexes,
3043                    bounding_boxes: _,
3044                }) => SerializedPaneGroup::Group {
3045                    axis: *axis,
3046                    children: members
3047                        .iter()
3048                        .map(|member| build_serialized_pane_group(member, cx))
3049                        .collect::<Vec<_>>(),
3050                    flexes: Some(flexes.lock().clone()),
3051                },
3052                Member::Pane(pane_handle) => {
3053                    SerializedPaneGroup::Pane(serialize_pane_handle(&pane_handle, cx))
3054                }
3055            }
3056        }
3057
3058        fn build_serialized_docks(
3059            this: &Workspace,
3060            cx: &mut ViewContext<Workspace>,
3061        ) -> DockStructure {
3062            let left_dock = this.left_dock.read(cx);
3063            let left_visible = left_dock.is_open();
3064            let left_active_panel = left_dock
3065                .visible_panel()
3066                .and_then(|panel| Some(panel.persistent_name().to_string()));
3067            let left_dock_zoom = left_dock
3068                .visible_panel()
3069                .map(|panel| panel.is_zoomed(cx))
3070                .unwrap_or(false);
3071
3072            let right_dock = this.right_dock.read(cx);
3073            let right_visible = right_dock.is_open();
3074            let right_active_panel = right_dock
3075                .visible_panel()
3076                .and_then(|panel| Some(panel.persistent_name().to_string()));
3077            let right_dock_zoom = right_dock
3078                .visible_panel()
3079                .map(|panel| panel.is_zoomed(cx))
3080                .unwrap_or(false);
3081
3082            let bottom_dock = this.bottom_dock.read(cx);
3083            let bottom_visible = bottom_dock.is_open();
3084            let bottom_active_panel = bottom_dock
3085                .visible_panel()
3086                .and_then(|panel| Some(panel.persistent_name().to_string()));
3087            let bottom_dock_zoom = bottom_dock
3088                .visible_panel()
3089                .map(|panel| panel.is_zoomed(cx))
3090                .unwrap_or(false);
3091
3092            DockStructure {
3093                left: DockData {
3094                    visible: left_visible,
3095                    active_panel: left_active_panel,
3096                    zoom: left_dock_zoom,
3097                },
3098                right: DockData {
3099                    visible: right_visible,
3100                    active_panel: right_active_panel,
3101                    zoom: right_dock_zoom,
3102                },
3103                bottom: DockData {
3104                    visible: bottom_visible,
3105                    active_panel: bottom_active_panel,
3106                    zoom: bottom_dock_zoom,
3107                },
3108            }
3109        }
3110
3111        if let Some(location) = self.location(cx) {
3112            // Load bearing special case:
3113            //  - with_local_workspace() relies on this to not have other stuff open
3114            //    when you open your log
3115            if !location.paths().is_empty() {
3116                let center_group = build_serialized_pane_group(&self.center.root, cx);
3117                let docks = build_serialized_docks(self, cx);
3118
3119                let serialized_workspace = SerializedWorkspace {
3120                    id: self.database_id,
3121                    location,
3122                    center_group,
3123                    bounds: Default::default(),
3124                    display: Default::default(),
3125                    docks,
3126                };
3127
3128                cx.spawn(|_, _| persistence::DB.save_workspace(serialized_workspace))
3129                    .detach();
3130            }
3131        }
3132    }
3133
3134    pub(crate) fn load_workspace(
3135        serialized_workspace: SerializedWorkspace,
3136        paths_to_open: Vec<Option<ProjectPath>>,
3137        cx: &mut ViewContext<Workspace>,
3138    ) -> Task<Result<Vec<Option<Box<dyn ItemHandle>>>>> {
3139        cx.spawn(|workspace, mut cx| async move {
3140            let project = workspace.update(&mut cx, |workspace, _| workspace.project().clone())?;
3141
3142            let mut center_group = None;
3143            let mut center_items = None;
3144
3145            // Traverse the splits tree and add to things
3146            if let Some((group, active_pane, items)) = serialized_workspace
3147                .center_group
3148                .deserialize(
3149                    &project,
3150                    serialized_workspace.id,
3151                    workspace.clone(),
3152                    &mut cx,
3153                )
3154                .await
3155            {
3156                center_items = Some(items);
3157                center_group = Some((group, active_pane))
3158            }
3159
3160            let mut items_by_project_path = cx.update(|_, cx| {
3161                center_items
3162                    .unwrap_or_default()
3163                    .into_iter()
3164                    .filter_map(|item| {
3165                        let item = item?;
3166                        let project_path = item.project_path(cx)?;
3167                        Some((project_path, item))
3168                    })
3169                    .collect::<HashMap<_, _>>()
3170            })?;
3171
3172            let opened_items = paths_to_open
3173                .into_iter()
3174                .map(|path_to_open| {
3175                    path_to_open
3176                        .and_then(|path_to_open| items_by_project_path.remove(&path_to_open))
3177                })
3178                .collect::<Vec<_>>();
3179
3180            // Remove old panes from workspace panes list
3181            workspace.update(&mut cx, |workspace, cx| {
3182                if let Some((center_group, active_pane)) = center_group {
3183                    workspace.remove_panes(workspace.center.root.clone(), cx);
3184
3185                    // Swap workspace center group
3186                    workspace.center = PaneGroup::with_root(center_group);
3187                    workspace.last_active_center_pane = active_pane.as_ref().map(|p| p.downgrade());
3188                    if let Some(active_pane) = active_pane {
3189                        workspace.active_pane = active_pane;
3190                        cx.focus_self();
3191                    } else {
3192                        workspace.active_pane = workspace.center.first_pane().clone();
3193                    }
3194                }
3195
3196                let docks = serialized_workspace.docks;
3197                workspace.left_dock.update(cx, |dock, cx| {
3198                    dock.set_open(docks.left.visible, cx);
3199                    if let Some(active_panel) = docks.left.active_panel {
3200                        if let Some(ix) = dock.panel_index_for_persistent_name(&active_panel, cx) {
3201                            dock.activate_panel(ix, cx);
3202                        }
3203                    }
3204                    dock.active_panel()
3205                        .map(|panel| panel.set_zoomed(docks.left.zoom, cx));
3206                    if docks.left.visible && docks.left.zoom {
3207                        cx.focus_self()
3208                    }
3209                });
3210                // TODO: I think the bug is that setting zoom or active undoes the bottom zoom or something
3211                workspace.right_dock.update(cx, |dock, cx| {
3212                    dock.set_open(docks.right.visible, cx);
3213                    if let Some(active_panel) = docks.right.active_panel {
3214                        if let Some(ix) = dock.panel_index_for_persistent_name(&active_panel, cx) {
3215                            dock.activate_panel(ix, cx);
3216                        }
3217                    }
3218                    dock.active_panel()
3219                        .map(|panel| panel.set_zoomed(docks.right.zoom, cx));
3220
3221                    if docks.right.visible && docks.right.zoom {
3222                        cx.focus_self()
3223                    }
3224                });
3225                workspace.bottom_dock.update(cx, |dock, cx| {
3226                    dock.set_open(docks.bottom.visible, cx);
3227                    if let Some(active_panel) = docks.bottom.active_panel {
3228                        if let Some(ix) = dock.panel_index_for_persistent_name(&active_panel, cx) {
3229                            dock.activate_panel(ix, cx);
3230                        }
3231                    }
3232
3233                    dock.active_panel()
3234                        .map(|panel| panel.set_zoomed(docks.bottom.zoom, cx));
3235
3236                    if docks.bottom.visible && docks.bottom.zoom {
3237                        cx.focus_self()
3238                    }
3239                });
3240
3241                cx.notify();
3242            })?;
3243
3244            // Serialize ourself to make sure our timestamps and any pane / item changes are replicated
3245            workspace.update(&mut cx, |workspace, cx| workspace.serialize_workspace(cx))?;
3246
3247            Ok(opened_items)
3248        })
3249    }
3250
3251    fn actions(&self, div: Div, cx: &mut ViewContext<Self>) -> Div {
3252        self.add_workspace_actions_listeners(div, cx)
3253            .on_action(cx.listener(Self::close_inactive_items_and_panes))
3254            .on_action(cx.listener(Self::close_all_items_and_panes))
3255            .on_action(cx.listener(Self::save_all))
3256            .on_action(cx.listener(Self::add_folder_to_project))
3257            .on_action(cx.listener(|workspace, _: &Unfollow, cx| {
3258                let pane = workspace.active_pane().clone();
3259                workspace.unfollow(&pane, cx);
3260            }))
3261            .on_action(cx.listener(|workspace, action: &Save, cx| {
3262                workspace
3263                    .save_active_item(action.save_intent.unwrap_or(SaveIntent::Save), cx)
3264                    .detach_and_log_err(cx);
3265            }))
3266            .on_action(cx.listener(|workspace, _: &SaveAs, cx| {
3267                workspace
3268                    .save_active_item(SaveIntent::SaveAs, cx)
3269                    .detach_and_log_err(cx);
3270            }))
3271            .on_action(cx.listener(|workspace, _: &ActivatePreviousPane, cx| {
3272                workspace.activate_previous_pane(cx)
3273            }))
3274            .on_action(
3275                cx.listener(|workspace, _: &ActivateNextPane, cx| workspace.activate_next_pane(cx)),
3276            )
3277            .on_action(
3278                cx.listener(|workspace, action: &ActivatePaneInDirection, cx| {
3279                    workspace.activate_pane_in_direction(action.0, cx)
3280                }),
3281            )
3282            .on_action(cx.listener(|workspace, action: &SwapPaneInDirection, cx| {
3283                workspace.swap_pane_in_direction(action.0, cx)
3284            }))
3285            .on_action(cx.listener(|this, _: &ToggleLeftDock, cx| {
3286                this.toggle_dock(DockPosition::Left, cx);
3287            }))
3288            .on_action(
3289                cx.listener(|workspace: &mut Workspace, _: &ToggleRightDock, cx| {
3290                    workspace.toggle_dock(DockPosition::Right, cx);
3291                }),
3292            )
3293            .on_action(
3294                cx.listener(|workspace: &mut Workspace, _: &ToggleBottomDock, cx| {
3295                    workspace.toggle_dock(DockPosition::Bottom, cx);
3296                }),
3297            )
3298            .on_action(
3299                cx.listener(|workspace: &mut Workspace, _: &CloseAllDocks, cx| {
3300                    workspace.close_all_docks(cx);
3301                }),
3302            )
3303            .on_action(cx.listener(Workspace::open))
3304            .on_action(cx.listener(Workspace::close_window))
3305
3306        //     cx.add_action(Workspace::activate_pane_at_index);
3307        //     cx.add_action(|workspace: &mut Workspace, _: &ReopenClosedItem, cx| {
3308        //         workspace.reopen_closed_item(cx).detach();
3309        //     });
3310        //     cx.add_action(|workspace: &mut Workspace, _: &GoBack, cx| {
3311        //         workspace
3312        //             .go_back(workspace.active_pane().downgrade(), cx)
3313        //             .detach();
3314        //     });
3315        //     cx.add_action(|workspace: &mut Workspace, _: &GoForward, cx| {
3316        //         workspace
3317        //             .go_forward(workspace.active_pane().downgrade(), cx)
3318        //             .detach();
3319        //     });
3320
3321        //     cx.add_action(|_: &mut Workspace, _: &install_cli::Install, cx| {
3322        //         cx.spawn(|workspace, mut cx| async move {
3323        //             let err = install_cli::install_cli(&cx)
3324        //                 .await
3325        //                 .context("Failed to create CLI symlink");
3326
3327        //             workspace.update(&mut cx, |workspace, cx| {
3328        //                 if matches!(err, Err(_)) {
3329        //                     err.notify_err(workspace, cx);
3330        //                 } else {
3331        //                     workspace.show_notification(1, cx, |cx| {
3332        //                         cx.build_view(|_| {
3333        //                             MessageNotification::new("Successfully installed the `zed` binary")
3334        //                         })
3335        //                     });
3336        //                 }
3337        //             })
3338        //         })
3339        //         .detach();
3340        //     });
3341    }
3342
3343    #[cfg(any(test, feature = "test-support"))]
3344    pub fn test_new(project: Model<Project>, cx: &mut ViewContext<Self>) -> Self {
3345        use node_runtime::FakeNodeRuntime;
3346
3347        let client = project.read(cx).client();
3348        let user_store = project.read(cx).user_store();
3349
3350        let workspace_store = cx.build_model(|cx| WorkspaceStore::new(client.clone(), cx));
3351        let app_state = Arc::new(AppState {
3352            languages: project.read(cx).languages().clone(),
3353            workspace_store,
3354            client,
3355            user_store,
3356            fs: project.read(cx).fs().clone(),
3357            build_window_options: |_, _, _| Default::default(),
3358            node_runtime: FakeNodeRuntime::new(),
3359        });
3360        let workspace = Self::new(0, project, app_state, cx);
3361        workspace.active_pane.update(cx, |pane, cx| pane.focus(cx));
3362        workspace
3363    }
3364
3365    //     fn render_dock(&self, position: DockPosition, cx: &WindowContext) -> Option<AnyElement<Self>> {
3366    //         let dock = match position {
3367    //             DockPosition::Left => &self.left_dock,
3368    //             DockPosition::Right => &self.right_dock,
3369    //             DockPosition::Bottom => &self.bottom_dock,
3370    //         };
3371    //         let active_panel = dock.read(cx).visible_panel()?;
3372    //         let element = if Some(active_panel.id()) == self.zoomed.as_ref().map(|zoomed| zoomed.id()) {
3373    //             dock.read(cx).render_placeholder(cx)
3374    //         } else {
3375    //             ChildView::new(dock, cx).into_any()
3376    //         };
3377
3378    //         Some(
3379    //             element
3380    //                 .constrained()
3381    //                 .dynamically(move |constraint, _, cx| match position {
3382    //                     DockPosition::Left | DockPosition::Right => SizeConstraint::new(
3383    //                         Vector2F::new(20., constraint.min.y()),
3384    //                         Vector2F::new(cx.window_size().x() * 0.8, constraint.max.y()),
3385    //                     ),
3386    //                     DockPosition::Bottom => SizeConstraint::new(
3387    //                         Vector2F::new(constraint.min.x(), 20.),
3388    //                         Vector2F::new(constraint.max.x(), cx.window_size().y() * 0.8),
3389    //                     ),
3390    //                 })
3391    //                 .into_any(),
3392    //         )
3393    //     }
3394    // }
3395    pub fn register_action<A: Action>(
3396        &mut self,
3397        callback: impl Fn(&mut Self, &A, &mut ViewContext<Self>) + 'static,
3398    ) -> &mut Self {
3399        let callback = Arc::new(callback);
3400
3401        self.workspace_actions.push(Box::new(move |div, cx| {
3402            let callback = callback.clone();
3403            div.on_action(
3404                cx.listener(move |workspace, event, cx| (callback.clone())(workspace, event, cx)),
3405            )
3406        }));
3407        self
3408    }
3409
3410    fn add_workspace_actions_listeners(&self, div: Div, cx: &mut ViewContext<Self>) -> Div {
3411        let mut div = div
3412            .on_action(cx.listener(Self::close_inactive_items_and_panes))
3413            .on_action(cx.listener(Self::close_all_items_and_panes))
3414            .on_action(cx.listener(Self::add_folder_to_project))
3415            .on_action(cx.listener(Self::save_all))
3416            .on_action(cx.listener(Self::open));
3417        for action in self.workspace_actions.iter() {
3418            div = (action)(div, cx)
3419        }
3420        div
3421    }
3422
3423    pub fn active_modal<V: ManagedView + 'static>(
3424        &mut self,
3425        cx: &ViewContext<Self>,
3426    ) -> Option<View<V>> {
3427        self.modal_layer.read(cx).active_modal()
3428    }
3429
3430    pub fn toggle_modal<V: ModalView, B>(&mut self, cx: &mut ViewContext<Self>, build: B)
3431    where
3432        B: FnOnce(&mut ViewContext<V>) -> V,
3433    {
3434        self.modal_layer
3435            .update(cx, |modal_layer, cx| modal_layer.toggle_modal(cx, build))
3436    }
3437}
3438
3439fn window_bounds_env_override(cx: &AsyncAppContext) -> Option<WindowBounds> {
3440    let display_origin = cx
3441        .update(|cx| Some(cx.displays().first()?.bounds().origin))
3442        .ok()??;
3443    ZED_WINDOW_POSITION
3444        .zip(*ZED_WINDOW_SIZE)
3445        .map(|(position, size)| {
3446            WindowBounds::Fixed(Bounds {
3447                origin: display_origin + position,
3448                size,
3449            })
3450        })
3451}
3452
3453fn open_items(
3454    serialized_workspace: Option<SerializedWorkspace>,
3455    mut project_paths_to_open: Vec<(PathBuf, Option<ProjectPath>)>,
3456    app_state: Arc<AppState>,
3457    cx: &mut ViewContext<Workspace>,
3458) -> impl 'static + Future<Output = Result<Vec<Option<Result<Box<dyn ItemHandle>>>>>> {
3459    let restored_items = serialized_workspace.map(|serialized_workspace| {
3460        Workspace::load_workspace(
3461            serialized_workspace,
3462            project_paths_to_open
3463                .iter()
3464                .map(|(_, project_path)| project_path)
3465                .cloned()
3466                .collect(),
3467            cx,
3468        )
3469    });
3470
3471    cx.spawn(|workspace, mut cx| async move {
3472        let mut opened_items = Vec::with_capacity(project_paths_to_open.len());
3473
3474        if let Some(restored_items) = restored_items {
3475            let restored_items = restored_items.await?;
3476
3477            let restored_project_paths = restored_items
3478                .iter()
3479                .filter_map(|item| {
3480                    cx.update(|_, cx| item.as_ref()?.project_path(cx))
3481                        .ok()
3482                        .flatten()
3483                })
3484                .collect::<HashSet<_>>();
3485
3486            for restored_item in restored_items {
3487                opened_items.push(restored_item.map(Ok));
3488            }
3489
3490            project_paths_to_open
3491                .iter_mut()
3492                .for_each(|(_, project_path)| {
3493                    if let Some(project_path_to_open) = project_path {
3494                        if restored_project_paths.contains(project_path_to_open) {
3495                            *project_path = None;
3496                        }
3497                    }
3498                });
3499        } else {
3500            for _ in 0..project_paths_to_open.len() {
3501                opened_items.push(None);
3502            }
3503        }
3504        assert!(opened_items.len() == project_paths_to_open.len());
3505
3506        let tasks =
3507            project_paths_to_open
3508                .into_iter()
3509                .enumerate()
3510                .map(|(i, (abs_path, project_path))| {
3511                    let workspace = workspace.clone();
3512                    cx.spawn(|mut cx| {
3513                        let fs = app_state.fs.clone();
3514                        async move {
3515                            let file_project_path = project_path?;
3516                            if fs.is_file(&abs_path).await {
3517                                Some((
3518                                    i,
3519                                    workspace
3520                                        .update(&mut cx, |workspace, cx| {
3521                                            workspace.open_path(file_project_path, None, true, cx)
3522                                        })
3523                                        .log_err()?
3524                                        .await,
3525                                ))
3526                            } else {
3527                                None
3528                            }
3529                        }
3530                    })
3531                });
3532
3533        let tasks = tasks.collect::<Vec<_>>();
3534
3535        let tasks = futures::future::join_all(tasks.into_iter());
3536        for maybe_opened_path in tasks.await.into_iter() {
3537            if let Some((i, path_open_result)) = maybe_opened_path {
3538                opened_items[i] = Some(path_open_result);
3539            }
3540        }
3541
3542        Ok(opened_items)
3543    })
3544}
3545
3546fn notify_if_database_failed(workspace: WindowHandle<Workspace>, cx: &mut AsyncAppContext) {
3547    const REPORT_ISSUE_URL: &str ="https://github.com/zed-industries/community/issues/new?assignees=&labels=defect%2Ctriage&template=2_bug_report.yml";
3548
3549    workspace
3550        .update(cx, |workspace, cx| {
3551            if (*db::ALL_FILE_DB_FAILED).load(std::sync::atomic::Ordering::Acquire) {
3552                workspace.show_notification_once(0, cx, |cx| {
3553                    cx.build_view(|_| {
3554                        MessageNotification::new("Failed to load the database file.")
3555                            .with_click_message("Click to let us know about this error")
3556                            .on_click(|cx| cx.open_url(REPORT_ISSUE_URL))
3557                    })
3558                });
3559            }
3560        })
3561        .log_err();
3562}
3563
3564impl FocusableView for Workspace {
3565    fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
3566        self.active_pane.focus_handle(cx)
3567    }
3568}
3569
3570#[derive(Clone, Render)]
3571struct DraggedDock(DockPosition);
3572
3573impl Render for Workspace {
3574    type Element = Div;
3575
3576    fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
3577        let mut context = KeyContext::default();
3578        context.add("Workspace");
3579
3580        let (ui_font, ui_font_size) = {
3581            let theme_settings = ThemeSettings::get_global(cx);
3582            (
3583                theme_settings.ui_font.family.clone(),
3584                theme_settings.ui_font_size.clone(),
3585            )
3586        };
3587
3588        cx.set_rem_size(ui_font_size);
3589
3590        self.actions(div(), cx)
3591            .key_context(context)
3592            .relative()
3593            .size_full()
3594            .flex()
3595            .flex_col()
3596            .font(ui_font)
3597            .gap_0()
3598            .justify_start()
3599            .items_start()
3600            .text_color(cx.theme().colors().text)
3601            .bg(cx.theme().colors().background)
3602            .border()
3603            .border_color(cx.theme().colors().border)
3604            .children(self.titlebar_item.clone())
3605            .child(
3606                div()
3607                    .id("workspace")
3608                    .relative()
3609                    .flex_1()
3610                    .w_full()
3611                    .flex()
3612                    .flex_col()
3613                    .overflow_hidden()
3614                    .border_t()
3615                    .border_b()
3616                    .border_color(cx.theme().colors().border)
3617                    .child(
3618                        canvas(cx.listener(|workspace, bounds, _| {
3619                            workspace.bounds = *bounds;
3620                        }))
3621                        .absolute()
3622                        .size_full(),
3623                    )
3624                    .on_drag_move(
3625                        cx.listener(|workspace, e: &DragMoveEvent<DraggedDock>, cx| {
3626                            match e.drag(cx).0 {
3627                                DockPosition::Left => {
3628                                    let size = workspace.bounds.left() + e.event.position.x;
3629                                    workspace.left_dock.update(cx, |left_dock, cx| {
3630                                        left_dock.resize_active_panel(Some(size.0), cx);
3631                                    });
3632                                }
3633                                DockPosition::Right => {
3634                                    let size = workspace.bounds.right() - e.event.position.x;
3635                                    workspace.right_dock.update(cx, |right_dock, cx| {
3636                                        right_dock.resize_active_panel(Some(size.0), cx);
3637                                    });
3638                                }
3639                                DockPosition::Bottom => {
3640                                    let size = workspace.bounds.bottom() - e.event.position.y;
3641                                    workspace.bottom_dock.update(cx, |bottom_dock, cx| {
3642                                        bottom_dock.resize_active_panel(Some(size.0), cx);
3643                                    });
3644                                }
3645                            }
3646                        }),
3647                    )
3648                    .child(self.modal_layer.clone())
3649                    .child(
3650                        div()
3651                            .flex()
3652                            .flex_row()
3653                            .h_full()
3654                            // Left Dock
3655                            .child(
3656                                div()
3657                                    .flex()
3658                                    .flex_none()
3659                                    .overflow_hidden()
3660                                    .child(self.left_dock.clone()),
3661                            )
3662                            // Panes
3663                            .child(
3664                                div()
3665                                    .flex()
3666                                    .flex_col()
3667                                    .flex_1()
3668                                    .overflow_hidden()
3669                                    .child(self.center.render(
3670                                        &self.project,
3671                                        &self.follower_states,
3672                                        self.active_call(),
3673                                        &self.active_pane,
3674                                        self.zoomed.as_ref(),
3675                                        &self.app_state,
3676                                        cx,
3677                                    ))
3678                                    .child(self.bottom_dock.clone()),
3679                            )
3680                            // Right Dock
3681                            .child(
3682                                div()
3683                                    .flex()
3684                                    .flex_none()
3685                                    .overflow_hidden()
3686                                    .child(self.right_dock.clone()),
3687                            ),
3688                    )
3689                    .children(self.render_notifications(cx)),
3690            )
3691            .child(self.status_bar.clone())
3692    }
3693}
3694
3695// impl View for Workspace {
3696
3697//     fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
3698//         let theme = theme::current(cx).clone();
3699//         Stack::new()
3700//             .with_child(
3701//                 Flex::column()
3702//                     .with_child(self.render_titlebar(&theme, cx))
3703//                     .with_child(
3704//                         Stack::new()
3705//                             .with_child({
3706//                                 let project = self.project.clone();
3707//                                 Flex::row()
3708//                                     .with_children(self.render_dock(DockPosition::Left, cx))
3709//                                     .with_child(
3710//                                         Flex::column()
3711//                                             .with_child(
3712//                                                 FlexItem::new(
3713//                                                     self.center.render(
3714//                                                         &project,
3715//                                                         &theme,
3716//                                                         &self.follower_states,
3717//                                                         self.active_call(),
3718//                                                         self.active_pane(),
3719//                                                         self.zoomed
3720//                                                             .as_ref()
3721//                                                             .and_then(|zoomed| zoomed.upgrade(cx))
3722//                                                             .as_ref(),
3723//                                                         &self.app_state,
3724//                                                         cx,
3725//                                                     ),
3726//                                                 )
3727//                                                 .flex(1., true),
3728//                                             )
3729//                                             .with_children(
3730//                                                 self.render_dock(DockPosition::Bottom, cx),
3731//                                             )
3732//                                             .flex(1., true),
3733//                                     )
3734//                                     .with_children(self.render_dock(DockPosition::Right, cx))
3735//                             })
3736//                             .with_child(Overlay::new(
3737//                                 Stack::new()
3738//                                     .with_children(self.zoomed.as_ref().and_then(|zoomed| {
3739//                                         enum ZoomBackground {}
3740//                                         let zoomed = zoomed.upgrade(cx)?;
3741
3742//                                         let mut foreground_style =
3743//                                             theme.workspace.zoomed_pane_foreground;
3744//                                         if let Some(zoomed_dock_position) = self.zoomed_position {
3745//                                             foreground_style =
3746//                                                 theme.workspace.zoomed_panel_foreground;
3747//                                             let margin = foreground_style.margin.top;
3748//                                             let border = foreground_style.border.top;
3749
3750//                                             // Only include a margin and border on the opposite side.
3751//                                             foreground_style.margin.top = 0.;
3752//                                             foreground_style.margin.left = 0.;
3753//                                             foreground_style.margin.bottom = 0.;
3754//                                             foreground_style.margin.right = 0.;
3755//                                             foreground_style.border.top = false;
3756//                                             foreground_style.border.left = false;
3757//                                             foreground_style.border.bottom = false;
3758//                                             foreground_style.border.right = false;
3759//                                             match zoomed_dock_position {
3760//                                                 DockPosition::Left => {
3761//                                                     foreground_style.margin.right = margin;
3762//                                                     foreground_style.border.right = border;
3763//                                                 }
3764//                                                 DockPosition::Right => {
3765//                                                     foreground_style.margin.left = margin;
3766//                                                     foreground_style.border.left = border;
3767//                                                 }
3768//                                                 DockPosition::Bottom => {
3769//                                                     foreground_style.margin.top = margin;
3770//                                                     foreground_style.border.top = border;
3771//                                                 }
3772//                                             }
3773//                                         }
3774
3775//                                         Some(
3776//                                             ChildView::new(&zoomed, cx)
3777//                                                 .contained()
3778//                                                 .with_style(foreground_style)
3779//                                                 .aligned()
3780//                                                 .contained()
3781//                                                 .with_style(theme.workspace.zoomed_background)
3782//                                                 .mouse::<ZoomBackground>(0)
3783//                                                 .capture_all()
3784//                                                 .on_down(
3785//                                                     MouseButton::Left,
3786//                                                     |_, this: &mut Self, cx| {
3787//                                                         this.zoom_out(cx);
3788//                                                     },
3789//                                                 ),
3790//                                         )
3791//                                     }))
3792//                                     .with_children(self.modal.as_ref().map(|modal| {
3793//                                         // Prevent clicks within the modal from falling
3794//                                         // through to the rest of the workspace.
3795//                                         enum ModalBackground {}
3796//                                         MouseEventHandler::new::<ModalBackground, _>(
3797//                                             0,
3798//                                             cx,
3799//                                             |_, cx| ChildView::new(modal.view.as_any(), cx),
3800//                                         )
3801//                                         .on_click(MouseButton::Left, |_, _, _| {})
3802//                                         .contained()
3803//                                         .with_style(theme.workspace.modal)
3804//                                         .aligned()
3805//                                         .top()
3806//                                     }))
3807//                                     .with_children(self.render_notifications(&theme.workspace, cx)),
3808//                             ))
3809//                             .provide_resize_bounds::<WorkspaceBounds>()
3810//                             .flex(1.0, true),
3811//                     )
3812//                     .with_child(ChildView::new(&self.status_bar, cx))
3813//                     .contained()
3814//                     .with_background_color(theme.workspace.background),
3815//             )
3816//             .with_children(DragAndDrop::render(cx))
3817//             .with_children(self.render_disconnected_overlay(cx))
3818//             .into_any_named("workspace")
3819//     }
3820
3821//     fn modifiers_changed(&mut self, e: &ModifiersChangedEvent, cx: &mut ViewContext<Self>) -> bool {
3822//         DragAndDrop::<Workspace>::update_modifiers(e.modifiers, cx)
3823//     }
3824// }
3825
3826impl WorkspaceStore {
3827    pub fn new(client: Arc<Client>, cx: &mut ModelContext<Self>) -> Self {
3828        Self {
3829            workspaces: Default::default(),
3830            followers: Default::default(),
3831            _subscriptions: vec![
3832                client.add_request_handler(cx.weak_model(), Self::handle_follow),
3833                client.add_message_handler(cx.weak_model(), Self::handle_unfollow),
3834                client.add_message_handler(cx.weak_model(), Self::handle_update_followers),
3835            ],
3836            client,
3837        }
3838    }
3839
3840    pub fn update_followers(
3841        &self,
3842        project_id: Option<u64>,
3843        update: proto::update_followers::Variant,
3844        cx: &AppContext,
3845    ) -> Option<()> {
3846        if !cx.has_global::<Model<ActiveCall>>() {
3847            return None;
3848        }
3849
3850        let room_id = ActiveCall::global(cx).read(cx).room()?.read(cx).id();
3851        let follower_ids: Vec<_> = self
3852            .followers
3853            .iter()
3854            .filter_map(|follower| {
3855                if follower.project_id == project_id || project_id.is_none() {
3856                    Some(follower.peer_id.into())
3857                } else {
3858                    None
3859                }
3860            })
3861            .collect();
3862        if follower_ids.is_empty() {
3863            return None;
3864        }
3865        self.client
3866            .send(proto::UpdateFollowers {
3867                room_id,
3868                project_id,
3869                follower_ids,
3870                variant: Some(update),
3871            })
3872            .log_err()
3873    }
3874
3875    pub async fn handle_follow(
3876        this: Model<Self>,
3877        envelope: TypedEnvelope<proto::Follow>,
3878        _: Arc<Client>,
3879        mut cx: AsyncAppContext,
3880    ) -> Result<proto::FollowResponse> {
3881        this.update(&mut cx, |this, cx| {
3882            let follower = Follower {
3883                project_id: envelope.payload.project_id,
3884                peer_id: envelope.original_sender_id()?,
3885            };
3886            let active_project = ActiveCall::global(cx).read(cx).location().cloned();
3887
3888            let mut response = proto::FollowResponse::default();
3889            for workspace in &this.workspaces {
3890                workspace
3891                    .update(cx, |workspace, cx| {
3892                        let handler_response = workspace.handle_follow(follower.project_id, cx);
3893                        if response.views.is_empty() {
3894                            response.views = handler_response.views;
3895                        } else {
3896                            response.views.extend_from_slice(&handler_response.views);
3897                        }
3898
3899                        if let Some(active_view_id) = handler_response.active_view_id.clone() {
3900                            if response.active_view_id.is_none()
3901                                || Some(workspace.project.downgrade()) == active_project
3902                            {
3903                                response.active_view_id = Some(active_view_id);
3904                            }
3905                        }
3906                    })
3907                    .ok();
3908            }
3909
3910            if let Err(ix) = this.followers.binary_search(&follower) {
3911                this.followers.insert(ix, follower);
3912            }
3913
3914            Ok(response)
3915        })?
3916    }
3917
3918    async fn handle_unfollow(
3919        model: Model<Self>,
3920        envelope: TypedEnvelope<proto::Unfollow>,
3921        _: Arc<Client>,
3922        mut cx: AsyncAppContext,
3923    ) -> Result<()> {
3924        model.update(&mut cx, |this, _| {
3925            let follower = Follower {
3926                project_id: envelope.payload.project_id,
3927                peer_id: envelope.original_sender_id()?,
3928            };
3929            if let Ok(ix) = this.followers.binary_search(&follower) {
3930                this.followers.remove(ix);
3931            }
3932            Ok(())
3933        })?
3934    }
3935
3936    async fn handle_update_followers(
3937        this: Model<Self>,
3938        envelope: TypedEnvelope<proto::UpdateFollowers>,
3939        _: Arc<Client>,
3940        mut cx: AsyncAppContext,
3941    ) -> Result<()> {
3942        let leader_id = envelope.original_sender_id()?;
3943        let update = envelope.payload;
3944
3945        this.update(&mut cx, |this, cx| {
3946            for workspace in &this.workspaces {
3947                workspace.update(cx, |workspace, cx| {
3948                    let project_id = workspace.project.read(cx).remote_id();
3949                    if update.project_id != project_id && update.project_id.is_some() {
3950                        return;
3951                    }
3952                    workspace.handle_update_followers(leader_id, update.clone(), cx);
3953                })?;
3954            }
3955            Ok(())
3956        })?
3957    }
3958}
3959
3960impl ViewId {
3961    pub(crate) fn from_proto(message: proto::ViewId) -> Result<Self> {
3962        Ok(Self {
3963            creator: message
3964                .creator
3965                .ok_or_else(|| anyhow!("creator is missing"))?,
3966            id: message.id,
3967        })
3968    }
3969
3970    pub(crate) fn to_proto(&self) -> proto::ViewId {
3971        proto::ViewId {
3972            creator: Some(self.creator),
3973            id: self.id,
3974        }
3975    }
3976}
3977
3978pub trait WorkspaceHandle {
3979    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath>;
3980}
3981
3982impl WorkspaceHandle for View<Workspace> {
3983    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath> {
3984        self.read(cx)
3985            .worktrees(cx)
3986            .flat_map(|worktree| {
3987                let worktree_id = worktree.read(cx).id();
3988                worktree.read(cx).files(true, 0).map(move |f| ProjectPath {
3989                    worktree_id,
3990                    path: f.path.clone(),
3991                })
3992            })
3993            .collect::<Vec<_>>()
3994    }
3995}
3996
3997impl std::fmt::Debug for OpenPaths {
3998    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3999        f.debug_struct("OpenPaths")
4000            .field("paths", &self.paths)
4001            .finish()
4002    }
4003}
4004
4005pub fn activate_workspace_for_project(
4006    cx: &mut AppContext,
4007    predicate: impl Fn(&Project, &AppContext) -> bool + Send + 'static,
4008) -> Option<WindowHandle<Workspace>> {
4009    for window in cx.windows() {
4010        let Some(workspace) = window.downcast::<Workspace>() else {
4011            continue;
4012        };
4013
4014        let predicate = workspace
4015            .update(cx, |workspace, cx| {
4016                let project = workspace.project.read(cx);
4017                if predicate(project, cx) {
4018                    cx.activate_window();
4019                    true
4020                } else {
4021                    false
4022                }
4023            })
4024            .log_err()
4025            .unwrap_or(false);
4026
4027        if predicate {
4028            return Some(workspace);
4029        }
4030    }
4031
4032    None
4033}
4034
4035pub async fn last_opened_workspace_paths() -> Option<WorkspaceLocation> {
4036    DB.last_workspace().await.log_err().flatten()
4037}
4038
4039async fn join_channel_internal(
4040    channel_id: u64,
4041    app_state: &Arc<AppState>,
4042    requesting_window: Option<WindowHandle<Workspace>>,
4043    active_call: &Model<ActiveCall>,
4044    cx: &mut AsyncAppContext,
4045) -> Result<bool> {
4046    let (should_prompt, open_room) = active_call.update(cx, |active_call, cx| {
4047        let Some(room) = active_call.room().map(|room| room.read(cx)) else {
4048            return (false, None);
4049        };
4050
4051        let already_in_channel = room.channel_id() == Some(channel_id);
4052        let should_prompt = room.is_sharing_project()
4053            && room.remote_participants().len() > 0
4054            && !already_in_channel;
4055        let open_room = if already_in_channel {
4056            active_call.room().cloned()
4057        } else {
4058            None
4059        };
4060        (should_prompt, open_room)
4061    })?;
4062
4063    if let Some(room) = open_room {
4064        let task = room.update(cx, |room, cx| {
4065            if let Some((project, host)) = room.most_active_project(cx) {
4066                return Some(join_remote_project(project, host, app_state.clone(), cx));
4067            }
4068
4069            None
4070        })?;
4071        if let Some(task) = task {
4072            task.await?;
4073        }
4074        return anyhow::Ok(true);
4075    }
4076
4077    if should_prompt {
4078        if let Some(workspace) = requesting_window {
4079            let answer  = workspace.update(cx, |_, cx| {
4080                cx.prompt(
4081                    PromptLevel::Warning,
4082                    "Leaving this call will unshare your current project.\nDo you want to switch channels?",
4083                    &["Yes, Join Channel", "Cancel"],
4084                )
4085            })?.await;
4086
4087            if answer == Ok(1) {
4088                return Ok(false);
4089            }
4090        } else {
4091            return Ok(false); // unreachable!() hopefully
4092        }
4093    }
4094
4095    let client = cx.update(|cx| active_call.read(cx).client())?;
4096
4097    let mut client_status = client.status();
4098
4099    // this loop will terminate within client::CONNECTION_TIMEOUT seconds.
4100    'outer: loop {
4101        let Some(status) = client_status.recv().await else {
4102            return Err(anyhow!("error connecting"));
4103        };
4104
4105        match status {
4106            Status::Connecting
4107            | Status::Authenticating
4108            | Status::Reconnecting
4109            | Status::Reauthenticating => continue,
4110            Status::Connected { .. } => break 'outer,
4111            Status::SignedOut => return Err(anyhow!("not signed in")),
4112            Status::UpgradeRequired => return Err(anyhow!("zed is out of date")),
4113            Status::ConnectionError | Status::ConnectionLost | Status::ReconnectionError { .. } => {
4114                return Err(anyhow!("zed is offline"))
4115            }
4116        }
4117    }
4118
4119    let room = active_call
4120        .update(cx, |active_call, cx| {
4121            active_call.join_channel(channel_id, cx)
4122        })?
4123        .await?;
4124
4125    let Some(room) = room else {
4126        return anyhow::Ok(true);
4127    };
4128
4129    room.update(cx, |room, _| room.room_update_completed())?
4130        .await;
4131
4132    let task = room.update(cx, |room, cx| {
4133        if let Some((project, host)) = room.most_active_project(cx) {
4134            return Some(join_remote_project(project, host, app_state.clone(), cx));
4135        }
4136
4137        None
4138    })?;
4139    if let Some(task) = task {
4140        task.await?;
4141        return anyhow::Ok(true);
4142    }
4143    anyhow::Ok(false)
4144}
4145
4146pub fn join_channel(
4147    channel_id: u64,
4148    app_state: Arc<AppState>,
4149    requesting_window: Option<WindowHandle<Workspace>>,
4150    cx: &mut AppContext,
4151) -> Task<Result<()>> {
4152    let active_call = ActiveCall::global(cx);
4153    cx.spawn(|mut cx| async move {
4154        let result = join_channel_internal(
4155            channel_id,
4156            &app_state,
4157            requesting_window,
4158            &active_call,
4159            &mut cx,
4160        )
4161        .await;
4162
4163        // join channel succeeded, and opened a window
4164        if matches!(result, Ok(true)) {
4165            return anyhow::Ok(());
4166        }
4167
4168        if requesting_window.is_some() {
4169            return anyhow::Ok(());
4170        }
4171
4172        // find an existing workspace to focus and show call controls
4173        let mut active_window = activate_any_workspace_window(&mut cx);
4174        if active_window.is_none() {
4175            // no open workspaces, make one to show the error in (blergh)
4176            cx.update(|cx| Workspace::new_local(vec![], app_state.clone(), requesting_window, cx))?
4177                .await?;
4178        }
4179
4180        active_window = activate_any_workspace_window(&mut cx);
4181        let Some(active_window) = active_window else {
4182            return anyhow::Ok(());
4183        };
4184
4185        if let Err(err) = result {
4186            active_window
4187                .update(&mut cx, |_, cx| {
4188                    cx.prompt(
4189                        PromptLevel::Critical,
4190                        &format!("Failed to join channel: {}", err),
4191                        &["Ok"],
4192                    )
4193                })?
4194                .await
4195                .ok();
4196        }
4197
4198        // return ok, we showed the error to the user.
4199        return anyhow::Ok(());
4200    })
4201}
4202
4203pub fn activate_any_workspace_window(cx: &mut AsyncAppContext) -> Option<AnyWindowHandle> {
4204    cx.update(|cx| {
4205        for window in cx.windows() {
4206            let is_workspace = window.downcast::<Workspace>().is_some();
4207            if is_workspace {
4208                window.update(cx, |_, cx| cx.activate_window()).ok();
4209                return Some(window);
4210            }
4211        }
4212        None
4213    })
4214    .ok()
4215    .flatten()
4216}
4217
4218#[allow(clippy::type_complexity)]
4219pub fn open_paths(
4220    abs_paths: &[PathBuf],
4221    app_state: &Arc<AppState>,
4222    requesting_window: Option<WindowHandle<Workspace>>,
4223    cx: &mut AppContext,
4224) -> Task<
4225    anyhow::Result<(
4226        WindowHandle<Workspace>,
4227        Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
4228    )>,
4229> {
4230    let app_state = app_state.clone();
4231    let abs_paths = abs_paths.to_vec();
4232    // Open paths in existing workspace if possible
4233    let existing = activate_workspace_for_project(cx, {
4234        let abs_paths = abs_paths.clone();
4235        move |project, cx| project.contains_paths(&abs_paths, cx)
4236    });
4237    cx.spawn(move |mut cx| async move {
4238        if let Some(existing) = existing {
4239            Ok((
4240                existing.clone(),
4241                existing
4242                    .update(&mut cx, |workspace, cx| {
4243                        workspace.open_paths(abs_paths, true, cx)
4244                    })?
4245                    .await,
4246            ))
4247        } else {
4248            cx.update(move |cx| {
4249                Workspace::new_local(abs_paths, app_state.clone(), requesting_window, cx)
4250            })?
4251            .await
4252        }
4253    })
4254}
4255
4256pub fn open_new(
4257    app_state: &Arc<AppState>,
4258    cx: &mut AppContext,
4259    init: impl FnOnce(&mut Workspace, &mut ViewContext<Workspace>) + 'static + Send,
4260) -> Task<()> {
4261    let task = Workspace::new_local(Vec::new(), app_state.clone(), None, cx);
4262    cx.spawn(|mut cx| async move {
4263        if let Some((workspace, opened_paths)) = task.await.log_err() {
4264            workspace
4265                .update(&mut cx, |workspace, cx| {
4266                    if opened_paths.is_empty() {
4267                        init(workspace, cx)
4268                    }
4269                })
4270                .log_err();
4271        }
4272    })
4273}
4274
4275pub fn create_and_open_local_file(
4276    path: &'static Path,
4277    cx: &mut ViewContext<Workspace>,
4278    default_content: impl 'static + Send + FnOnce() -> Rope,
4279) -> Task<Result<Box<dyn ItemHandle>>> {
4280    cx.spawn(|workspace, mut cx| async move {
4281        let fs = workspace.update(&mut cx, |workspace, _| workspace.app_state().fs.clone())?;
4282        if !fs.is_file(path).await {
4283            fs.create_file(path, Default::default()).await?;
4284            fs.save(path, &default_content(), Default::default())
4285                .await?;
4286        }
4287
4288        let mut items = workspace
4289            .update(&mut cx, |workspace, cx| {
4290                workspace.with_local_workspace(cx, |workspace, cx| {
4291                    workspace.open_paths(vec![path.to_path_buf()], false, cx)
4292                })
4293            })?
4294            .await?
4295            .await;
4296
4297        let item = items.pop().flatten();
4298        item.ok_or_else(|| anyhow!("path {path:?} is not a file"))?
4299    })
4300}
4301
4302pub fn join_remote_project(
4303    project_id: u64,
4304    follow_user_id: u64,
4305    app_state: Arc<AppState>,
4306    cx: &mut AppContext,
4307) -> Task<Result<()>> {
4308    let windows = cx.windows();
4309    cx.spawn(|mut cx| async move {
4310        let existing_workspace = windows.into_iter().find_map(|window| {
4311            window.downcast::<Workspace>().and_then(|window| {
4312                window
4313                    .update(&mut cx, |workspace, cx| {
4314                        if workspace.project().read(cx).remote_id() == Some(project_id) {
4315                            Some(window)
4316                        } else {
4317                            None
4318                        }
4319                    })
4320                    .unwrap_or(None)
4321            })
4322        });
4323
4324        let workspace = if let Some(existing_workspace) = existing_workspace {
4325            existing_workspace
4326        } else {
4327            let active_call = cx.update(|cx| ActiveCall::global(cx))?;
4328            let room = active_call
4329                .read_with(&cx, |call, _| call.room().cloned())?
4330                .ok_or_else(|| anyhow!("not in a call"))?;
4331            let project = room
4332                .update(&mut cx, |room, cx| {
4333                    room.join_project(
4334                        project_id,
4335                        app_state.languages.clone(),
4336                        app_state.fs.clone(),
4337                        cx,
4338                    )
4339                })?
4340                .await?;
4341
4342            let window_bounds_override = window_bounds_env_override(&cx);
4343            cx.update(|cx| {
4344                let options = (app_state.build_window_options)(window_bounds_override, None, cx);
4345                cx.open_window(options, |cx| {
4346                    cx.build_view(|cx| Workspace::new(0, project, app_state.clone(), cx))
4347                })
4348            })?
4349        };
4350
4351        workspace.update(&mut cx, |workspace, cx| {
4352            cx.activate(true);
4353            cx.activate_window();
4354
4355            if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
4356                let follow_peer_id = room
4357                    .read(cx)
4358                    .remote_participants()
4359                    .iter()
4360                    .find(|(_, participant)| participant.user.id == follow_user_id)
4361                    .map(|(_, p)| p.peer_id)
4362                    .or_else(|| {
4363                        // If we couldn't follow the given user, follow the host instead.
4364                        let collaborator = workspace
4365                            .project()
4366                            .read(cx)
4367                            .collaborators()
4368                            .values()
4369                            .find(|collaborator| collaborator.replica_id == 0)?;
4370                        Some(collaborator.peer_id)
4371                    });
4372
4373                if let Some(follow_peer_id) = follow_peer_id {
4374                    workspace
4375                        .follow(follow_peer_id, cx)
4376                        .map(|follow| follow.detach_and_log_err(cx));
4377                }
4378            }
4379        })?;
4380
4381        anyhow::Ok(())
4382    })
4383}
4384
4385pub fn restart(_: &Restart, cx: &mut AppContext) {
4386    let should_confirm = WorkspaceSettings::get_global(cx).confirm_quit;
4387    let mut workspace_windows = cx
4388        .windows()
4389        .into_iter()
4390        .filter_map(|window| window.downcast::<Workspace>())
4391        .collect::<Vec<_>>();
4392
4393    // If multiple windows have unsaved changes, and need a save prompt,
4394    // prompt in the active window before switching to a different window.
4395    workspace_windows.sort_by_key(|window| window.is_active(&cx) == Some(false));
4396
4397    let mut prompt = None;
4398    if let (true, Some(window)) = (should_confirm, workspace_windows.first()) {
4399        prompt = window
4400            .update(cx, |_, cx| {
4401                cx.prompt(
4402                    PromptLevel::Info,
4403                    "Are you sure you want to restart?",
4404                    &["Restart", "Cancel"],
4405                )
4406            })
4407            .ok();
4408    }
4409
4410    cx.spawn(|mut cx| async move {
4411        if let Some(prompt) = prompt {
4412            let answer = prompt.await?;
4413            if answer != 0 {
4414                return Ok(());
4415            }
4416        }
4417
4418        // If the user cancels any save prompt, then keep the app open.
4419        for window in workspace_windows {
4420            if let Ok(should_close) = window.update(&mut cx, |workspace, cx| {
4421                workspace.prepare_to_close(true, cx)
4422            }) {
4423                if !should_close.await? {
4424                    return Ok(());
4425                }
4426            }
4427        }
4428
4429        cx.update(|cx| cx.restart())
4430    })
4431    .detach_and_log_err(cx);
4432}
4433
4434fn parse_pixel_position_env_var(value: &str) -> Option<Point<GlobalPixels>> {
4435    let mut parts = value.split(',');
4436    let x: usize = parts.next()?.parse().ok()?;
4437    let y: usize = parts.next()?.parse().ok()?;
4438    Some(point((x as f64).into(), (y as f64).into()))
4439}
4440
4441fn parse_pixel_size_env_var(value: &str) -> Option<Size<GlobalPixels>> {
4442    let mut parts = value.split(',');
4443    let width: usize = parts.next()?.parse().ok()?;
4444    let height: usize = parts.next()?.parse().ok()?;
4445    Some(size((width as f64).into(), (height as f64).into()))
4446}
4447
4448#[cfg(test)]
4449mod tests {
4450    use std::{cell::RefCell, rc::Rc};
4451
4452    use super::*;
4453    use crate::item::{
4454        test::{TestItem, TestProjectItem},
4455        ItemEvent,
4456    };
4457    use fs::FakeFs;
4458    use gpui::TestAppContext;
4459    use project::{Project, ProjectEntryId};
4460    use serde_json::json;
4461    use settings::SettingsStore;
4462
4463    #[gpui::test]
4464    async fn test_tab_disambiguation(cx: &mut TestAppContext) {
4465        init_test(cx);
4466
4467        let fs = FakeFs::new(cx.executor());
4468        let project = Project::test(fs, [], cx).await;
4469        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
4470
4471        // Adding an item with no ambiguity renders the tab without detail.
4472        let item1 = cx.build_view(|cx| {
4473            let mut item = TestItem::new(cx);
4474            item.tab_descriptions = Some(vec!["c", "b1/c", "a/b1/c"]);
4475            item
4476        });
4477        workspace.update(cx, |workspace, cx| {
4478            workspace.add_item(Box::new(item1.clone()), cx);
4479        });
4480        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(0)));
4481
4482        // Adding an item that creates ambiguity increases the level of detail on
4483        // both tabs.
4484        let item2 = cx.build_view(|cx| {
4485            let mut item = TestItem::new(cx);
4486            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
4487            item
4488        });
4489        workspace.update(cx, |workspace, cx| {
4490            workspace.add_item(Box::new(item2.clone()), cx);
4491        });
4492        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4493        item2.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4494
4495        // Adding an item that creates ambiguity increases the level of detail only
4496        // on the ambiguous tabs. In this case, the ambiguity can't be resolved so
4497        // we stop at the highest detail available.
4498        let item3 = cx.build_view(|cx| {
4499            let mut item = TestItem::new(cx);
4500            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
4501            item
4502        });
4503        workspace.update(cx, |workspace, cx| {
4504            workspace.add_item(Box::new(item3.clone()), cx);
4505        });
4506        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4507        item2.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
4508        item3.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
4509    }
4510
4511    #[gpui::test]
4512    async fn test_tracking_active_path(cx: &mut TestAppContext) {
4513        init_test(cx);
4514
4515        let fs = FakeFs::new(cx.executor());
4516        fs.insert_tree(
4517            "/root1",
4518            json!({
4519                "one.txt": "",
4520                "two.txt": "",
4521            }),
4522        )
4523        .await;
4524        fs.insert_tree(
4525            "/root2",
4526            json!({
4527                "three.txt": "",
4528            }),
4529        )
4530        .await;
4531
4532        let project = Project::test(fs, ["root1".as_ref()], cx).await;
4533        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
4534        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
4535        let worktree_id = project.update(cx, |project, cx| {
4536            project.worktrees().next().unwrap().read(cx).id()
4537        });
4538
4539        let item1 = cx.build_view(|cx| {
4540            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "one.txt", cx)])
4541        });
4542        let item2 = cx.build_view(|cx| {
4543            TestItem::new(cx).with_project_items(&[TestProjectItem::new(2, "two.txt", cx)])
4544        });
4545
4546        // Add an item to an empty pane
4547        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item1), cx));
4548        project.update(cx, |project, cx| {
4549            assert_eq!(
4550                project.active_entry(),
4551                project
4552                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
4553                    .map(|e| e.id)
4554            );
4555        });
4556        assert_eq!(cx.window_title().as_deref(), Some("one.txt β€” root1"));
4557
4558        // Add a second item to a non-empty pane
4559        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item2), cx));
4560        assert_eq!(cx.window_title().as_deref(), Some("two.txt β€” root1"));
4561        project.update(cx, |project, cx| {
4562            assert_eq!(
4563                project.active_entry(),
4564                project
4565                    .entry_for_path(&(worktree_id, "two.txt").into(), cx)
4566                    .map(|e| e.id)
4567            );
4568        });
4569
4570        // Close the active item
4571        pane.update(cx, |pane, cx| {
4572            pane.close_active_item(&Default::default(), cx).unwrap()
4573        })
4574        .await
4575        .unwrap();
4576        assert_eq!(cx.window_title().as_deref(), Some("one.txt β€” root1"));
4577        project.update(cx, |project, cx| {
4578            assert_eq!(
4579                project.active_entry(),
4580                project
4581                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
4582                    .map(|e| e.id)
4583            );
4584        });
4585
4586        // Add a project folder
4587        project
4588            .update(cx, |project, cx| {
4589                project.find_or_create_local_worktree("/root2", true, cx)
4590            })
4591            .await
4592            .unwrap();
4593        assert_eq!(cx.window_title().as_deref(), Some("one.txt β€” root1, root2"));
4594
4595        // Remove a project folder
4596        project.update(cx, |project, cx| project.remove_worktree(worktree_id, cx));
4597        assert_eq!(cx.window_title().as_deref(), Some("one.txt β€” root2"));
4598    }
4599
4600    #[gpui::test]
4601    async fn test_close_window(cx: &mut TestAppContext) {
4602        init_test(cx);
4603
4604        let fs = FakeFs::new(cx.executor());
4605        fs.insert_tree("/root", json!({ "one": "" })).await;
4606
4607        let project = Project::test(fs, ["root".as_ref()], cx).await;
4608        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
4609
4610        // When there are no dirty items, there's nothing to do.
4611        let item1 = cx.build_view(|cx| TestItem::new(cx));
4612        workspace.update(cx, |w, cx| w.add_item(Box::new(item1.clone()), cx));
4613        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
4614        assert!(task.await.unwrap());
4615
4616        // When there are dirty untitled items, prompt to save each one. If the user
4617        // cancels any prompt, then abort.
4618        let item2 = cx.build_view(|cx| TestItem::new(cx).with_dirty(true));
4619        let item3 = cx.build_view(|cx| {
4620            TestItem::new(cx)
4621                .with_dirty(true)
4622                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4623        });
4624        workspace.update(cx, |w, cx| {
4625            w.add_item(Box::new(item2.clone()), cx);
4626            w.add_item(Box::new(item3.clone()), cx);
4627        });
4628        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
4629        cx.executor().run_until_parked();
4630        cx.simulate_prompt_answer(2); // cancel save all
4631        cx.executor().run_until_parked();
4632        cx.simulate_prompt_answer(2); // cancel save all
4633        cx.executor().run_until_parked();
4634        assert!(!cx.has_pending_prompt());
4635        assert!(!task.await.unwrap());
4636    }
4637
4638    #[gpui::test]
4639    async fn test_close_pane_items(cx: &mut TestAppContext) {
4640        init_test(cx);
4641
4642        let fs = FakeFs::new(cx.executor());
4643
4644        let project = Project::test(fs, None, cx).await;
4645        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
4646
4647        let item1 = cx.build_view(|cx| {
4648            TestItem::new(cx)
4649                .with_dirty(true)
4650                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4651        });
4652        let item2 = cx.build_view(|cx| {
4653            TestItem::new(cx)
4654                .with_dirty(true)
4655                .with_conflict(true)
4656                .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)])
4657        });
4658        let item3 = cx.build_view(|cx| {
4659            TestItem::new(cx)
4660                .with_dirty(true)
4661                .with_conflict(true)
4662                .with_project_items(&[TestProjectItem::new(3, "3.txt", cx)])
4663        });
4664        let item4 = cx.build_view(|cx| {
4665            TestItem::new(cx)
4666                .with_dirty(true)
4667                .with_project_items(&[TestProjectItem::new_untitled(cx)])
4668        });
4669        let pane = workspace.update(cx, |workspace, cx| {
4670            workspace.add_item(Box::new(item1.clone()), cx);
4671            workspace.add_item(Box::new(item2.clone()), cx);
4672            workspace.add_item(Box::new(item3.clone()), cx);
4673            workspace.add_item(Box::new(item4.clone()), cx);
4674            workspace.active_pane().clone()
4675        });
4676
4677        let close_items = pane.update(cx, |pane, cx| {
4678            pane.activate_item(1, true, true, cx);
4679            assert_eq!(pane.active_item().unwrap().item_id(), item2.item_id());
4680            let item1_id = item1.item_id();
4681            let item3_id = item3.item_id();
4682            let item4_id = item4.item_id();
4683            pane.close_items(cx, SaveIntent::Close, move |id| {
4684                [item1_id, item3_id, item4_id].contains(&id)
4685            })
4686        });
4687        cx.executor().run_until_parked();
4688
4689        assert!(cx.has_pending_prompt());
4690        // Ignore "Save all" prompt
4691        cx.simulate_prompt_answer(2);
4692        cx.executor().run_until_parked();
4693        // There's a prompt to save item 1.
4694        pane.update(cx, |pane, _| {
4695            assert_eq!(pane.items_len(), 4);
4696            assert_eq!(pane.active_item().unwrap().item_id(), item1.item_id());
4697        });
4698        // Confirm saving item 1.
4699        cx.simulate_prompt_answer(0);
4700        cx.executor().run_until_parked();
4701
4702        // Item 1 is saved. There's a prompt to save item 3.
4703        pane.update(cx, |pane, cx| {
4704            assert_eq!(item1.read(cx).save_count, 1);
4705            assert_eq!(item1.read(cx).save_as_count, 0);
4706            assert_eq!(item1.read(cx).reload_count, 0);
4707            assert_eq!(pane.items_len(), 3);
4708            assert_eq!(pane.active_item().unwrap().item_id(), item3.item_id());
4709        });
4710        assert!(cx.has_pending_prompt());
4711
4712        // Cancel saving item 3.
4713        cx.simulate_prompt_answer(1);
4714        cx.executor().run_until_parked();
4715
4716        // Item 3 is reloaded. There's a prompt to save item 4.
4717        pane.update(cx, |pane, cx| {
4718            assert_eq!(item3.read(cx).save_count, 0);
4719            assert_eq!(item3.read(cx).save_as_count, 0);
4720            assert_eq!(item3.read(cx).reload_count, 1);
4721            assert_eq!(pane.items_len(), 2);
4722            assert_eq!(pane.active_item().unwrap().item_id(), item4.item_id());
4723        });
4724        assert!(cx.has_pending_prompt());
4725
4726        // Confirm saving item 4.
4727        cx.simulate_prompt_answer(0);
4728        cx.executor().run_until_parked();
4729
4730        // There's a prompt for a path for item 4.
4731        cx.simulate_new_path_selection(|_| Some(Default::default()));
4732        close_items.await.unwrap();
4733
4734        // The requested items are closed.
4735        pane.update(cx, |pane, cx| {
4736            assert_eq!(item4.read(cx).save_count, 0);
4737            assert_eq!(item4.read(cx).save_as_count, 1);
4738            assert_eq!(item4.read(cx).reload_count, 0);
4739            assert_eq!(pane.items_len(), 1);
4740            assert_eq!(pane.active_item().unwrap().item_id(), item2.item_id());
4741        });
4742    }
4743
4744    #[gpui::test]
4745    async fn test_prompting_to_save_only_on_last_item_for_entry(cx: &mut TestAppContext) {
4746        init_test(cx);
4747
4748        let fs = FakeFs::new(cx.executor());
4749        let project = Project::test(fs, [], cx).await;
4750        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
4751
4752        // Create several workspace items with single project entries, and two
4753        // workspace items with multiple project entries.
4754        let single_entry_items = (0..=4)
4755            .map(|project_entry_id| {
4756                cx.build_view(|cx| {
4757                    TestItem::new(cx)
4758                        .with_dirty(true)
4759                        .with_project_items(&[TestProjectItem::new(
4760                            project_entry_id,
4761                            &format!("{project_entry_id}.txt"),
4762                            cx,
4763                        )])
4764                })
4765            })
4766            .collect::<Vec<_>>();
4767        let item_2_3 = cx.build_view(|cx| {
4768            TestItem::new(cx)
4769                .with_dirty(true)
4770                .with_singleton(false)
4771                .with_project_items(&[
4772                    single_entry_items[2].read(cx).project_items[0].clone(),
4773                    single_entry_items[3].read(cx).project_items[0].clone(),
4774                ])
4775        });
4776        let item_3_4 = cx.build_view(|cx| {
4777            TestItem::new(cx)
4778                .with_dirty(true)
4779                .with_singleton(false)
4780                .with_project_items(&[
4781                    single_entry_items[3].read(cx).project_items[0].clone(),
4782                    single_entry_items[4].read(cx).project_items[0].clone(),
4783                ])
4784        });
4785
4786        // Create two panes that contain the following project entries:
4787        //   left pane:
4788        //     multi-entry items:   (2, 3)
4789        //     single-entry items:  0, 1, 2, 3, 4
4790        //   right pane:
4791        //     single-entry items:  1
4792        //     multi-entry items:   (3, 4)
4793        let left_pane = workspace.update(cx, |workspace, cx| {
4794            let left_pane = workspace.active_pane().clone();
4795            workspace.add_item(Box::new(item_2_3.clone()), cx);
4796            for item in single_entry_items {
4797                workspace.add_item(Box::new(item), cx);
4798            }
4799            left_pane.update(cx, |pane, cx| {
4800                pane.activate_item(2, true, true, cx);
4801            });
4802
4803            let right_pane = workspace
4804                .split_and_clone(left_pane.clone(), SplitDirection::Right, cx)
4805                .unwrap();
4806
4807            right_pane.update(cx, |pane, cx| {
4808                pane.add_item(Box::new(item_3_4.clone()), true, true, None, cx);
4809            });
4810
4811            left_pane
4812        });
4813
4814        cx.focus_view(&left_pane);
4815
4816        // When closing all of the items in the left pane, we should be prompted twice:
4817        // once for project entry 0, and once for project entry 2. Project entries 1,
4818        // 3, and 4 are all still open in the other paten. After those two
4819        // prompts, the task should complete.
4820
4821        let close = left_pane.update(cx, |pane, cx| {
4822            pane.close_all_items(&CloseAllItems::default(), cx).unwrap()
4823        });
4824        cx.executor().run_until_parked();
4825
4826        // Discard "Save all" prompt
4827        cx.simulate_prompt_answer(2);
4828
4829        cx.executor().run_until_parked();
4830        left_pane.update(cx, |pane, cx| {
4831            assert_eq!(
4832                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
4833                &[ProjectEntryId::from_proto(0)]
4834            );
4835        });
4836        cx.simulate_prompt_answer(0);
4837
4838        cx.executor().run_until_parked();
4839        left_pane.update(cx, |pane, cx| {
4840            assert_eq!(
4841                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
4842                &[ProjectEntryId::from_proto(2)]
4843            );
4844        });
4845        cx.simulate_prompt_answer(0);
4846
4847        cx.executor().run_until_parked();
4848        close.await.unwrap();
4849        left_pane.update(cx, |pane, _| {
4850            assert_eq!(pane.items_len(), 0);
4851        });
4852    }
4853
4854    #[gpui::test]
4855    async fn test_autosave(cx: &mut gpui::TestAppContext) {
4856        init_test(cx);
4857
4858        let fs = FakeFs::new(cx.executor());
4859        let project = Project::test(fs, [], cx).await;
4860        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
4861        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
4862
4863        let item = cx.build_view(|cx| {
4864            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4865        });
4866        let item_id = item.entity_id();
4867        workspace.update(cx, |workspace, cx| {
4868            workspace.add_item(Box::new(item.clone()), cx);
4869        });
4870
4871        // Autosave on window change.
4872        item.update(cx, |item, cx| {
4873            cx.update_global(|settings: &mut SettingsStore, cx| {
4874                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4875                    settings.autosave = Some(AutosaveSetting::OnWindowChange);
4876                })
4877            });
4878            item.is_dirty = true;
4879        });
4880
4881        // Deactivating the window saves the file.
4882        cx.simulate_deactivation();
4883        cx.executor().run_until_parked();
4884        item.update(cx, |item, _| assert_eq!(item.save_count, 1));
4885
4886        // Autosave on focus change.
4887        item.update(cx, |item, cx| {
4888            cx.focus_self();
4889            cx.update_global(|settings: &mut SettingsStore, cx| {
4890                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4891                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
4892                })
4893            });
4894            item.is_dirty = true;
4895        });
4896
4897        // Blurring the item saves the file.
4898        item.update(cx, |_, cx| cx.blur());
4899        cx.executor().run_until_parked();
4900        item.update(cx, |item, _| assert_eq!(item.save_count, 2));
4901
4902        // Deactivating the window still saves the file.
4903        cx.simulate_activation();
4904        item.update(cx, |item, cx| {
4905            cx.focus_self();
4906            item.is_dirty = true;
4907        });
4908        cx.simulate_deactivation();
4909
4910        cx.executor().run_until_parked();
4911        item.update(cx, |item, _| assert_eq!(item.save_count, 3));
4912
4913        // Autosave after delay.
4914        item.update(cx, |item, cx| {
4915            cx.update_global(|settings: &mut SettingsStore, cx| {
4916                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4917                    settings.autosave = Some(AutosaveSetting::AfterDelay { milliseconds: 500 });
4918                })
4919            });
4920            item.is_dirty = true;
4921            cx.emit(ItemEvent::Edit);
4922        });
4923
4924        // Delay hasn't fully expired, so the file is still dirty and unsaved.
4925        cx.executor().advance_clock(Duration::from_millis(250));
4926        item.update(cx, |item, _| assert_eq!(item.save_count, 3));
4927
4928        // After delay expires, the file is saved.
4929        cx.executor().advance_clock(Duration::from_millis(250));
4930        item.update(cx, |item, _| assert_eq!(item.save_count, 4));
4931
4932        // Autosave on focus change, ensuring closing the tab counts as such.
4933        item.update(cx, |item, cx| {
4934            cx.update_global(|settings: &mut SettingsStore, cx| {
4935                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4936                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
4937                })
4938            });
4939            item.is_dirty = true;
4940        });
4941
4942        pane.update(cx, |pane, cx| {
4943            pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
4944        })
4945        .await
4946        .unwrap();
4947        assert!(!cx.has_pending_prompt());
4948        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
4949
4950        // Add the item again, ensuring autosave is prevented if the underlying file has been deleted.
4951        workspace.update(cx, |workspace, cx| {
4952            workspace.add_item(Box::new(item.clone()), cx);
4953        });
4954        item.update(cx, |item, cx| {
4955            item.project_items[0].update(cx, |item, _| {
4956                item.entry_id = None;
4957            });
4958            item.is_dirty = true;
4959            cx.blur();
4960        });
4961        cx.run_until_parked();
4962        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
4963
4964        // Ensure autosave is prevented for deleted files also when closing the buffer.
4965        let _close_items = pane.update(cx, |pane, cx| {
4966            pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
4967        });
4968        cx.run_until_parked();
4969        assert!(cx.has_pending_prompt());
4970        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
4971    }
4972
4973    #[gpui::test]
4974    async fn test_pane_navigation(cx: &mut gpui::TestAppContext) {
4975        init_test(cx);
4976
4977        let fs = FakeFs::new(cx.executor());
4978
4979        let project = Project::test(fs, [], cx).await;
4980        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
4981
4982        let item = cx.build_view(|cx| {
4983            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4984        });
4985        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
4986        let toolbar = pane.update(cx, |pane, _| pane.toolbar().clone());
4987        let toolbar_notify_count = Rc::new(RefCell::new(0));
4988
4989        workspace.update(cx, |workspace, cx| {
4990            workspace.add_item(Box::new(item.clone()), cx);
4991            let toolbar_notification_count = toolbar_notify_count.clone();
4992            cx.observe(&toolbar, move |_, _, _| {
4993                *toolbar_notification_count.borrow_mut() += 1
4994            })
4995            .detach();
4996        });
4997
4998        pane.update(cx, |pane, _| {
4999            assert!(!pane.can_navigate_backward());
5000            assert!(!pane.can_navigate_forward());
5001        });
5002
5003        item.update(cx, |item, cx| {
5004            item.set_state("one".to_string(), cx);
5005        });
5006
5007        // Toolbar must be notified to re-render the navigation buttons
5008        assert_eq!(*toolbar_notify_count.borrow(), 1);
5009
5010        pane.update(cx, |pane, _| {
5011            assert!(pane.can_navigate_backward());
5012            assert!(!pane.can_navigate_forward());
5013        });
5014
5015        workspace
5016            .update(cx, |workspace, cx| workspace.go_back(pane.downgrade(), cx))
5017            .await
5018            .unwrap();
5019
5020        assert_eq!(*toolbar_notify_count.borrow(), 2);
5021        pane.update(cx, |pane, _| {
5022            assert!(!pane.can_navigate_backward());
5023            assert!(pane.can_navigate_forward());
5024        });
5025    }
5026
5027    // #[gpui::test]
5028    // async fn test_toggle_docks_and_panels(cx: &mut gpui::TestAppContext) {
5029    //     init_test(cx);
5030    //     let fs = FakeFs::new(cx.executor());
5031
5032    //     let project = Project::test(fs, [], cx).await;
5033    //     let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
5034
5035    //     let panel = workspace.update(cx, |workspace, cx| {
5036    //         let panel = cx.build_view(|cx| TestPanel::new(DockPosition::Right, cx));
5037    //         workspace.add_panel(panel.clone(), cx);
5038
5039    //         workspace
5040    //             .right_dock()
5041    //             .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
5042
5043    //         panel
5044    //     });
5045
5046    //     let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
5047    //     pane.update(cx, |pane, cx| {
5048    //         let item = cx.build_view(|cx| TestItem::new(cx));
5049    //         pane.add_item(Box::new(item), true, true, None, cx);
5050    //     });
5051
5052    //     // Transfer focus from center to panel
5053    //     workspace.update(cx, |workspace, cx| {
5054    //         workspace.toggle_panel_focus::<TestPanel>(cx);
5055    //     });
5056
5057    //     workspace.update(cx, |workspace, cx| {
5058    //         assert!(workspace.right_dock().read(cx).is_open());
5059    //         assert!(!panel.is_zoomed(cx));
5060    //         assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
5061    //     });
5062
5063    //     // Transfer focus from panel to center
5064    //     workspace.update(cx, |workspace, cx| {
5065    //         workspace.toggle_panel_focus::<TestPanel>(cx);
5066    //     });
5067
5068    //     workspace.update(cx, |workspace, cx| {
5069    //         assert!(workspace.right_dock().read(cx).is_open());
5070    //         assert!(!panel.is_zoomed(cx));
5071    //         assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
5072    //     });
5073
5074    //     // Close the dock
5075    //     workspace.update(cx, |workspace, cx| {
5076    //         workspace.toggle_dock(DockPosition::Right, cx);
5077    //     });
5078
5079    //     workspace.update(cx, |workspace, cx| {
5080    //         assert!(!workspace.right_dock().read(cx).is_open());
5081    //         assert!(!panel.is_zoomed(cx));
5082    //         assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
5083    //     });
5084
5085    //     // Open the dock
5086    //     workspace.update(cx, |workspace, cx| {
5087    //         workspace.toggle_dock(DockPosition::Right, cx);
5088    //     });
5089
5090    //     workspace.update(cx, |workspace, cx| {
5091    //         assert!(workspace.right_dock().read(cx).is_open());
5092    //         assert!(!panel.is_zoomed(cx));
5093    //         assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
5094    //     });
5095
5096    //     // Focus and zoom panel
5097    //     panel.update(cx, |panel, cx| {
5098    //         cx.focus_self();
5099    //         panel.set_zoomed(true, cx)
5100    //     });
5101
5102    //     workspace.update(cx, |workspace, cx| {
5103    //         assert!(workspace.right_dock().read(cx).is_open());
5104    //         assert!(panel.is_zoomed(cx));
5105    //         assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
5106    //     });
5107
5108    //     // Transfer focus to the center closes the dock
5109    //     workspace.update(cx, |workspace, cx| {
5110    //         workspace.toggle_panel_focus::<TestPanel>(cx);
5111    //     });
5112
5113    //     workspace.update(cx, |workspace, cx| {
5114    //         assert!(!workspace.right_dock().read(cx).is_open());
5115    //         assert!(panel.is_zoomed(cx));
5116    //         assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
5117    //     });
5118
5119    //     // Transferring focus back to the panel keeps it zoomed
5120    //     workspace.update(cx, |workspace, cx| {
5121    //         workspace.toggle_panel_focus::<TestPanel>(cx);
5122    //     });
5123
5124    //     workspace.update(cx, |workspace, cx| {
5125    //         assert!(workspace.right_dock().read(cx).is_open());
5126    //         assert!(panel.is_zoomed(cx));
5127    //         assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
5128    //     });
5129
5130    //     // Close the dock while it is zoomed
5131    //     workspace.update(cx, |workspace, cx| {
5132    //         workspace.toggle_dock(DockPosition::Right, cx)
5133    //     });
5134
5135    //     workspace.update(cx, |workspace, cx| {
5136    //         assert!(!workspace.right_dock().read(cx).is_open());
5137    //         assert!(panel.is_zoomed(cx));
5138    //         assert!(workspace.zoomed.is_none());
5139    //         assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
5140    //     });
5141
5142    //     // Opening the dock, when it's zoomed, retains focus
5143    //     workspace.update(cx, |workspace, cx| {
5144    //         workspace.toggle_dock(DockPosition::Right, cx)
5145    //     });
5146
5147    //     workspace.update(cx, |workspace, cx| {
5148    //         assert!(workspace.right_dock().read(cx).is_open());
5149    //         assert!(panel.is_zoomed(cx));
5150    //         assert!(workspace.zoomed.is_some());
5151    //         assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
5152    //     });
5153
5154    //     // Unzoom and close the panel, zoom the active pane.
5155    //     panel.update(cx, |panel, cx| panel.set_zoomed(false, cx));
5156    //     workspace.update(cx, |workspace, cx| {
5157    //         workspace.toggle_dock(DockPosition::Right, cx)
5158    //     });
5159    //     pane.update(cx, |pane, cx| pane.toggle_zoom(&Default::default(), cx));
5160
5161    //     // Opening a dock unzooms the pane.
5162    //     workspace.update(cx, |workspace, cx| {
5163    //         workspace.toggle_dock(DockPosition::Right, cx)
5164    //     });
5165    //     workspace.update(cx, |workspace, cx| {
5166    //         let pane = pane.read(cx);
5167    //         assert!(!pane.is_zoomed());
5168    //         assert!(!pane.focus_handle(cx).is_focused(cx));
5169    //         assert!(workspace.right_dock().read(cx).is_open());
5170    //         assert!(workspace.zoomed.is_none());
5171    //     });
5172    // }
5173
5174    // #[gpui::test]
5175    // async fn test_panels(cx: &mut gpui::TestAppContext) {
5176    //     init_test(cx);
5177    //     let fs = FakeFs::new(cx.executor());
5178
5179    //     let project = Project::test(fs, [], cx).await;
5180    //     let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
5181
5182    //     let (panel_1, panel_2) = workspace.update(cx, |workspace, cx| {
5183    //         // Add panel_1 on the left, panel_2 on the right.
5184    //         let panel_1 = cx.build_view(|cx| TestPanel::new(DockPosition::Left, cx));
5185    //         workspace.add_panel(panel_1.clone(), cx);
5186    //         workspace
5187    //             .left_dock()
5188    //             .update(cx, |left_dock, cx| left_dock.set_open(true, cx));
5189    //         let panel_2 = cx.build_view(|cx| TestPanel::new(DockPosition::Right, cx));
5190    //         workspace.add_panel(panel_2.clone(), cx);
5191    //         workspace
5192    //             .right_dock()
5193    //             .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
5194
5195    //         let left_dock = workspace.left_dock();
5196    //         assert_eq!(
5197    //             left_dock.read(cx).visible_panel().unwrap().panel_id(),
5198    //             panel_1.panel_id()
5199    //         );
5200    //         assert_eq!(
5201    //             left_dock.read(cx).active_panel_size(cx).unwrap(),
5202    //             panel_1.size(cx)
5203    //         );
5204
5205    //         left_dock.update(cx, |left_dock, cx| {
5206    //             left_dock.resize_active_panel(Some(1337.), cx)
5207    //         });
5208    //         assert_eq!(
5209    //             workspace
5210    //                 .right_dock()
5211    //                 .read(cx)
5212    //                 .visible_panel()
5213    //                 .unwrap()
5214    //                 .panel_id(),
5215    //             panel_2.panel_id(),
5216    //         );
5217
5218    //         (panel_1, panel_2)
5219    //     });
5220
5221    //     // Move panel_1 to the right
5222    //     panel_1.update(cx, |panel_1, cx| {
5223    //         panel_1.set_position(DockPosition::Right, cx)
5224    //     });
5225
5226    //     workspace.update(cx, |workspace, cx| {
5227    //         // Since panel_1 was visible on the left, it should now be visible now that it's been moved to the right.
5228    //         // Since it was the only panel on the left, the left dock should now be closed.
5229    //         assert!(!workspace.left_dock().read(cx).is_open());
5230    //         assert!(workspace.left_dock().read(cx).visible_panel().is_none());
5231    //         let right_dock = workspace.right_dock();
5232    //         assert_eq!(
5233    //             right_dock.read(cx).visible_panel().unwrap().panel_id(),
5234    //             panel_1.panel_id()
5235    //         );
5236    //         assert_eq!(right_dock.read(cx).active_panel_size(cx).unwrap(), 1337.);
5237
5238    //         // Now we move panel_2Β to the left
5239    //         panel_2.set_position(DockPosition::Left, cx);
5240    //     });
5241
5242    //     workspace.update(cx, |workspace, cx| {
5243    //         // Since panel_2 was not visible on the right, we don't open the left dock.
5244    //         assert!(!workspace.left_dock().read(cx).is_open());
5245    //         // And the right dock is unaffected in it's displaying of panel_1
5246    //         assert!(workspace.right_dock().read(cx).is_open());
5247    //         assert_eq!(
5248    //             workspace
5249    //                 .right_dock()
5250    //                 .read(cx)
5251    //                 .visible_panel()
5252    //                 .unwrap()
5253    //                 .panel_id(),
5254    //             panel_1.panel_id(),
5255    //         );
5256    //     });
5257
5258    //     // Move panel_1 back to the left
5259    //     panel_1.update(cx, |panel_1, cx| {
5260    //         panel_1.set_position(DockPosition::Left, cx)
5261    //     });
5262
5263    //     workspace.update(cx, |workspace, cx| {
5264    //         // Since panel_1 was visible on the right, we open the left dock and make panel_1 active.
5265    //         let left_dock = workspace.left_dock();
5266    //         assert!(left_dock.read(cx).is_open());
5267    //         assert_eq!(
5268    //             left_dock.read(cx).visible_panel().unwrap().panel_id(),
5269    //             panel_1.panel_id()
5270    //         );
5271    //         assert_eq!(left_dock.read(cx).active_panel_size(cx).unwrap(), 1337.);
5272    //         // And right the dock should be closed as it no longer has any panels.
5273    //         assert!(!workspace.right_dock().read(cx).is_open());
5274
5275    //         // Now we move panel_1 to the bottom
5276    //         panel_1.set_position(DockPosition::Bottom, cx);
5277    //     });
5278
5279    //     workspace.update(cx, |workspace, cx| {
5280    //         // Since panel_1 was visible on the left, we close the left dock.
5281    //         assert!(!workspace.left_dock().read(cx).is_open());
5282    //         // The bottom dock is sized based on the panel's default size,
5283    //         // since the panel orientation changed from vertical to horizontal.
5284    //         let bottom_dock = workspace.bottom_dock();
5285    //         assert_eq!(
5286    //             bottom_dock.read(cx).active_panel_size(cx).unwrap(),
5287    //             panel_1.size(cx),
5288    //         );
5289    //         // Close bottom dock and move panel_1 back to the left.
5290    //         bottom_dock.update(cx, |bottom_dock, cx| bottom_dock.set_open(false, cx));
5291    //         panel_1.set_position(DockPosition::Left, cx);
5292    //     });
5293
5294    //     // Emit activated event on panel 1
5295    //     panel_1.update(cx, |_, cx| cx.emit(PanelEvent::Activate));
5296
5297    //     // Now the left dock is open and panel_1 is active and focused.
5298    //     workspace.update(cx, |workspace, cx| {
5299    //         let left_dock = workspace.left_dock();
5300    //         assert!(left_dock.read(cx).is_open());
5301    //         assert_eq!(
5302    //             left_dock.read(cx).visible_panel().unwrap().panel_id(),
5303    //             panel_1.panel_id(),
5304    //         );
5305    //         assert!(panel_1.focus_handle(cx).is_focused(cx));
5306    //     });
5307
5308    //     // Emit closed event on panel 2, which is not active
5309    //     panel_2.update(cx, |_, cx| cx.emit(PanelEvent::Close));
5310
5311    //     // Wo don't close the left dock, because panel_2 wasn't the active panel
5312    //     workspace.update(cx, |workspace, cx| {
5313    //         let left_dock = workspace.left_dock();
5314    //         assert!(left_dock.read(cx).is_open());
5315    //         assert_eq!(
5316    //             left_dock.read(cx).visible_panel().unwrap().panel_id(),
5317    //             panel_1.panel_id(),
5318    //         );
5319    //     });
5320
5321    //     // Emitting a ZoomIn event shows the panel as zoomed.
5322    //     panel_1.update(cx, |_, cx| cx.emit(PanelEvent::ZoomIn));
5323    //     workspace.update(cx, |workspace, _| {
5324    //         assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
5325    //         assert_eq!(workspace.zoomed_position, Some(DockPosition::Left));
5326    //     });
5327
5328    //     // Move panel to another dock while it is zoomed
5329    //     panel_1.update(cx, |panel, cx| panel.set_position(DockPosition::Right, cx));
5330    //     workspace.update(cx, |workspace, _| {
5331    //         assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
5332
5333    //         assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5334    //     });
5335
5336    //     // If focus is transferred to another view that's not a panel or another pane, we still show
5337    //     // the panel as zoomed.
5338    //     let other_focus_handle = cx.update(|cx| cx.focus_handle());
5339    //     cx.update(|cx| cx.focus(&other_focus_handle));
5340    //     workspace.update(cx, |workspace, _| {
5341    //         assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
5342    //         assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5343    //     });
5344
5345    //     // If focus is transferred elsewhere in the workspace, the panel is no longer zoomed.
5346    //     workspace.update(cx, |_, cx| cx.focus_self());
5347    //     workspace.update(cx, |workspace, _| {
5348    //         assert_eq!(workspace.zoomed, None);
5349    //         assert_eq!(workspace.zoomed_position, None);
5350    //     });
5351
5352    //     // If focus is transferred again to another view that's not a panel or a pane, we won't
5353    //     // show the panel as zoomed because it wasn't zoomed before.
5354    //     cx.update(|cx| cx.focus(&other_focus_handle));
5355    //     workspace.update(cx, |workspace, _| {
5356    //         assert_eq!(workspace.zoomed, None);
5357    //         assert_eq!(workspace.zoomed_position, None);
5358    //     });
5359
5360    //     // When focus is transferred back to the panel, it is zoomed again.
5361    //     panel_1.update(cx, |_, cx| cx.focus_self());
5362    //     workspace.update(cx, |workspace, _| {
5363    //         assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
5364    //         assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5365    //     });
5366
5367    //     // Emitting a ZoomOut event unzooms the panel.
5368    //     panel_1.update(cx, |_, cx| cx.emit(PanelEvent::ZoomOut));
5369    //     workspace.update(cx, |workspace, _| {
5370    //         assert_eq!(workspace.zoomed, None);
5371    //         assert_eq!(workspace.zoomed_position, None);
5372    //     });
5373
5374    //     // Emit closed event on panel 1, which is active
5375    //     panel_1.update(cx, |_, cx| cx.emit(PanelEvent::Close));
5376
5377    //     // Now the left dock is closed, because panel_1 was the active panel
5378    //     workspace.update(cx, |workspace, cx| {
5379    //         let right_dock = workspace.right_dock();
5380    //         assert!(!right_dock.read(cx).is_open());
5381    //     });
5382    // }
5383
5384    pub fn init_test(cx: &mut TestAppContext) {
5385        cx.update(|cx| {
5386            let settings_store = SettingsStore::test(cx);
5387            cx.set_global(settings_store);
5388            theme::init(theme::LoadThemes::JustBase, cx);
5389            language::init(cx);
5390            crate::init_settings(cx);
5391            Project::init_settings(cx);
5392        });
5393    }
5394}