workspace2.rs

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