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