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