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            .on_action(cx.listener(Workspace::activate_pane_at_index))
3306            .on_action(
3307                cx.listener(|workspace: &mut Workspace, _: &ReopenClosedItem, cx| {
3308                    workspace.reopen_closed_item(cx).detach();
3309                }),
3310            )
3311    }
3312
3313    #[cfg(any(test, feature = "test-support"))]
3314    pub fn test_new(project: Model<Project>, cx: &mut ViewContext<Self>) -> Self {
3315        use node_runtime::FakeNodeRuntime;
3316
3317        let client = project.read(cx).client();
3318        let user_store = project.read(cx).user_store();
3319
3320        let workspace_store = cx.build_model(|cx| WorkspaceStore::new(client.clone(), cx));
3321        let app_state = Arc::new(AppState {
3322            languages: project.read(cx).languages().clone(),
3323            workspace_store,
3324            client,
3325            user_store,
3326            fs: project.read(cx).fs().clone(),
3327            build_window_options: |_, _, _| Default::default(),
3328            node_runtime: FakeNodeRuntime::new(),
3329        });
3330        let workspace = Self::new(0, project, app_state, cx);
3331        workspace.active_pane.update(cx, |pane, cx| pane.focus(cx));
3332        workspace
3333    }
3334
3335    //     fn render_dock(&self, position: DockPosition, cx: &WindowContext) -> Option<AnyElement<Self>> {
3336    //         let dock = match position {
3337    //             DockPosition::Left => &self.left_dock,
3338    //             DockPosition::Right => &self.right_dock,
3339    //             DockPosition::Bottom => &self.bottom_dock,
3340    //         };
3341    //         let active_panel = dock.read(cx).visible_panel()?;
3342    //         let element = if Some(active_panel.id()) == self.zoomed.as_ref().map(|zoomed| zoomed.id()) {
3343    //             dock.read(cx).render_placeholder(cx)
3344    //         } else {
3345    //             ChildView::new(dock, cx).into_any()
3346    //         };
3347
3348    //         Some(
3349    //             element
3350    //                 .constrained()
3351    //                 .dynamically(move |constraint, _, cx| match position {
3352    //                     DockPosition::Left | DockPosition::Right => SizeConstraint::new(
3353    //                         Vector2F::new(20., constraint.min.y()),
3354    //                         Vector2F::new(cx.window_size().x() * 0.8, constraint.max.y()),
3355    //                     ),
3356    //                     DockPosition::Bottom => SizeConstraint::new(
3357    //                         Vector2F::new(constraint.min.x(), 20.),
3358    //                         Vector2F::new(constraint.max.x(), cx.window_size().y() * 0.8),
3359    //                     ),
3360    //                 })
3361    //                 .into_any(),
3362    //         )
3363    //     }
3364    // }
3365    pub fn register_action<A: Action>(
3366        &mut self,
3367        callback: impl Fn(&mut Self, &A, &mut ViewContext<Self>) + 'static,
3368    ) -> &mut Self {
3369        let callback = Arc::new(callback);
3370
3371        self.workspace_actions.push(Box::new(move |div, cx| {
3372            let callback = callback.clone();
3373            div.on_action(
3374                cx.listener(move |workspace, event, cx| (callback.clone())(workspace, event, cx)),
3375            )
3376        }));
3377        self
3378    }
3379
3380    fn add_workspace_actions_listeners(&self, div: Div, cx: &mut ViewContext<Self>) -> Div {
3381        let mut div = div
3382            .on_action(cx.listener(Self::close_inactive_items_and_panes))
3383            .on_action(cx.listener(Self::close_all_items_and_panes))
3384            .on_action(cx.listener(Self::add_folder_to_project))
3385            .on_action(cx.listener(Self::save_all))
3386            .on_action(cx.listener(Self::open));
3387        for action in self.workspace_actions.iter() {
3388            div = (action)(div, cx)
3389        }
3390        div
3391    }
3392
3393    pub fn active_modal<V: ManagedView + 'static>(
3394        &mut self,
3395        cx: &ViewContext<Self>,
3396    ) -> Option<View<V>> {
3397        self.modal_layer.read(cx).active_modal()
3398    }
3399
3400    pub fn toggle_modal<V: ModalView, B>(&mut self, cx: &mut ViewContext<Self>, build: B)
3401    where
3402        B: FnOnce(&mut ViewContext<V>) -> V,
3403    {
3404        self.modal_layer
3405            .update(cx, |modal_layer, cx| modal_layer.toggle_modal(cx, build))
3406    }
3407}
3408
3409fn window_bounds_env_override(cx: &AsyncAppContext) -> Option<WindowBounds> {
3410    let display_origin = cx
3411        .update(|cx| Some(cx.displays().first()?.bounds().origin))
3412        .ok()??;
3413    ZED_WINDOW_POSITION
3414        .zip(*ZED_WINDOW_SIZE)
3415        .map(|(position, size)| {
3416            WindowBounds::Fixed(Bounds {
3417                origin: display_origin + position,
3418                size,
3419            })
3420        })
3421}
3422
3423fn open_items(
3424    serialized_workspace: Option<SerializedWorkspace>,
3425    mut project_paths_to_open: Vec<(PathBuf, Option<ProjectPath>)>,
3426    app_state: Arc<AppState>,
3427    cx: &mut ViewContext<Workspace>,
3428) -> impl 'static + Future<Output = Result<Vec<Option<Result<Box<dyn ItemHandle>>>>>> {
3429    let restored_items = serialized_workspace.map(|serialized_workspace| {
3430        Workspace::load_workspace(
3431            serialized_workspace,
3432            project_paths_to_open
3433                .iter()
3434                .map(|(_, project_path)| project_path)
3435                .cloned()
3436                .collect(),
3437            cx,
3438        )
3439    });
3440
3441    cx.spawn(|workspace, mut cx| async move {
3442        let mut opened_items = Vec::with_capacity(project_paths_to_open.len());
3443
3444        if let Some(restored_items) = restored_items {
3445            let restored_items = restored_items.await?;
3446
3447            let restored_project_paths = restored_items
3448                .iter()
3449                .filter_map(|item| {
3450                    cx.update(|_, cx| item.as_ref()?.project_path(cx))
3451                        .ok()
3452                        .flatten()
3453                })
3454                .collect::<HashSet<_>>();
3455
3456            for restored_item in restored_items {
3457                opened_items.push(restored_item.map(Ok));
3458            }
3459
3460            project_paths_to_open
3461                .iter_mut()
3462                .for_each(|(_, project_path)| {
3463                    if let Some(project_path_to_open) = project_path {
3464                        if restored_project_paths.contains(project_path_to_open) {
3465                            *project_path = None;
3466                        }
3467                    }
3468                });
3469        } else {
3470            for _ in 0..project_paths_to_open.len() {
3471                opened_items.push(None);
3472            }
3473        }
3474        assert!(opened_items.len() == project_paths_to_open.len());
3475
3476        let tasks =
3477            project_paths_to_open
3478                .into_iter()
3479                .enumerate()
3480                .map(|(i, (abs_path, project_path))| {
3481                    let workspace = workspace.clone();
3482                    cx.spawn(|mut cx| {
3483                        let fs = app_state.fs.clone();
3484                        async move {
3485                            let file_project_path = project_path?;
3486                            if fs.is_file(&abs_path).await {
3487                                Some((
3488                                    i,
3489                                    workspace
3490                                        .update(&mut cx, |workspace, cx| {
3491                                            workspace.open_path(file_project_path, None, true, cx)
3492                                        })
3493                                        .log_err()?
3494                                        .await,
3495                                ))
3496                            } else {
3497                                None
3498                            }
3499                        }
3500                    })
3501                });
3502
3503        let tasks = tasks.collect::<Vec<_>>();
3504
3505        let tasks = futures::future::join_all(tasks.into_iter());
3506        for maybe_opened_path in tasks.await.into_iter() {
3507            if let Some((i, path_open_result)) = maybe_opened_path {
3508                opened_items[i] = Some(path_open_result);
3509            }
3510        }
3511
3512        Ok(opened_items)
3513    })
3514}
3515
3516fn notify_if_database_failed(workspace: WindowHandle<Workspace>, cx: &mut AsyncAppContext) {
3517    const REPORT_ISSUE_URL: &str ="https://github.com/zed-industries/community/issues/new?assignees=&labels=defect%2Ctriage&template=2_bug_report.yml";
3518
3519    workspace
3520        .update(cx, |workspace, cx| {
3521            if (*db::ALL_FILE_DB_FAILED).load(std::sync::atomic::Ordering::Acquire) {
3522                workspace.show_notification_once(0, cx, |cx| {
3523                    cx.build_view(|_| {
3524                        MessageNotification::new("Failed to load the database file.")
3525                            .with_click_message("Click to let us know about this error")
3526                            .on_click(|cx| cx.open_url(REPORT_ISSUE_URL))
3527                    })
3528                });
3529            }
3530        })
3531        .log_err();
3532}
3533
3534impl FocusableView for Workspace {
3535    fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
3536        self.active_pane.focus_handle(cx)
3537    }
3538}
3539
3540#[derive(Clone, Render)]
3541struct DraggedDock(DockPosition);
3542
3543impl Render for Workspace {
3544    type Element = Div;
3545
3546    fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
3547        let mut context = KeyContext::default();
3548        context.add("Workspace");
3549
3550        let (ui_font, ui_font_size) = {
3551            let theme_settings = ThemeSettings::get_global(cx);
3552            (
3553                theme_settings.ui_font.family.clone(),
3554                theme_settings.ui_font_size.clone(),
3555            )
3556        };
3557
3558        cx.set_rem_size(ui_font_size);
3559
3560        self.actions(div(), cx)
3561            .key_context(context)
3562            .relative()
3563            .size_full()
3564            .flex()
3565            .flex_col()
3566            .font(ui_font)
3567            .gap_0()
3568            .justify_start()
3569            .items_start()
3570            .text_color(cx.theme().colors().text)
3571            .bg(cx.theme().colors().background)
3572            .border()
3573            .border_color(cx.theme().colors().border)
3574            .children(self.titlebar_item.clone())
3575            .child(
3576                div()
3577                    .id("workspace")
3578                    .relative()
3579                    .flex_1()
3580                    .w_full()
3581                    .flex()
3582                    .flex_col()
3583                    .overflow_hidden()
3584                    .border_t()
3585                    .border_b()
3586                    .border_color(cx.theme().colors().border)
3587                    .child(
3588                        canvas(cx.listener(|workspace, bounds, _| {
3589                            workspace.bounds = *bounds;
3590                        }))
3591                        .absolute()
3592                        .size_full(),
3593                    )
3594                    .on_drag_move(
3595                        cx.listener(|workspace, e: &DragMoveEvent<DraggedDock>, cx| {
3596                            match e.drag(cx).0 {
3597                                DockPosition::Left => {
3598                                    let size = workspace.bounds.left() + e.event.position.x;
3599                                    workspace.left_dock.update(cx, |left_dock, cx| {
3600                                        left_dock.resize_active_panel(Some(size.0), cx);
3601                                    });
3602                                }
3603                                DockPosition::Right => {
3604                                    let size = workspace.bounds.right() - e.event.position.x;
3605                                    workspace.right_dock.update(cx, |right_dock, cx| {
3606                                        right_dock.resize_active_panel(Some(size.0), cx);
3607                                    });
3608                                }
3609                                DockPosition::Bottom => {
3610                                    let size = workspace.bounds.bottom() - e.event.position.y;
3611                                    workspace.bottom_dock.update(cx, |bottom_dock, cx| {
3612                                        bottom_dock.resize_active_panel(Some(size.0), cx);
3613                                    });
3614                                }
3615                            }
3616                        }),
3617                    )
3618                    .child(self.modal_layer.clone())
3619                    .child(
3620                        div()
3621                            .flex()
3622                            .flex_row()
3623                            .h_full()
3624                            // Left Dock
3625                            .child(
3626                                div()
3627                                    .flex()
3628                                    .flex_none()
3629                                    .overflow_hidden()
3630                                    .child(self.left_dock.clone()),
3631                            )
3632                            // Panes
3633                            .child(
3634                                div()
3635                                    .flex()
3636                                    .flex_col()
3637                                    .flex_1()
3638                                    .overflow_hidden()
3639                                    .child(self.center.render(
3640                                        &self.project,
3641                                        &self.follower_states,
3642                                        self.active_call(),
3643                                        &self.active_pane,
3644                                        self.zoomed.as_ref(),
3645                                        &self.app_state,
3646                                        cx,
3647                                    ))
3648                                    .child(self.bottom_dock.clone()),
3649                            )
3650                            // Right Dock
3651                            .child(
3652                                div()
3653                                    .flex()
3654                                    .flex_none()
3655                                    .overflow_hidden()
3656                                    .child(self.right_dock.clone()),
3657                            ),
3658                    )
3659                    .children(self.render_notifications(cx)),
3660            )
3661            .child(self.status_bar.clone())
3662    }
3663}
3664
3665// impl View for Workspace {
3666
3667//     fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
3668//         let theme = theme::current(cx).clone();
3669//         Stack::new()
3670//             .with_child(
3671//                 Flex::column()
3672//                     .with_child(self.render_titlebar(&theme, cx))
3673//                     .with_child(
3674//                         Stack::new()
3675//                             .with_child({
3676//                                 let project = self.project.clone();
3677//                                 Flex::row()
3678//                                     .with_children(self.render_dock(DockPosition::Left, cx))
3679//                                     .with_child(
3680//                                         Flex::column()
3681//                                             .with_child(
3682//                                                 FlexItem::new(
3683//                                                     self.center.render(
3684//                                                         &project,
3685//                                                         &theme,
3686//                                                         &self.follower_states,
3687//                                                         self.active_call(),
3688//                                                         self.active_pane(),
3689//                                                         self.zoomed
3690//                                                             .as_ref()
3691//                                                             .and_then(|zoomed| zoomed.upgrade(cx))
3692//                                                             .as_ref(),
3693//                                                         &self.app_state,
3694//                                                         cx,
3695//                                                     ),
3696//                                                 )
3697//                                                 .flex(1., true),
3698//                                             )
3699//                                             .with_children(
3700//                                                 self.render_dock(DockPosition::Bottom, cx),
3701//                                             )
3702//                                             .flex(1., true),
3703//                                     )
3704//                                     .with_children(self.render_dock(DockPosition::Right, cx))
3705//                             })
3706//                             .with_child(Overlay::new(
3707//                                 Stack::new()
3708//                                     .with_children(self.zoomed.as_ref().and_then(|zoomed| {
3709//                                         enum ZoomBackground {}
3710//                                         let zoomed = zoomed.upgrade(cx)?;
3711
3712//                                         let mut foreground_style =
3713//                                             theme.workspace.zoomed_pane_foreground;
3714//                                         if let Some(zoomed_dock_position) = self.zoomed_position {
3715//                                             foreground_style =
3716//                                                 theme.workspace.zoomed_panel_foreground;
3717//                                             let margin = foreground_style.margin.top;
3718//                                             let border = foreground_style.border.top;
3719
3720//                                             // Only include a margin and border on the opposite side.
3721//                                             foreground_style.margin.top = 0.;
3722//                                             foreground_style.margin.left = 0.;
3723//                                             foreground_style.margin.bottom = 0.;
3724//                                             foreground_style.margin.right = 0.;
3725//                                             foreground_style.border.top = false;
3726//                                             foreground_style.border.left = false;
3727//                                             foreground_style.border.bottom = false;
3728//                                             foreground_style.border.right = false;
3729//                                             match zoomed_dock_position {
3730//                                                 DockPosition::Left => {
3731//                                                     foreground_style.margin.right = margin;
3732//                                                     foreground_style.border.right = border;
3733//                                                 }
3734//                                                 DockPosition::Right => {
3735//                                                     foreground_style.margin.left = margin;
3736//                                                     foreground_style.border.left = border;
3737//                                                 }
3738//                                                 DockPosition::Bottom => {
3739//                                                     foreground_style.margin.top = margin;
3740//                                                     foreground_style.border.top = border;
3741//                                                 }
3742//                                             }
3743//                                         }
3744
3745//                                         Some(
3746//                                             ChildView::new(&zoomed, cx)
3747//                                                 .contained()
3748//                                                 .with_style(foreground_style)
3749//                                                 .aligned()
3750//                                                 .contained()
3751//                                                 .with_style(theme.workspace.zoomed_background)
3752//                                                 .mouse::<ZoomBackground>(0)
3753//                                                 .capture_all()
3754//                                                 .on_down(
3755//                                                     MouseButton::Left,
3756//                                                     |_, this: &mut Self, cx| {
3757//                                                         this.zoom_out(cx);
3758//                                                     },
3759//                                                 ),
3760//                                         )
3761//                                     }))
3762//                                     .with_children(self.modal.as_ref().map(|modal| {
3763//                                         // Prevent clicks within the modal from falling
3764//                                         // through to the rest of the workspace.
3765//                                         enum ModalBackground {}
3766//                                         MouseEventHandler::new::<ModalBackground, _>(
3767//                                             0,
3768//                                             cx,
3769//                                             |_, cx| ChildView::new(modal.view.as_any(), cx),
3770//                                         )
3771//                                         .on_click(MouseButton::Left, |_, _, _| {})
3772//                                         .contained()
3773//                                         .with_style(theme.workspace.modal)
3774//                                         .aligned()
3775//                                         .top()
3776//                                     }))
3777//                                     .with_children(self.render_notifications(&theme.workspace, cx)),
3778//                             ))
3779//                             .provide_resize_bounds::<WorkspaceBounds>()
3780//                             .flex(1.0, true),
3781//                     )
3782//                     .with_child(ChildView::new(&self.status_bar, cx))
3783//                     .contained()
3784//                     .with_background_color(theme.workspace.background),
3785//             )
3786//             .with_children(DragAndDrop::render(cx))
3787//             .with_children(self.render_disconnected_overlay(cx))
3788//             .into_any_named("workspace")
3789//     }
3790
3791//     fn modifiers_changed(&mut self, e: &ModifiersChangedEvent, cx: &mut ViewContext<Self>) -> bool {
3792//         DragAndDrop::<Workspace>::update_modifiers(e.modifiers, cx)
3793//     }
3794// }
3795
3796impl WorkspaceStore {
3797    pub fn new(client: Arc<Client>, cx: &mut ModelContext<Self>) -> Self {
3798        Self {
3799            workspaces: Default::default(),
3800            followers: Default::default(),
3801            _subscriptions: vec![
3802                client.add_request_handler(cx.weak_model(), Self::handle_follow),
3803                client.add_message_handler(cx.weak_model(), Self::handle_unfollow),
3804                client.add_message_handler(cx.weak_model(), Self::handle_update_followers),
3805            ],
3806            client,
3807        }
3808    }
3809
3810    pub fn update_followers(
3811        &self,
3812        project_id: Option<u64>,
3813        update: proto::update_followers::Variant,
3814        cx: &AppContext,
3815    ) -> Option<()> {
3816        if !cx.has_global::<Model<ActiveCall>>() {
3817            return None;
3818        }
3819
3820        let room_id = ActiveCall::global(cx).read(cx).room()?.read(cx).id();
3821        let follower_ids: Vec<_> = self
3822            .followers
3823            .iter()
3824            .filter_map(|follower| {
3825                if follower.project_id == project_id || project_id.is_none() {
3826                    Some(follower.peer_id.into())
3827                } else {
3828                    None
3829                }
3830            })
3831            .collect();
3832        if follower_ids.is_empty() {
3833            return None;
3834        }
3835        self.client
3836            .send(proto::UpdateFollowers {
3837                room_id,
3838                project_id,
3839                follower_ids,
3840                variant: Some(update),
3841            })
3842            .log_err()
3843    }
3844
3845    pub async fn handle_follow(
3846        this: Model<Self>,
3847        envelope: TypedEnvelope<proto::Follow>,
3848        _: Arc<Client>,
3849        mut cx: AsyncAppContext,
3850    ) -> Result<proto::FollowResponse> {
3851        this.update(&mut cx, |this, cx| {
3852            let follower = Follower {
3853                project_id: envelope.payload.project_id,
3854                peer_id: envelope.original_sender_id()?,
3855            };
3856            let active_project = ActiveCall::global(cx).read(cx).location().cloned();
3857
3858            let mut response = proto::FollowResponse::default();
3859            for workspace in &this.workspaces {
3860                workspace
3861                    .update(cx, |workspace, cx| {
3862                        let handler_response = workspace.handle_follow(follower.project_id, cx);
3863                        if response.views.is_empty() {
3864                            response.views = handler_response.views;
3865                        } else {
3866                            response.views.extend_from_slice(&handler_response.views);
3867                        }
3868
3869                        if let Some(active_view_id) = handler_response.active_view_id.clone() {
3870                            if response.active_view_id.is_none()
3871                                || Some(workspace.project.downgrade()) == active_project
3872                            {
3873                                response.active_view_id = Some(active_view_id);
3874                            }
3875                        }
3876                    })
3877                    .ok();
3878            }
3879
3880            if let Err(ix) = this.followers.binary_search(&follower) {
3881                this.followers.insert(ix, follower);
3882            }
3883
3884            Ok(response)
3885        })?
3886    }
3887
3888    async fn handle_unfollow(
3889        model: Model<Self>,
3890        envelope: TypedEnvelope<proto::Unfollow>,
3891        _: Arc<Client>,
3892        mut cx: AsyncAppContext,
3893    ) -> Result<()> {
3894        model.update(&mut cx, |this, _| {
3895            let follower = Follower {
3896                project_id: envelope.payload.project_id,
3897                peer_id: envelope.original_sender_id()?,
3898            };
3899            if let Ok(ix) = this.followers.binary_search(&follower) {
3900                this.followers.remove(ix);
3901            }
3902            Ok(())
3903        })?
3904    }
3905
3906    async fn handle_update_followers(
3907        this: Model<Self>,
3908        envelope: TypedEnvelope<proto::UpdateFollowers>,
3909        _: Arc<Client>,
3910        mut cx: AsyncAppContext,
3911    ) -> Result<()> {
3912        let leader_id = envelope.original_sender_id()?;
3913        let update = envelope.payload;
3914
3915        this.update(&mut cx, |this, cx| {
3916            for workspace in &this.workspaces {
3917                workspace.update(cx, |workspace, cx| {
3918                    let project_id = workspace.project.read(cx).remote_id();
3919                    if update.project_id != project_id && update.project_id.is_some() {
3920                        return;
3921                    }
3922                    workspace.handle_update_followers(leader_id, update.clone(), cx);
3923                })?;
3924            }
3925            Ok(())
3926        })?
3927    }
3928}
3929
3930impl ViewId {
3931    pub(crate) fn from_proto(message: proto::ViewId) -> Result<Self> {
3932        Ok(Self {
3933            creator: message
3934                .creator
3935                .ok_or_else(|| anyhow!("creator is missing"))?,
3936            id: message.id,
3937        })
3938    }
3939
3940    pub(crate) fn to_proto(&self) -> proto::ViewId {
3941        proto::ViewId {
3942            creator: Some(self.creator),
3943            id: self.id,
3944        }
3945    }
3946}
3947
3948pub trait WorkspaceHandle {
3949    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath>;
3950}
3951
3952impl WorkspaceHandle for View<Workspace> {
3953    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath> {
3954        self.read(cx)
3955            .worktrees(cx)
3956            .flat_map(|worktree| {
3957                let worktree_id = worktree.read(cx).id();
3958                worktree.read(cx).files(true, 0).map(move |f| ProjectPath {
3959                    worktree_id,
3960                    path: f.path.clone(),
3961                })
3962            })
3963            .collect::<Vec<_>>()
3964    }
3965}
3966
3967impl std::fmt::Debug for OpenPaths {
3968    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3969        f.debug_struct("OpenPaths")
3970            .field("paths", &self.paths)
3971            .finish()
3972    }
3973}
3974
3975pub fn activate_workspace_for_project(
3976    cx: &mut AppContext,
3977    predicate: impl Fn(&Project, &AppContext) -> bool + Send + 'static,
3978) -> Option<WindowHandle<Workspace>> {
3979    for window in cx.windows() {
3980        let Some(workspace) = window.downcast::<Workspace>() else {
3981            continue;
3982        };
3983
3984        let predicate = workspace
3985            .update(cx, |workspace, cx| {
3986                let project = workspace.project.read(cx);
3987                if predicate(project, cx) {
3988                    cx.activate_window();
3989                    true
3990                } else {
3991                    false
3992                }
3993            })
3994            .log_err()
3995            .unwrap_or(false);
3996
3997        if predicate {
3998            return Some(workspace);
3999        }
4000    }
4001
4002    None
4003}
4004
4005pub async fn last_opened_workspace_paths() -> Option<WorkspaceLocation> {
4006    DB.last_workspace().await.log_err().flatten()
4007}
4008
4009async fn join_channel_internal(
4010    channel_id: u64,
4011    app_state: &Arc<AppState>,
4012    requesting_window: Option<WindowHandle<Workspace>>,
4013    active_call: &Model<ActiveCall>,
4014    cx: &mut AsyncAppContext,
4015) -> Result<bool> {
4016    let (should_prompt, open_room) = active_call.update(cx, |active_call, cx| {
4017        let Some(room) = active_call.room().map(|room| room.read(cx)) else {
4018            return (false, None);
4019        };
4020
4021        let already_in_channel = room.channel_id() == Some(channel_id);
4022        let should_prompt = room.is_sharing_project()
4023            && room.remote_participants().len() > 0
4024            && !already_in_channel;
4025        let open_room = if already_in_channel {
4026            active_call.room().cloned()
4027        } else {
4028            None
4029        };
4030        (should_prompt, open_room)
4031    })?;
4032
4033    if let Some(room) = open_room {
4034        let task = room.update(cx, |room, cx| {
4035            if let Some((project, host)) = room.most_active_project(cx) {
4036                return Some(join_remote_project(project, host, app_state.clone(), cx));
4037            }
4038
4039            None
4040        })?;
4041        if let Some(task) = task {
4042            task.await?;
4043        }
4044        return anyhow::Ok(true);
4045    }
4046
4047    if should_prompt {
4048        if let Some(workspace) = requesting_window {
4049            let answer  = workspace.update(cx, |_, cx| {
4050                cx.prompt(
4051                    PromptLevel::Warning,
4052                    "Leaving this call will unshare your current project.\nDo you want to switch channels?",
4053                    &["Yes, Join Channel", "Cancel"],
4054                )
4055            })?.await;
4056
4057            if answer == Ok(1) {
4058                return Ok(false);
4059            }
4060        } else {
4061            return Ok(false); // unreachable!() hopefully
4062        }
4063    }
4064
4065    let client = cx.update(|cx| active_call.read(cx).client())?;
4066
4067    let mut client_status = client.status();
4068
4069    // this loop will terminate within client::CONNECTION_TIMEOUT seconds.
4070    'outer: loop {
4071        let Some(status) = client_status.recv().await else {
4072            return Err(anyhow!("error connecting"));
4073        };
4074
4075        match status {
4076            Status::Connecting
4077            | Status::Authenticating
4078            | Status::Reconnecting
4079            | Status::Reauthenticating => continue,
4080            Status::Connected { .. } => break 'outer,
4081            Status::SignedOut => return Err(anyhow!("not signed in")),
4082            Status::UpgradeRequired => return Err(anyhow!("zed is out of date")),
4083            Status::ConnectionError | Status::ConnectionLost | Status::ReconnectionError { .. } => {
4084                return Err(anyhow!("zed is offline"))
4085            }
4086        }
4087    }
4088
4089    let room = active_call
4090        .update(cx, |active_call, cx| {
4091            active_call.join_channel(channel_id, cx)
4092        })?
4093        .await?;
4094
4095    let Some(room) = room else {
4096        return anyhow::Ok(true);
4097    };
4098
4099    room.update(cx, |room, _| room.room_update_completed())?
4100        .await;
4101
4102    let task = room.update(cx, |room, cx| {
4103        if let Some((project, host)) = room.most_active_project(cx) {
4104            return Some(join_remote_project(project, host, app_state.clone(), cx));
4105        }
4106
4107        None
4108    })?;
4109    if let Some(task) = task {
4110        task.await?;
4111        return anyhow::Ok(true);
4112    }
4113    anyhow::Ok(false)
4114}
4115
4116pub fn join_channel(
4117    channel_id: u64,
4118    app_state: Arc<AppState>,
4119    requesting_window: Option<WindowHandle<Workspace>>,
4120    cx: &mut AppContext,
4121) -> Task<Result<()>> {
4122    let active_call = ActiveCall::global(cx);
4123    cx.spawn(|mut cx| async move {
4124        let result = join_channel_internal(
4125            channel_id,
4126            &app_state,
4127            requesting_window,
4128            &active_call,
4129            &mut cx,
4130        )
4131        .await;
4132
4133        // join channel succeeded, and opened a window
4134        if matches!(result, Ok(true)) {
4135            return anyhow::Ok(());
4136        }
4137
4138        if requesting_window.is_some() {
4139            return anyhow::Ok(());
4140        }
4141
4142        // find an existing workspace to focus and show call controls
4143        let mut active_window = activate_any_workspace_window(&mut cx);
4144        if active_window.is_none() {
4145            // no open workspaces, make one to show the error in (blergh)
4146            cx.update(|cx| Workspace::new_local(vec![], app_state.clone(), requesting_window, cx))?
4147                .await?;
4148        }
4149
4150        active_window = activate_any_workspace_window(&mut cx);
4151        let Some(active_window) = active_window else {
4152            return anyhow::Ok(());
4153        };
4154
4155        if let Err(err) = result {
4156            active_window
4157                .update(&mut cx, |_, cx| {
4158                    cx.prompt(
4159                        PromptLevel::Critical,
4160                        &format!("Failed to join channel: {}", err),
4161                        &["Ok"],
4162                    )
4163                })?
4164                .await
4165                .ok();
4166        }
4167
4168        // return ok, we showed the error to the user.
4169        return anyhow::Ok(());
4170    })
4171}
4172
4173pub fn activate_any_workspace_window(cx: &mut AsyncAppContext) -> Option<AnyWindowHandle> {
4174    cx.update(|cx| {
4175        for window in cx.windows() {
4176            let is_workspace = window.downcast::<Workspace>().is_some();
4177            if is_workspace {
4178                window.update(cx, |_, cx| cx.activate_window()).ok();
4179                return Some(window);
4180            }
4181        }
4182        None
4183    })
4184    .ok()
4185    .flatten()
4186}
4187
4188#[allow(clippy::type_complexity)]
4189pub fn open_paths(
4190    abs_paths: &[PathBuf],
4191    app_state: &Arc<AppState>,
4192    requesting_window: Option<WindowHandle<Workspace>>,
4193    cx: &mut AppContext,
4194) -> Task<
4195    anyhow::Result<(
4196        WindowHandle<Workspace>,
4197        Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
4198    )>,
4199> {
4200    let app_state = app_state.clone();
4201    let abs_paths = abs_paths.to_vec();
4202    // Open paths in existing workspace if possible
4203    let existing = activate_workspace_for_project(cx, {
4204        let abs_paths = abs_paths.clone();
4205        move |project, cx| project.contains_paths(&abs_paths, cx)
4206    });
4207    cx.spawn(move |mut cx| async move {
4208        if let Some(existing) = existing {
4209            Ok((
4210                existing.clone(),
4211                existing
4212                    .update(&mut cx, |workspace, cx| {
4213                        workspace.open_paths(abs_paths, true, cx)
4214                    })?
4215                    .await,
4216            ))
4217        } else {
4218            cx.update(move |cx| {
4219                Workspace::new_local(abs_paths, app_state.clone(), requesting_window, cx)
4220            })?
4221            .await
4222        }
4223    })
4224}
4225
4226pub fn open_new(
4227    app_state: &Arc<AppState>,
4228    cx: &mut AppContext,
4229    init: impl FnOnce(&mut Workspace, &mut ViewContext<Workspace>) + 'static + Send,
4230) -> Task<()> {
4231    let task = Workspace::new_local(Vec::new(), app_state.clone(), None, cx);
4232    cx.spawn(|mut cx| async move {
4233        if let Some((workspace, opened_paths)) = task.await.log_err() {
4234            workspace
4235                .update(&mut cx, |workspace, cx| {
4236                    if opened_paths.is_empty() {
4237                        init(workspace, cx)
4238                    }
4239                })
4240                .log_err();
4241        }
4242    })
4243}
4244
4245pub fn create_and_open_local_file(
4246    path: &'static Path,
4247    cx: &mut ViewContext<Workspace>,
4248    default_content: impl 'static + Send + FnOnce() -> Rope,
4249) -> Task<Result<Box<dyn ItemHandle>>> {
4250    cx.spawn(|workspace, mut cx| async move {
4251        let fs = workspace.update(&mut cx, |workspace, _| workspace.app_state().fs.clone())?;
4252        if !fs.is_file(path).await {
4253            fs.create_file(path, Default::default()).await?;
4254            fs.save(path, &default_content(), Default::default())
4255                .await?;
4256        }
4257
4258        let mut items = workspace
4259            .update(&mut cx, |workspace, cx| {
4260                workspace.with_local_workspace(cx, |workspace, cx| {
4261                    workspace.open_paths(vec![path.to_path_buf()], false, cx)
4262                })
4263            })?
4264            .await?
4265            .await;
4266
4267        let item = items.pop().flatten();
4268        item.ok_or_else(|| anyhow!("path {path:?} is not a file"))?
4269    })
4270}
4271
4272pub fn join_remote_project(
4273    project_id: u64,
4274    follow_user_id: u64,
4275    app_state: Arc<AppState>,
4276    cx: &mut AppContext,
4277) -> Task<Result<()>> {
4278    let windows = cx.windows();
4279    cx.spawn(|mut cx| async move {
4280        let existing_workspace = windows.into_iter().find_map(|window| {
4281            window.downcast::<Workspace>().and_then(|window| {
4282                window
4283                    .update(&mut cx, |workspace, cx| {
4284                        if workspace.project().read(cx).remote_id() == Some(project_id) {
4285                            Some(window)
4286                        } else {
4287                            None
4288                        }
4289                    })
4290                    .unwrap_or(None)
4291            })
4292        });
4293
4294        let workspace = if let Some(existing_workspace) = existing_workspace {
4295            existing_workspace
4296        } else {
4297            let active_call = cx.update(|cx| ActiveCall::global(cx))?;
4298            let room = active_call
4299                .read_with(&cx, |call, _| call.room().cloned())?
4300                .ok_or_else(|| anyhow!("not in a call"))?;
4301            let project = room
4302                .update(&mut cx, |room, cx| {
4303                    room.join_project(
4304                        project_id,
4305                        app_state.languages.clone(),
4306                        app_state.fs.clone(),
4307                        cx,
4308                    )
4309                })?
4310                .await?;
4311
4312            let window_bounds_override = window_bounds_env_override(&cx);
4313            cx.update(|cx| {
4314                let options = (app_state.build_window_options)(window_bounds_override, None, cx);
4315                cx.open_window(options, |cx| {
4316                    cx.build_view(|cx| Workspace::new(0, project, app_state.clone(), cx))
4317                })
4318            })?
4319        };
4320
4321        workspace.update(&mut cx, |workspace, cx| {
4322            cx.activate(true);
4323            cx.activate_window();
4324
4325            if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
4326                let follow_peer_id = room
4327                    .read(cx)
4328                    .remote_participants()
4329                    .iter()
4330                    .find(|(_, participant)| participant.user.id == follow_user_id)
4331                    .map(|(_, p)| p.peer_id)
4332                    .or_else(|| {
4333                        // If we couldn't follow the given user, follow the host instead.
4334                        let collaborator = workspace
4335                            .project()
4336                            .read(cx)
4337                            .collaborators()
4338                            .values()
4339                            .find(|collaborator| collaborator.replica_id == 0)?;
4340                        Some(collaborator.peer_id)
4341                    });
4342
4343                if let Some(follow_peer_id) = follow_peer_id {
4344                    workspace
4345                        .follow(follow_peer_id, cx)
4346                        .map(|follow| follow.detach_and_log_err(cx));
4347                }
4348            }
4349        })?;
4350
4351        anyhow::Ok(())
4352    })
4353}
4354
4355pub fn restart(_: &Restart, cx: &mut AppContext) {
4356    let should_confirm = WorkspaceSettings::get_global(cx).confirm_quit;
4357    let mut workspace_windows = cx
4358        .windows()
4359        .into_iter()
4360        .filter_map(|window| window.downcast::<Workspace>())
4361        .collect::<Vec<_>>();
4362
4363    // If multiple windows have unsaved changes, and need a save prompt,
4364    // prompt in the active window before switching to a different window.
4365    workspace_windows.sort_by_key(|window| window.is_active(&cx) == Some(false));
4366
4367    let mut prompt = None;
4368    if let (true, Some(window)) = (should_confirm, workspace_windows.first()) {
4369        prompt = window
4370            .update(cx, |_, cx| {
4371                cx.prompt(
4372                    PromptLevel::Info,
4373                    "Are you sure you want to restart?",
4374                    &["Restart", "Cancel"],
4375                )
4376            })
4377            .ok();
4378    }
4379
4380    cx.spawn(|mut cx| async move {
4381        if let Some(prompt) = prompt {
4382            let answer = prompt.await?;
4383            if answer != 0 {
4384                return Ok(());
4385            }
4386        }
4387
4388        // If the user cancels any save prompt, then keep the app open.
4389        for window in workspace_windows {
4390            if let Ok(should_close) = window.update(&mut cx, |workspace, cx| {
4391                workspace.prepare_to_close(true, cx)
4392            }) {
4393                if !should_close.await? {
4394                    return Ok(());
4395                }
4396            }
4397        }
4398
4399        cx.update(|cx| cx.restart())
4400    })
4401    .detach_and_log_err(cx);
4402}
4403
4404fn parse_pixel_position_env_var(value: &str) -> Option<Point<GlobalPixels>> {
4405    let mut parts = value.split(',');
4406    let x: usize = parts.next()?.parse().ok()?;
4407    let y: usize = parts.next()?.parse().ok()?;
4408    Some(point((x as f64).into(), (y as f64).into()))
4409}
4410
4411fn parse_pixel_size_env_var(value: &str) -> Option<Size<GlobalPixels>> {
4412    let mut parts = value.split(',');
4413    let width: usize = parts.next()?.parse().ok()?;
4414    let height: usize = parts.next()?.parse().ok()?;
4415    Some(size((width as f64).into(), (height as f64).into()))
4416}
4417
4418#[cfg(test)]
4419mod tests {
4420    use std::{cell::RefCell, rc::Rc};
4421
4422    use super::*;
4423    use crate::item::{
4424        test::{TestItem, TestProjectItem},
4425        ItemEvent,
4426    };
4427    use fs::FakeFs;
4428    use gpui::TestAppContext;
4429    use project::{Project, ProjectEntryId};
4430    use serde_json::json;
4431    use settings::SettingsStore;
4432
4433    #[gpui::test]
4434    async fn test_tab_disambiguation(cx: &mut TestAppContext) {
4435        init_test(cx);
4436
4437        let fs = FakeFs::new(cx.executor());
4438        let project = Project::test(fs, [], cx).await;
4439        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
4440
4441        // Adding an item with no ambiguity renders the tab without detail.
4442        let item1 = cx.build_view(|cx| {
4443            let mut item = TestItem::new(cx);
4444            item.tab_descriptions = Some(vec!["c", "b1/c", "a/b1/c"]);
4445            item
4446        });
4447        workspace.update(cx, |workspace, cx| {
4448            workspace.add_item(Box::new(item1.clone()), cx);
4449        });
4450        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(0)));
4451
4452        // Adding an item that creates ambiguity increases the level of detail on
4453        // both tabs.
4454        let item2 = cx.build_view(|cx| {
4455            let mut item = TestItem::new(cx);
4456            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
4457            item
4458        });
4459        workspace.update(cx, |workspace, cx| {
4460            workspace.add_item(Box::new(item2.clone()), cx);
4461        });
4462        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4463        item2.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4464
4465        // Adding an item that creates ambiguity increases the level of detail only
4466        // on the ambiguous tabs. In this case, the ambiguity can't be resolved so
4467        // we stop at the highest detail available.
4468        let item3 = cx.build_view(|cx| {
4469            let mut item = TestItem::new(cx);
4470            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
4471            item
4472        });
4473        workspace.update(cx, |workspace, cx| {
4474            workspace.add_item(Box::new(item3.clone()), cx);
4475        });
4476        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4477        item2.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
4478        item3.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
4479    }
4480
4481    #[gpui::test]
4482    async fn test_tracking_active_path(cx: &mut TestAppContext) {
4483        init_test(cx);
4484
4485        let fs = FakeFs::new(cx.executor());
4486        fs.insert_tree(
4487            "/root1",
4488            json!({
4489                "one.txt": "",
4490                "two.txt": "",
4491            }),
4492        )
4493        .await;
4494        fs.insert_tree(
4495            "/root2",
4496            json!({
4497                "three.txt": "",
4498            }),
4499        )
4500        .await;
4501
4502        let project = Project::test(fs, ["root1".as_ref()], cx).await;
4503        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
4504        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
4505        let worktree_id = project.update(cx, |project, cx| {
4506            project.worktrees().next().unwrap().read(cx).id()
4507        });
4508
4509        let item1 = cx.build_view(|cx| {
4510            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "one.txt", cx)])
4511        });
4512        let item2 = cx.build_view(|cx| {
4513            TestItem::new(cx).with_project_items(&[TestProjectItem::new(2, "two.txt", cx)])
4514        });
4515
4516        // Add an item to an empty pane
4517        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item1), cx));
4518        project.update(cx, |project, cx| {
4519            assert_eq!(
4520                project.active_entry(),
4521                project
4522                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
4523                    .map(|e| e.id)
4524            );
4525        });
4526        assert_eq!(cx.window_title().as_deref(), Some("one.txt β€” root1"));
4527
4528        // Add a second item to a non-empty pane
4529        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item2), cx));
4530        assert_eq!(cx.window_title().as_deref(), Some("two.txt β€” root1"));
4531        project.update(cx, |project, cx| {
4532            assert_eq!(
4533                project.active_entry(),
4534                project
4535                    .entry_for_path(&(worktree_id, "two.txt").into(), cx)
4536                    .map(|e| e.id)
4537            );
4538        });
4539
4540        // Close the active item
4541        pane.update(cx, |pane, cx| {
4542            pane.close_active_item(&Default::default(), cx).unwrap()
4543        })
4544        .await
4545        .unwrap();
4546        assert_eq!(cx.window_title().as_deref(), Some("one.txt β€” root1"));
4547        project.update(cx, |project, cx| {
4548            assert_eq!(
4549                project.active_entry(),
4550                project
4551                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
4552                    .map(|e| e.id)
4553            );
4554        });
4555
4556        // Add a project folder
4557        project
4558            .update(cx, |project, cx| {
4559                project.find_or_create_local_worktree("/root2", true, cx)
4560            })
4561            .await
4562            .unwrap();
4563        assert_eq!(cx.window_title().as_deref(), Some("one.txt β€” root1, root2"));
4564
4565        // Remove a project folder
4566        project.update(cx, |project, cx| project.remove_worktree(worktree_id, cx));
4567        assert_eq!(cx.window_title().as_deref(), Some("one.txt β€” root2"));
4568    }
4569
4570    #[gpui::test]
4571    async fn test_close_window(cx: &mut TestAppContext) {
4572        init_test(cx);
4573
4574        let fs = FakeFs::new(cx.executor());
4575        fs.insert_tree("/root", json!({ "one": "" })).await;
4576
4577        let project = Project::test(fs, ["root".as_ref()], cx).await;
4578        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
4579
4580        // When there are no dirty items, there's nothing to do.
4581        let item1 = cx.build_view(|cx| TestItem::new(cx));
4582        workspace.update(cx, |w, cx| w.add_item(Box::new(item1.clone()), cx));
4583        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
4584        assert!(task.await.unwrap());
4585
4586        // When there are dirty untitled items, prompt to save each one. If the user
4587        // cancels any prompt, then abort.
4588        let item2 = cx.build_view(|cx| TestItem::new(cx).with_dirty(true));
4589        let item3 = cx.build_view(|cx| {
4590            TestItem::new(cx)
4591                .with_dirty(true)
4592                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4593        });
4594        workspace.update(cx, |w, cx| {
4595            w.add_item(Box::new(item2.clone()), cx);
4596            w.add_item(Box::new(item3.clone()), cx);
4597        });
4598        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
4599        cx.executor().run_until_parked();
4600        cx.simulate_prompt_answer(2); // cancel save all
4601        cx.executor().run_until_parked();
4602        cx.simulate_prompt_answer(2); // cancel save all
4603        cx.executor().run_until_parked();
4604        assert!(!cx.has_pending_prompt());
4605        assert!(!task.await.unwrap());
4606    }
4607
4608    #[gpui::test]
4609    async fn test_close_pane_items(cx: &mut TestAppContext) {
4610        init_test(cx);
4611
4612        let fs = FakeFs::new(cx.executor());
4613
4614        let project = Project::test(fs, None, cx).await;
4615        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
4616
4617        let item1 = cx.build_view(|cx| {
4618            TestItem::new(cx)
4619                .with_dirty(true)
4620                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4621        });
4622        let item2 = cx.build_view(|cx| {
4623            TestItem::new(cx)
4624                .with_dirty(true)
4625                .with_conflict(true)
4626                .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)])
4627        });
4628        let item3 = cx.build_view(|cx| {
4629            TestItem::new(cx)
4630                .with_dirty(true)
4631                .with_conflict(true)
4632                .with_project_items(&[TestProjectItem::new(3, "3.txt", cx)])
4633        });
4634        let item4 = cx.build_view(|cx| {
4635            TestItem::new(cx)
4636                .with_dirty(true)
4637                .with_project_items(&[TestProjectItem::new_untitled(cx)])
4638        });
4639        let pane = workspace.update(cx, |workspace, cx| {
4640            workspace.add_item(Box::new(item1.clone()), cx);
4641            workspace.add_item(Box::new(item2.clone()), cx);
4642            workspace.add_item(Box::new(item3.clone()), cx);
4643            workspace.add_item(Box::new(item4.clone()), cx);
4644            workspace.active_pane().clone()
4645        });
4646
4647        let close_items = pane.update(cx, |pane, cx| {
4648            pane.activate_item(1, true, true, cx);
4649            assert_eq!(pane.active_item().unwrap().item_id(), item2.item_id());
4650            let item1_id = item1.item_id();
4651            let item3_id = item3.item_id();
4652            let item4_id = item4.item_id();
4653            pane.close_items(cx, SaveIntent::Close, move |id| {
4654                [item1_id, item3_id, item4_id].contains(&id)
4655            })
4656        });
4657        cx.executor().run_until_parked();
4658
4659        assert!(cx.has_pending_prompt());
4660        // Ignore "Save all" prompt
4661        cx.simulate_prompt_answer(2);
4662        cx.executor().run_until_parked();
4663        // There's a prompt to save item 1.
4664        pane.update(cx, |pane, _| {
4665            assert_eq!(pane.items_len(), 4);
4666            assert_eq!(pane.active_item().unwrap().item_id(), item1.item_id());
4667        });
4668        // Confirm saving item 1.
4669        cx.simulate_prompt_answer(0);
4670        cx.executor().run_until_parked();
4671
4672        // Item 1 is saved. There's a prompt to save item 3.
4673        pane.update(cx, |pane, cx| {
4674            assert_eq!(item1.read(cx).save_count, 1);
4675            assert_eq!(item1.read(cx).save_as_count, 0);
4676            assert_eq!(item1.read(cx).reload_count, 0);
4677            assert_eq!(pane.items_len(), 3);
4678            assert_eq!(pane.active_item().unwrap().item_id(), item3.item_id());
4679        });
4680        assert!(cx.has_pending_prompt());
4681
4682        // Cancel saving item 3.
4683        cx.simulate_prompt_answer(1);
4684        cx.executor().run_until_parked();
4685
4686        // Item 3 is reloaded. There's a prompt to save item 4.
4687        pane.update(cx, |pane, cx| {
4688            assert_eq!(item3.read(cx).save_count, 0);
4689            assert_eq!(item3.read(cx).save_as_count, 0);
4690            assert_eq!(item3.read(cx).reload_count, 1);
4691            assert_eq!(pane.items_len(), 2);
4692            assert_eq!(pane.active_item().unwrap().item_id(), item4.item_id());
4693        });
4694        assert!(cx.has_pending_prompt());
4695
4696        // Confirm saving item 4.
4697        cx.simulate_prompt_answer(0);
4698        cx.executor().run_until_parked();
4699
4700        // There's a prompt for a path for item 4.
4701        cx.simulate_new_path_selection(|_| Some(Default::default()));
4702        close_items.await.unwrap();
4703
4704        // The requested items are closed.
4705        pane.update(cx, |pane, cx| {
4706            assert_eq!(item4.read(cx).save_count, 0);
4707            assert_eq!(item4.read(cx).save_as_count, 1);
4708            assert_eq!(item4.read(cx).reload_count, 0);
4709            assert_eq!(pane.items_len(), 1);
4710            assert_eq!(pane.active_item().unwrap().item_id(), item2.item_id());
4711        });
4712    }
4713
4714    #[gpui::test]
4715    async fn test_prompting_to_save_only_on_last_item_for_entry(cx: &mut TestAppContext) {
4716        init_test(cx);
4717
4718        let fs = FakeFs::new(cx.executor());
4719        let project = Project::test(fs, [], cx).await;
4720        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
4721
4722        // Create several workspace items with single project entries, and two
4723        // workspace items with multiple project entries.
4724        let single_entry_items = (0..=4)
4725            .map(|project_entry_id| {
4726                cx.build_view(|cx| {
4727                    TestItem::new(cx)
4728                        .with_dirty(true)
4729                        .with_project_items(&[TestProjectItem::new(
4730                            project_entry_id,
4731                            &format!("{project_entry_id}.txt"),
4732                            cx,
4733                        )])
4734                })
4735            })
4736            .collect::<Vec<_>>();
4737        let item_2_3 = cx.build_view(|cx| {
4738            TestItem::new(cx)
4739                .with_dirty(true)
4740                .with_singleton(false)
4741                .with_project_items(&[
4742                    single_entry_items[2].read(cx).project_items[0].clone(),
4743                    single_entry_items[3].read(cx).project_items[0].clone(),
4744                ])
4745        });
4746        let item_3_4 = cx.build_view(|cx| {
4747            TestItem::new(cx)
4748                .with_dirty(true)
4749                .with_singleton(false)
4750                .with_project_items(&[
4751                    single_entry_items[3].read(cx).project_items[0].clone(),
4752                    single_entry_items[4].read(cx).project_items[0].clone(),
4753                ])
4754        });
4755
4756        // Create two panes that contain the following project entries:
4757        //   left pane:
4758        //     multi-entry items:   (2, 3)
4759        //     single-entry items:  0, 1, 2, 3, 4
4760        //   right pane:
4761        //     single-entry items:  1
4762        //     multi-entry items:   (3, 4)
4763        let left_pane = workspace.update(cx, |workspace, cx| {
4764            let left_pane = workspace.active_pane().clone();
4765            workspace.add_item(Box::new(item_2_3.clone()), cx);
4766            for item in single_entry_items {
4767                workspace.add_item(Box::new(item), cx);
4768            }
4769            left_pane.update(cx, |pane, cx| {
4770                pane.activate_item(2, true, true, cx);
4771            });
4772
4773            let right_pane = workspace
4774                .split_and_clone(left_pane.clone(), SplitDirection::Right, cx)
4775                .unwrap();
4776
4777            right_pane.update(cx, |pane, cx| {
4778                pane.add_item(Box::new(item_3_4.clone()), true, true, None, cx);
4779            });
4780
4781            left_pane
4782        });
4783
4784        cx.focus_view(&left_pane);
4785
4786        // When closing all of the items in the left pane, we should be prompted twice:
4787        // once for project entry 0, and once for project entry 2. Project entries 1,
4788        // 3, and 4 are all still open in the other paten. After those two
4789        // prompts, the task should complete.
4790
4791        let close = left_pane.update(cx, |pane, cx| {
4792            pane.close_all_items(&CloseAllItems::default(), cx).unwrap()
4793        });
4794        cx.executor().run_until_parked();
4795
4796        // Discard "Save all" prompt
4797        cx.simulate_prompt_answer(2);
4798
4799        cx.executor().run_until_parked();
4800        left_pane.update(cx, |pane, cx| {
4801            assert_eq!(
4802                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
4803                &[ProjectEntryId::from_proto(0)]
4804            );
4805        });
4806        cx.simulate_prompt_answer(0);
4807
4808        cx.executor().run_until_parked();
4809        left_pane.update(cx, |pane, cx| {
4810            assert_eq!(
4811                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
4812                &[ProjectEntryId::from_proto(2)]
4813            );
4814        });
4815        cx.simulate_prompt_answer(0);
4816
4817        cx.executor().run_until_parked();
4818        close.await.unwrap();
4819        left_pane.update(cx, |pane, _| {
4820            assert_eq!(pane.items_len(), 0);
4821        });
4822    }
4823
4824    #[gpui::test]
4825    async fn test_autosave(cx: &mut gpui::TestAppContext) {
4826        init_test(cx);
4827
4828        let fs = FakeFs::new(cx.executor());
4829        let project = Project::test(fs, [], cx).await;
4830        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
4831        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
4832
4833        let item = cx.build_view(|cx| {
4834            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4835        });
4836        let item_id = item.entity_id();
4837        workspace.update(cx, |workspace, cx| {
4838            workspace.add_item(Box::new(item.clone()), cx);
4839        });
4840
4841        // Autosave on window change.
4842        item.update(cx, |item, cx| {
4843            cx.update_global(|settings: &mut SettingsStore, cx| {
4844                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4845                    settings.autosave = Some(AutosaveSetting::OnWindowChange);
4846                })
4847            });
4848            item.is_dirty = true;
4849        });
4850
4851        // Deactivating the window saves the file.
4852        cx.simulate_deactivation();
4853        cx.executor().run_until_parked();
4854        item.update(cx, |item, _| assert_eq!(item.save_count, 1));
4855
4856        // Autosave on focus change.
4857        item.update(cx, |item, cx| {
4858            cx.focus_self();
4859            cx.update_global(|settings: &mut SettingsStore, cx| {
4860                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4861                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
4862                })
4863            });
4864            item.is_dirty = true;
4865        });
4866
4867        // Blurring the item saves the file.
4868        item.update(cx, |_, cx| cx.blur());
4869        cx.executor().run_until_parked();
4870        item.update(cx, |item, _| assert_eq!(item.save_count, 2));
4871
4872        // Deactivating the window still saves the file.
4873        cx.simulate_activation();
4874        item.update(cx, |item, cx| {
4875            cx.focus_self();
4876            item.is_dirty = true;
4877        });
4878        cx.simulate_deactivation();
4879
4880        cx.executor().run_until_parked();
4881        item.update(cx, |item, _| assert_eq!(item.save_count, 3));
4882
4883        // Autosave after delay.
4884        item.update(cx, |item, cx| {
4885            cx.update_global(|settings: &mut SettingsStore, cx| {
4886                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4887                    settings.autosave = Some(AutosaveSetting::AfterDelay { milliseconds: 500 });
4888                })
4889            });
4890            item.is_dirty = true;
4891            cx.emit(ItemEvent::Edit);
4892        });
4893
4894        // Delay hasn't fully expired, so the file is still dirty and unsaved.
4895        cx.executor().advance_clock(Duration::from_millis(250));
4896        item.update(cx, |item, _| assert_eq!(item.save_count, 3));
4897
4898        // After delay expires, the file is saved.
4899        cx.executor().advance_clock(Duration::from_millis(250));
4900        item.update(cx, |item, _| assert_eq!(item.save_count, 4));
4901
4902        // Autosave on focus change, ensuring closing the tab counts as such.
4903        item.update(cx, |item, cx| {
4904            cx.update_global(|settings: &mut SettingsStore, cx| {
4905                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4906                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
4907                })
4908            });
4909            item.is_dirty = true;
4910        });
4911
4912        pane.update(cx, |pane, cx| {
4913            pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
4914        })
4915        .await
4916        .unwrap();
4917        assert!(!cx.has_pending_prompt());
4918        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
4919
4920        // Add the item again, ensuring autosave is prevented if the underlying file has been deleted.
4921        workspace.update(cx, |workspace, cx| {
4922            workspace.add_item(Box::new(item.clone()), cx);
4923        });
4924        item.update(cx, |item, cx| {
4925            item.project_items[0].update(cx, |item, _| {
4926                item.entry_id = None;
4927            });
4928            item.is_dirty = true;
4929            cx.blur();
4930        });
4931        cx.run_until_parked();
4932        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
4933
4934        // Ensure autosave is prevented for deleted files also when closing the buffer.
4935        let _close_items = pane.update(cx, |pane, cx| {
4936            pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
4937        });
4938        cx.run_until_parked();
4939        assert!(cx.has_pending_prompt());
4940        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
4941    }
4942
4943    #[gpui::test]
4944    async fn test_pane_navigation(cx: &mut gpui::TestAppContext) {
4945        init_test(cx);
4946
4947        let fs = FakeFs::new(cx.executor());
4948
4949        let project = Project::test(fs, [], cx).await;
4950        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
4951
4952        let item = cx.build_view(|cx| {
4953            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4954        });
4955        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
4956        let toolbar = pane.update(cx, |pane, _| pane.toolbar().clone());
4957        let toolbar_notify_count = Rc::new(RefCell::new(0));
4958
4959        workspace.update(cx, |workspace, cx| {
4960            workspace.add_item(Box::new(item.clone()), cx);
4961            let toolbar_notification_count = toolbar_notify_count.clone();
4962            cx.observe(&toolbar, move |_, _, _| {
4963                *toolbar_notification_count.borrow_mut() += 1
4964            })
4965            .detach();
4966        });
4967
4968        pane.update(cx, |pane, _| {
4969            assert!(!pane.can_navigate_backward());
4970            assert!(!pane.can_navigate_forward());
4971        });
4972
4973        item.update(cx, |item, cx| {
4974            item.set_state("one".to_string(), cx);
4975        });
4976
4977        // Toolbar must be notified to re-render the navigation buttons
4978        assert_eq!(*toolbar_notify_count.borrow(), 1);
4979
4980        pane.update(cx, |pane, _| {
4981            assert!(pane.can_navigate_backward());
4982            assert!(!pane.can_navigate_forward());
4983        });
4984
4985        workspace
4986            .update(cx, |workspace, cx| workspace.go_back(pane.downgrade(), cx))
4987            .await
4988            .unwrap();
4989
4990        assert_eq!(*toolbar_notify_count.borrow(), 2);
4991        pane.update(cx, |pane, _| {
4992            assert!(!pane.can_navigate_backward());
4993            assert!(pane.can_navigate_forward());
4994        });
4995    }
4996
4997    // #[gpui::test]
4998    // async fn test_toggle_docks_and_panels(cx: &mut gpui::TestAppContext) {
4999    //     init_test(cx);
5000    //     let fs = FakeFs::new(cx.executor());
5001
5002    //     let project = Project::test(fs, [], cx).await;
5003    //     let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
5004
5005    //     let panel = workspace.update(cx, |workspace, cx| {
5006    //         let panel = cx.build_view(|cx| TestPanel::new(DockPosition::Right, cx));
5007    //         workspace.add_panel(panel.clone(), cx);
5008
5009    //         workspace
5010    //             .right_dock()
5011    //             .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
5012
5013    //         panel
5014    //     });
5015
5016    //     let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
5017    //     pane.update(cx, |pane, cx| {
5018    //         let item = cx.build_view(|cx| TestItem::new(cx));
5019    //         pane.add_item(Box::new(item), true, true, None, cx);
5020    //     });
5021
5022    //     // Transfer focus from center to panel
5023    //     workspace.update(cx, |workspace, cx| {
5024    //         workspace.toggle_panel_focus::<TestPanel>(cx);
5025    //     });
5026
5027    //     workspace.update(cx, |workspace, cx| {
5028    //         assert!(workspace.right_dock().read(cx).is_open());
5029    //         assert!(!panel.is_zoomed(cx));
5030    //         assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
5031    //     });
5032
5033    //     // Transfer focus from panel to center
5034    //     workspace.update(cx, |workspace, cx| {
5035    //         workspace.toggle_panel_focus::<TestPanel>(cx);
5036    //     });
5037
5038    //     workspace.update(cx, |workspace, cx| {
5039    //         assert!(workspace.right_dock().read(cx).is_open());
5040    //         assert!(!panel.is_zoomed(cx));
5041    //         assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
5042    //     });
5043
5044    //     // Close the dock
5045    //     workspace.update(cx, |workspace, cx| {
5046    //         workspace.toggle_dock(DockPosition::Right, cx);
5047    //     });
5048
5049    //     workspace.update(cx, |workspace, cx| {
5050    //         assert!(!workspace.right_dock().read(cx).is_open());
5051    //         assert!(!panel.is_zoomed(cx));
5052    //         assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
5053    //     });
5054
5055    //     // Open the dock
5056    //     workspace.update(cx, |workspace, cx| {
5057    //         workspace.toggle_dock(DockPosition::Right, cx);
5058    //     });
5059
5060    //     workspace.update(cx, |workspace, cx| {
5061    //         assert!(workspace.right_dock().read(cx).is_open());
5062    //         assert!(!panel.is_zoomed(cx));
5063    //         assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
5064    //     });
5065
5066    //     // Focus and zoom panel
5067    //     panel.update(cx, |panel, cx| {
5068    //         cx.focus_self();
5069    //         panel.set_zoomed(true, cx)
5070    //     });
5071
5072    //     workspace.update(cx, |workspace, cx| {
5073    //         assert!(workspace.right_dock().read(cx).is_open());
5074    //         assert!(panel.is_zoomed(cx));
5075    //         assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
5076    //     });
5077
5078    //     // Transfer focus to the center closes the dock
5079    //     workspace.update(cx, |workspace, cx| {
5080    //         workspace.toggle_panel_focus::<TestPanel>(cx);
5081    //     });
5082
5083    //     workspace.update(cx, |workspace, cx| {
5084    //         assert!(!workspace.right_dock().read(cx).is_open());
5085    //         assert!(panel.is_zoomed(cx));
5086    //         assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
5087    //     });
5088
5089    //     // Transferring focus back to the panel keeps it zoomed
5090    //     workspace.update(cx, |workspace, cx| {
5091    //         workspace.toggle_panel_focus::<TestPanel>(cx);
5092    //     });
5093
5094    //     workspace.update(cx, |workspace, cx| {
5095    //         assert!(workspace.right_dock().read(cx).is_open());
5096    //         assert!(panel.is_zoomed(cx));
5097    //         assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
5098    //     });
5099
5100    //     // Close the dock while it is zoomed
5101    //     workspace.update(cx, |workspace, cx| {
5102    //         workspace.toggle_dock(DockPosition::Right, cx)
5103    //     });
5104
5105    //     workspace.update(cx, |workspace, cx| {
5106    //         assert!(!workspace.right_dock().read(cx).is_open());
5107    //         assert!(panel.is_zoomed(cx));
5108    //         assert!(workspace.zoomed.is_none());
5109    //         assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
5110    //     });
5111
5112    //     // Opening the dock, when it's zoomed, retains focus
5113    //     workspace.update(cx, |workspace, cx| {
5114    //         workspace.toggle_dock(DockPosition::Right, cx)
5115    //     });
5116
5117    //     workspace.update(cx, |workspace, cx| {
5118    //         assert!(workspace.right_dock().read(cx).is_open());
5119    //         assert!(panel.is_zoomed(cx));
5120    //         assert!(workspace.zoomed.is_some());
5121    //         assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
5122    //     });
5123
5124    //     // Unzoom and close the panel, zoom the active pane.
5125    //     panel.update(cx, |panel, cx| panel.set_zoomed(false, cx));
5126    //     workspace.update(cx, |workspace, cx| {
5127    //         workspace.toggle_dock(DockPosition::Right, cx)
5128    //     });
5129    //     pane.update(cx, |pane, cx| pane.toggle_zoom(&Default::default(), cx));
5130
5131    //     // Opening a dock unzooms the pane.
5132    //     workspace.update(cx, |workspace, cx| {
5133    //         workspace.toggle_dock(DockPosition::Right, cx)
5134    //     });
5135    //     workspace.update(cx, |workspace, cx| {
5136    //         let pane = pane.read(cx);
5137    //         assert!(!pane.is_zoomed());
5138    //         assert!(!pane.focus_handle(cx).is_focused(cx));
5139    //         assert!(workspace.right_dock().read(cx).is_open());
5140    //         assert!(workspace.zoomed.is_none());
5141    //     });
5142    // }
5143
5144    // #[gpui::test]
5145    // async fn test_panels(cx: &mut gpui::TestAppContext) {
5146    //     init_test(cx);
5147    //     let fs = FakeFs::new(cx.executor());
5148
5149    //     let project = Project::test(fs, [], cx).await;
5150    //     let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
5151
5152    //     let (panel_1, panel_2) = workspace.update(cx, |workspace, cx| {
5153    //         // Add panel_1 on the left, panel_2 on the right.
5154    //         let panel_1 = cx.build_view(|cx| TestPanel::new(DockPosition::Left, cx));
5155    //         workspace.add_panel(panel_1.clone(), cx);
5156    //         workspace
5157    //             .left_dock()
5158    //             .update(cx, |left_dock, cx| left_dock.set_open(true, cx));
5159    //         let panel_2 = cx.build_view(|cx| TestPanel::new(DockPosition::Right, cx));
5160    //         workspace.add_panel(panel_2.clone(), cx);
5161    //         workspace
5162    //             .right_dock()
5163    //             .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
5164
5165    //         let left_dock = workspace.left_dock();
5166    //         assert_eq!(
5167    //             left_dock.read(cx).visible_panel().unwrap().panel_id(),
5168    //             panel_1.panel_id()
5169    //         );
5170    //         assert_eq!(
5171    //             left_dock.read(cx).active_panel_size(cx).unwrap(),
5172    //             panel_1.size(cx)
5173    //         );
5174
5175    //         left_dock.update(cx, |left_dock, cx| {
5176    //             left_dock.resize_active_panel(Some(1337.), cx)
5177    //         });
5178    //         assert_eq!(
5179    //             workspace
5180    //                 .right_dock()
5181    //                 .read(cx)
5182    //                 .visible_panel()
5183    //                 .unwrap()
5184    //                 .panel_id(),
5185    //             panel_2.panel_id(),
5186    //         );
5187
5188    //         (panel_1, panel_2)
5189    //     });
5190
5191    //     // Move panel_1 to the right
5192    //     panel_1.update(cx, |panel_1, cx| {
5193    //         panel_1.set_position(DockPosition::Right, cx)
5194    //     });
5195
5196    //     workspace.update(cx, |workspace, cx| {
5197    //         // Since panel_1 was visible on the left, it should now be visible now that it's been moved to the right.
5198    //         // Since it was the only panel on the left, the left dock should now be closed.
5199    //         assert!(!workspace.left_dock().read(cx).is_open());
5200    //         assert!(workspace.left_dock().read(cx).visible_panel().is_none());
5201    //         let right_dock = workspace.right_dock();
5202    //         assert_eq!(
5203    //             right_dock.read(cx).visible_panel().unwrap().panel_id(),
5204    //             panel_1.panel_id()
5205    //         );
5206    //         assert_eq!(right_dock.read(cx).active_panel_size(cx).unwrap(), 1337.);
5207
5208    //         // Now we move panel_2Β to the left
5209    //         panel_2.set_position(DockPosition::Left, cx);
5210    //     });
5211
5212    //     workspace.update(cx, |workspace, cx| {
5213    //         // Since panel_2 was not visible on the right, we don't open the left dock.
5214    //         assert!(!workspace.left_dock().read(cx).is_open());
5215    //         // And the right dock is unaffected in it's displaying of panel_1
5216    //         assert!(workspace.right_dock().read(cx).is_open());
5217    //         assert_eq!(
5218    //             workspace
5219    //                 .right_dock()
5220    //                 .read(cx)
5221    //                 .visible_panel()
5222    //                 .unwrap()
5223    //                 .panel_id(),
5224    //             panel_1.panel_id(),
5225    //         );
5226    //     });
5227
5228    //     // Move panel_1 back to the left
5229    //     panel_1.update(cx, |panel_1, cx| {
5230    //         panel_1.set_position(DockPosition::Left, cx)
5231    //     });
5232
5233    //     workspace.update(cx, |workspace, cx| {
5234    //         // Since panel_1 was visible on the right, we open the left dock and make panel_1 active.
5235    //         let left_dock = workspace.left_dock();
5236    //         assert!(left_dock.read(cx).is_open());
5237    //         assert_eq!(
5238    //             left_dock.read(cx).visible_panel().unwrap().panel_id(),
5239    //             panel_1.panel_id()
5240    //         );
5241    //         assert_eq!(left_dock.read(cx).active_panel_size(cx).unwrap(), 1337.);
5242    //         // And right the dock should be closed as it no longer has any panels.
5243    //         assert!(!workspace.right_dock().read(cx).is_open());
5244
5245    //         // Now we move panel_1 to the bottom
5246    //         panel_1.set_position(DockPosition::Bottom, cx);
5247    //     });
5248
5249    //     workspace.update(cx, |workspace, cx| {
5250    //         // Since panel_1 was visible on the left, we close the left dock.
5251    //         assert!(!workspace.left_dock().read(cx).is_open());
5252    //         // The bottom dock is sized based on the panel's default size,
5253    //         // since the panel orientation changed from vertical to horizontal.
5254    //         let bottom_dock = workspace.bottom_dock();
5255    //         assert_eq!(
5256    //             bottom_dock.read(cx).active_panel_size(cx).unwrap(),
5257    //             panel_1.size(cx),
5258    //         );
5259    //         // Close bottom dock and move panel_1 back to the left.
5260    //         bottom_dock.update(cx, |bottom_dock, cx| bottom_dock.set_open(false, cx));
5261    //         panel_1.set_position(DockPosition::Left, cx);
5262    //     });
5263
5264    //     // Emit activated event on panel 1
5265    //     panel_1.update(cx, |_, cx| cx.emit(PanelEvent::Activate));
5266
5267    //     // Now the left dock is open and panel_1 is active and focused.
5268    //     workspace.update(cx, |workspace, cx| {
5269    //         let left_dock = workspace.left_dock();
5270    //         assert!(left_dock.read(cx).is_open());
5271    //         assert_eq!(
5272    //             left_dock.read(cx).visible_panel().unwrap().panel_id(),
5273    //             panel_1.panel_id(),
5274    //         );
5275    //         assert!(panel_1.focus_handle(cx).is_focused(cx));
5276    //     });
5277
5278    //     // Emit closed event on panel 2, which is not active
5279    //     panel_2.update(cx, |_, cx| cx.emit(PanelEvent::Close));
5280
5281    //     // Wo don't close the left dock, because panel_2 wasn't the active panel
5282    //     workspace.update(cx, |workspace, cx| {
5283    //         let left_dock = workspace.left_dock();
5284    //         assert!(left_dock.read(cx).is_open());
5285    //         assert_eq!(
5286    //             left_dock.read(cx).visible_panel().unwrap().panel_id(),
5287    //             panel_1.panel_id(),
5288    //         );
5289    //     });
5290
5291    //     // Emitting a ZoomIn event shows the panel as zoomed.
5292    //     panel_1.update(cx, |_, cx| cx.emit(PanelEvent::ZoomIn));
5293    //     workspace.update(cx, |workspace, _| {
5294    //         assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
5295    //         assert_eq!(workspace.zoomed_position, Some(DockPosition::Left));
5296    //     });
5297
5298    //     // Move panel to another dock while it is zoomed
5299    //     panel_1.update(cx, |panel, cx| panel.set_position(DockPosition::Right, cx));
5300    //     workspace.update(cx, |workspace, _| {
5301    //         assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
5302
5303    //         assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5304    //     });
5305
5306    //     // If focus is transferred to another view that's not a panel or another pane, we still show
5307    //     // the panel as zoomed.
5308    //     let other_focus_handle = cx.update(|cx| cx.focus_handle());
5309    //     cx.update(|cx| cx.focus(&other_focus_handle));
5310    //     workspace.update(cx, |workspace, _| {
5311    //         assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
5312    //         assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5313    //     });
5314
5315    //     // If focus is transferred elsewhere in the workspace, the panel is no longer zoomed.
5316    //     workspace.update(cx, |_, cx| cx.focus_self());
5317    //     workspace.update(cx, |workspace, _| {
5318    //         assert_eq!(workspace.zoomed, None);
5319    //         assert_eq!(workspace.zoomed_position, None);
5320    //     });
5321
5322    //     // If focus is transferred again to another view that's not a panel or a pane, we won't
5323    //     // show the panel as zoomed because it wasn't zoomed before.
5324    //     cx.update(|cx| cx.focus(&other_focus_handle));
5325    //     workspace.update(cx, |workspace, _| {
5326    //         assert_eq!(workspace.zoomed, None);
5327    //         assert_eq!(workspace.zoomed_position, None);
5328    //     });
5329
5330    //     // When focus is transferred back to the panel, it is zoomed again.
5331    //     panel_1.update(cx, |_, cx| cx.focus_self());
5332    //     workspace.update(cx, |workspace, _| {
5333    //         assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
5334    //         assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5335    //     });
5336
5337    //     // Emitting a ZoomOut event unzooms the panel.
5338    //     panel_1.update(cx, |_, cx| cx.emit(PanelEvent::ZoomOut));
5339    //     workspace.update(cx, |workspace, _| {
5340    //         assert_eq!(workspace.zoomed, None);
5341    //         assert_eq!(workspace.zoomed_position, None);
5342    //     });
5343
5344    //     // Emit closed event on panel 1, which is active
5345    //     panel_1.update(cx, |_, cx| cx.emit(PanelEvent::Close));
5346
5347    //     // Now the left dock is closed, because panel_1 was the active panel
5348    //     workspace.update(cx, |workspace, cx| {
5349    //         let right_dock = workspace.right_dock();
5350    //         assert!(!right_dock.read(cx).is_open());
5351    //     });
5352    // }
5353
5354    pub fn init_test(cx: &mut TestAppContext) {
5355        cx.update(|cx| {
5356            let settings_store = SettingsStore::test(cx);
5357            cx.set_global(settings_store);
5358            theme::init(theme::LoadThemes::JustBase, cx);
5359            language::init(cx);
5360            crate::init_settings(cx);
5361            Project::init_settings(cx);
5362        });
5363    }
5364}