workspace.rs

   1pub mod dock;
   2pub mod history_manager;
   3pub mod item;
   4mod modal_layer;
   5pub mod notifications;
   6pub mod pane;
   7pub mod pane_group;
   8mod persistence;
   9pub mod searchable;
  10pub mod shared_screen;
  11mod status_bar;
  12pub mod tasks;
  13mod theme_preview;
  14mod toast_layer;
  15mod toolbar;
  16mod workspace_settings;
  17
  18pub use toast_layer::{RunAction, ToastAction, ToastLayer, ToastView};
  19
  20use anyhow::{Context as _, Result, anyhow};
  21use call::{ActiveCall, call_settings::CallSettings};
  22use client::{
  23    ChannelId, Client, ErrorExt, Status, TypedEnvelope, UserStore,
  24    proto::{self, ErrorCode, PanelId, PeerId},
  25};
  26use collections::{HashMap, HashSet, hash_map};
  27pub use dock::Panel;
  28use dock::{Dock, DockPosition, PanelButtons, PanelHandle, RESIZE_HANDLE_SIZE};
  29use futures::{
  30    Future, FutureExt, StreamExt,
  31    channel::{
  32        mpsc::{self, UnboundedReceiver, UnboundedSender},
  33        oneshot,
  34    },
  35    future::try_join_all,
  36};
  37use gpui::{
  38    Action, AnyEntity, AnyView, AnyWeakView, App, AsyncApp, AsyncWindowContext, Bounds, Context,
  39    CursorStyle, Decorations, DragMoveEvent, Entity, EntityId, EventEmitter, FocusHandle,
  40    Focusable, Global, Hsla, KeyContext, Keystroke, ManagedView, MouseButton, PathPromptOptions,
  41    Point, PromptLevel, Render, ResizeEdge, Size, Stateful, Subscription, Task, Tiling, WeakEntity,
  42    WindowBounds, WindowHandle, WindowId, WindowOptions, action_as, actions, canvas,
  43    impl_action_as, impl_actions, point, relative, size, transparent_black,
  44};
  45pub use history_manager::*;
  46pub use item::{
  47    FollowableItem, FollowableItemHandle, Item, ItemHandle, ItemSettings, PreviewTabsSettings,
  48    ProjectItem, SerializableItem, SerializableItemHandle, WeakItemHandle,
  49};
  50use itertools::Itertools;
  51use language::{Buffer, LanguageRegistry, Rope};
  52pub use modal_layer::*;
  53use node_runtime::NodeRuntime;
  54use notifications::{
  55    DetachAndPromptErr, Notifications, dismiss_app_notification,
  56    simple_message_notification::MessageNotification,
  57};
  58pub use pane::*;
  59pub use pane_group::*;
  60use persistence::{
  61    DB, SerializedWindowBounds,
  62    model::{SerializedSshProject, SerializedWorkspace},
  63};
  64pub use persistence::{
  65    DB as WORKSPACE_DB, WorkspaceDb, delete_unloaded_items,
  66    model::{ItemId, LocalPaths, SerializedWorkspaceLocation},
  67};
  68use postage::stream::Stream;
  69use project::{
  70    DirectoryLister, Project, ProjectEntryId, ProjectPath, ResolvedPath, Worktree, WorktreeId,
  71    debugger::breakpoint_store::BreakpointStoreEvent,
  72};
  73use remote::{SshClientDelegate, SshConnectionOptions, ssh_session::ConnectionIdentifier};
  74use schemars::JsonSchema;
  75use serde::Deserialize;
  76use session::AppSession;
  77use settings::Settings;
  78use shared_screen::SharedScreen;
  79use sqlez::{
  80    bindable::{Bind, Column, StaticColumnCount},
  81    statement::Statement,
  82};
  83use status_bar::StatusBar;
  84pub use status_bar::StatusItemView;
  85use std::{
  86    any::TypeId,
  87    borrow::Cow,
  88    cell::RefCell,
  89    cmp,
  90    collections::hash_map::DefaultHasher,
  91    env,
  92    hash::{Hash, Hasher},
  93    path::{Path, PathBuf},
  94    process::ExitStatus,
  95    rc::Rc,
  96    sync::{Arc, LazyLock, Weak, atomic::AtomicUsize},
  97    time::Duration,
  98};
  99use task::{DebugScenario, SpawnInTerminal, TaskContext};
 100use theme::{ActiveTheme, SystemAppearance, ThemeSettings};
 101pub use toolbar::{Toolbar, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView};
 102pub use ui;
 103use ui::prelude::*;
 104use util::{ResultExt, TryFutureExt, paths::SanitizedPath, serde::default_true};
 105use uuid::Uuid;
 106pub use workspace_settings::{
 107    AutosaveSetting, BottomDockLayout, RestoreOnStartupBehavior, TabBarSettings, WorkspaceSettings,
 108};
 109use zed_actions::feedback::FileBugReport;
 110
 111use crate::notifications::NotificationId;
 112use crate::persistence::{
 113    SerializedAxis,
 114    model::{DockData, DockStructure, SerializedItem, SerializedPane, SerializedPaneGroup},
 115};
 116
 117pub const SERIALIZATION_THROTTLE_TIME: Duration = Duration::from_millis(200);
 118
 119static ZED_WINDOW_SIZE: LazyLock<Option<Size<Pixels>>> = LazyLock::new(|| {
 120    env::var("ZED_WINDOW_SIZE")
 121        .ok()
 122        .as_deref()
 123        .and_then(parse_pixel_size_env_var)
 124});
 125
 126static ZED_WINDOW_POSITION: LazyLock<Option<Point<Pixels>>> = LazyLock::new(|| {
 127    env::var("ZED_WINDOW_POSITION")
 128        .ok()
 129        .as_deref()
 130        .and_then(parse_pixel_position_env_var)
 131});
 132
 133pub trait TerminalProvider {
 134    fn spawn(
 135        &self,
 136        task: SpawnInTerminal,
 137        window: &mut Window,
 138        cx: &mut App,
 139    ) -> Task<Option<Result<ExitStatus>>>;
 140}
 141
 142pub trait DebuggerProvider {
 143    // `active_buffer` is used to resolve build task's name against language-specific tasks.
 144    fn start_session(
 145        &self,
 146        definition: DebugScenario,
 147        task_context: TaskContext,
 148        active_buffer: Option<Entity<Buffer>>,
 149        window: &mut Window,
 150        cx: &mut App,
 151    );
 152}
 153
 154actions!(
 155    workspace,
 156    [
 157        ActivateNextPane,
 158        ActivatePreviousPane,
 159        ActivateNextWindow,
 160        ActivatePreviousWindow,
 161        AddFolderToProject,
 162        ClearAllNotifications,
 163        CloseActiveDock,
 164        CloseAllDocks,
 165        CloseWindow,
 166        Feedback,
 167        FollowNextCollaborator,
 168        MoveFocusedPanelToNextPosition,
 169        NewCenterTerminal,
 170        NewFile,
 171        NewFileSplitVertical,
 172        NewFileSplitHorizontal,
 173        NewSearch,
 174        NewTerminal,
 175        NewWindow,
 176        Open,
 177        OpenFiles,
 178        OpenInTerminal,
 179        OpenComponentPreview,
 180        ReloadActiveItem,
 181        SaveAs,
 182        SaveWithoutFormat,
 183        ShutdownDebugAdapters,
 184        SuppressNotification,
 185        ToggleBottomDock,
 186        ToggleCenteredLayout,
 187        ToggleLeftDock,
 188        ToggleRightDock,
 189        ToggleZoom,
 190        Unfollow,
 191        Welcome,
 192        RestoreBanner,
 193    ]
 194);
 195
 196#[derive(Clone, PartialEq)]
 197pub struct OpenPaths {
 198    pub paths: Vec<PathBuf>,
 199}
 200
 201#[derive(Clone, Deserialize, PartialEq, JsonSchema)]
 202pub struct ActivatePane(pub usize);
 203
 204#[derive(Clone, Deserialize, PartialEq, JsonSchema)]
 205#[serde(deny_unknown_fields)]
 206pub struct MoveItemToPane {
 207    pub destination: usize,
 208    #[serde(default = "default_true")]
 209    pub focus: bool,
 210}
 211
 212#[derive(Clone, Deserialize, PartialEq, JsonSchema)]
 213#[serde(deny_unknown_fields)]
 214pub struct MoveItemToPaneInDirection {
 215    pub direction: SplitDirection,
 216    #[serde(default = "default_true")]
 217    pub focus: bool,
 218}
 219
 220#[derive(Clone, PartialEq, Debug, Deserialize, JsonSchema)]
 221#[serde(deny_unknown_fields)]
 222pub struct SaveAll {
 223    pub save_intent: Option<SaveIntent>,
 224}
 225
 226#[derive(Clone, PartialEq, Debug, Deserialize, JsonSchema)]
 227#[serde(deny_unknown_fields)]
 228pub struct Save {
 229    pub save_intent: Option<SaveIntent>,
 230}
 231
 232#[derive(Clone, PartialEq, Debug, Deserialize, Default, JsonSchema)]
 233#[serde(deny_unknown_fields)]
 234pub struct CloseAllItemsAndPanes {
 235    pub save_intent: Option<SaveIntent>,
 236}
 237
 238#[derive(Clone, PartialEq, Debug, Deserialize, Default, JsonSchema)]
 239#[serde(deny_unknown_fields)]
 240pub struct CloseInactiveTabsAndPanes {
 241    pub save_intent: Option<SaveIntent>,
 242}
 243
 244#[derive(Clone, Deserialize, PartialEq, JsonSchema)]
 245pub struct SendKeystrokes(pub String);
 246
 247#[derive(Clone, Deserialize, PartialEq, Default, JsonSchema)]
 248#[serde(deny_unknown_fields)]
 249pub struct Reload {
 250    pub binary_path: Option<PathBuf>,
 251}
 252
 253action_as!(project_symbols, ToggleProjectSymbols as Toggle);
 254
 255#[derive(Default, PartialEq, Eq, Clone, Deserialize, JsonSchema)]
 256pub struct ToggleFileFinder {
 257    #[serde(default)]
 258    pub separate_history: bool,
 259}
 260
 261impl_action_as!(file_finder, ToggleFileFinder as Toggle);
 262
 263impl_actions!(
 264    workspace,
 265    [
 266        ActivatePane,
 267        CloseAllItemsAndPanes,
 268        CloseInactiveTabsAndPanes,
 269        MoveItemToPane,
 270        MoveItemToPaneInDirection,
 271        OpenTerminal,
 272        Reload,
 273        Save,
 274        SaveAll,
 275        SendKeystrokes,
 276    ]
 277);
 278
 279actions!(
 280    workspace,
 281    [
 282        ActivatePaneLeft,
 283        ActivatePaneRight,
 284        ActivatePaneUp,
 285        ActivatePaneDown,
 286        SwapPaneLeft,
 287        SwapPaneRight,
 288        SwapPaneUp,
 289        SwapPaneDown,
 290    ]
 291);
 292
 293#[derive(PartialEq, Eq, Debug)]
 294pub enum CloseIntent {
 295    /// Quit the program entirely.
 296    Quit,
 297    /// Close a window.
 298    CloseWindow,
 299    /// Replace the workspace in an existing window.
 300    ReplaceWindow,
 301}
 302
 303#[derive(Clone)]
 304pub struct Toast {
 305    id: NotificationId,
 306    msg: Cow<'static, str>,
 307    autohide: bool,
 308    on_click: Option<(Cow<'static, str>, Arc<dyn Fn(&mut Window, &mut App)>)>,
 309}
 310
 311impl Toast {
 312    pub fn new<I: Into<Cow<'static, str>>>(id: NotificationId, msg: I) -> Self {
 313        Toast {
 314            id,
 315            msg: msg.into(),
 316            on_click: None,
 317            autohide: false,
 318        }
 319    }
 320
 321    pub fn on_click<F, M>(mut self, message: M, on_click: F) -> Self
 322    where
 323        M: Into<Cow<'static, str>>,
 324        F: Fn(&mut Window, &mut App) + 'static,
 325    {
 326        self.on_click = Some((message.into(), Arc::new(on_click)));
 327        self
 328    }
 329
 330    pub fn autohide(mut self) -> Self {
 331        self.autohide = true;
 332        self
 333    }
 334}
 335
 336impl PartialEq for Toast {
 337    fn eq(&self, other: &Self) -> bool {
 338        self.id == other.id
 339            && self.msg == other.msg
 340            && self.on_click.is_some() == other.on_click.is_some()
 341    }
 342}
 343
 344#[derive(Debug, Default, Clone, Deserialize, PartialEq, JsonSchema)]
 345#[serde(deny_unknown_fields)]
 346pub struct OpenTerminal {
 347    pub working_directory: PathBuf,
 348}
 349
 350#[derive(Clone, Copy, Debug, Default, Hash, PartialEq, Eq, PartialOrd, Ord)]
 351pub struct WorkspaceId(i64);
 352
 353impl StaticColumnCount for WorkspaceId {}
 354impl Bind for WorkspaceId {
 355    fn bind(&self, statement: &Statement, start_index: i32) -> Result<i32> {
 356        self.0.bind(statement, start_index)
 357    }
 358}
 359impl Column for WorkspaceId {
 360    fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> {
 361        i64::column(statement, start_index)
 362            .map(|(i, next_index)| (Self(i), next_index))
 363            .with_context(|| format!("Failed to read WorkspaceId at index {start_index}"))
 364    }
 365}
 366impl From<WorkspaceId> for i64 {
 367    fn from(val: WorkspaceId) -> Self {
 368        val.0
 369    }
 370}
 371
 372pub fn init_settings(cx: &mut App) {
 373    WorkspaceSettings::register(cx);
 374    ItemSettings::register(cx);
 375    PreviewTabsSettings::register(cx);
 376    TabBarSettings::register(cx);
 377}
 378
 379fn prompt_and_open_paths(app_state: Arc<AppState>, options: PathPromptOptions, cx: &mut App) {
 380    let paths = cx.prompt_for_paths(options);
 381    cx.spawn(
 382        async move |cx| match paths.await.anyhow().and_then(|res| res) {
 383            Ok(Some(paths)) => {
 384                cx.update(|cx| {
 385                    open_paths(&paths, app_state, OpenOptions::default(), cx).detach_and_log_err(cx)
 386                })
 387                .ok();
 388            }
 389            Ok(None) => {}
 390            Err(err) => {
 391                util::log_err(&err);
 392                cx.update(|cx| {
 393                    if let Some(workspace_window) = cx
 394                        .active_window()
 395                        .and_then(|window| window.downcast::<Workspace>())
 396                    {
 397                        workspace_window
 398                            .update(cx, |workspace, _, cx| {
 399                                workspace.show_portal_error(err.to_string(), cx);
 400                            })
 401                            .ok();
 402                    }
 403                })
 404                .ok();
 405            }
 406        },
 407    )
 408    .detach();
 409}
 410
 411pub fn init(app_state: Arc<AppState>, cx: &mut App) {
 412    init_settings(cx);
 413    component::init();
 414    theme_preview::init(cx);
 415    toast_layer::init(cx);
 416    history_manager::init(cx);
 417
 418    cx.on_action(Workspace::close_global);
 419    cx.on_action(reload);
 420
 421    cx.on_action({
 422        let app_state = Arc::downgrade(&app_state);
 423        move |_: &Open, cx: &mut App| {
 424            if let Some(app_state) = app_state.upgrade() {
 425                prompt_and_open_paths(
 426                    app_state,
 427                    PathPromptOptions {
 428                        files: true,
 429                        directories: true,
 430                        multiple: true,
 431                    },
 432                    cx,
 433                );
 434            }
 435        }
 436    });
 437    cx.on_action({
 438        let app_state = Arc::downgrade(&app_state);
 439        move |_: &OpenFiles, cx: &mut App| {
 440            let directories = cx.can_select_mixed_files_and_dirs();
 441            if let Some(app_state) = app_state.upgrade() {
 442                prompt_and_open_paths(
 443                    app_state,
 444                    PathPromptOptions {
 445                        files: true,
 446                        directories,
 447                        multiple: true,
 448                    },
 449                    cx,
 450                );
 451            }
 452        }
 453    });
 454}
 455
 456type BuildProjectItemFn =
 457    fn(AnyEntity, Entity<Project>, Option<&Pane>, &mut Window, &mut App) -> Box<dyn ItemHandle>;
 458
 459type BuildProjectItemForPathFn =
 460    fn(
 461        &Entity<Project>,
 462        &ProjectPath,
 463        &mut Window,
 464        &mut App,
 465    ) -> Option<Task<Result<(Option<ProjectEntryId>, WorkspaceItemBuilder)>>>;
 466
 467#[derive(Clone, Default)]
 468struct ProjectItemRegistry {
 469    build_project_item_fns_by_type: HashMap<TypeId, BuildProjectItemFn>,
 470    build_project_item_for_path_fns: Vec<BuildProjectItemForPathFn>,
 471}
 472
 473impl ProjectItemRegistry {
 474    fn register<T: ProjectItem>(&mut self) {
 475        self.build_project_item_fns_by_type.insert(
 476            TypeId::of::<T::Item>(),
 477            |item, project, pane, window, cx| {
 478                let item = item.downcast().unwrap();
 479                Box::new(cx.new(|cx| T::for_project_item(project, pane, item, window, cx)))
 480                    as Box<dyn ItemHandle>
 481            },
 482        );
 483        self.build_project_item_for_path_fns
 484            .push(|project, project_path, window, cx| {
 485                let project_item =
 486                    <T::Item as project::ProjectItem>::try_open(project, project_path, cx)?;
 487                let project = project.clone();
 488                Some(window.spawn(cx, async move |cx| {
 489                    let project_item = project_item.await?;
 490                    let project_entry_id: Option<ProjectEntryId> =
 491                        project_item.read_with(cx, project::ProjectItem::entry_id)?;
 492                    let build_workspace_item = Box::new(
 493                        |pane: &mut Pane, window: &mut Window, cx: &mut Context<Pane>| {
 494                            Box::new(cx.new(|cx| {
 495                                T::for_project_item(project, Some(pane), project_item, window, cx)
 496                            })) as Box<dyn ItemHandle>
 497                        },
 498                    ) as Box<_>;
 499                    Ok((project_entry_id, build_workspace_item))
 500                }))
 501            });
 502    }
 503
 504    fn open_path(
 505        &self,
 506        project: &Entity<Project>,
 507        path: &ProjectPath,
 508        window: &mut Window,
 509        cx: &mut App,
 510    ) -> Task<Result<(Option<ProjectEntryId>, WorkspaceItemBuilder)>> {
 511        let Some(open_project_item) = self
 512            .build_project_item_for_path_fns
 513            .iter()
 514            .rev()
 515            .find_map(|open_project_item| open_project_item(&project, &path, window, cx))
 516        else {
 517            return Task::ready(Err(anyhow!("cannot open file {:?}", path.path)));
 518        };
 519        open_project_item
 520    }
 521
 522    fn build_item<T: project::ProjectItem>(
 523        &self,
 524        item: Entity<T>,
 525        project: Entity<Project>,
 526        pane: Option<&Pane>,
 527        window: &mut Window,
 528        cx: &mut App,
 529    ) -> Option<Box<dyn ItemHandle>> {
 530        let build = self
 531            .build_project_item_fns_by_type
 532            .get(&TypeId::of::<T>())?;
 533        Some(build(item.into_any(), project, pane, window, cx))
 534    }
 535}
 536
 537type WorkspaceItemBuilder =
 538    Box<dyn FnOnce(&mut Pane, &mut Window, &mut Context<Pane>) -> Box<dyn ItemHandle>>;
 539
 540impl Global for ProjectItemRegistry {}
 541
 542/// Registers a [ProjectItem] for the app. When opening a file, all the registered
 543/// items will get a chance to open the file, starting from the project item that
 544/// was added last.
 545pub fn register_project_item<I: ProjectItem>(cx: &mut App) {
 546    cx.default_global::<ProjectItemRegistry>().register::<I>();
 547}
 548
 549#[derive(Default)]
 550pub struct FollowableViewRegistry(HashMap<TypeId, FollowableViewDescriptor>);
 551
 552struct FollowableViewDescriptor {
 553    from_state_proto: fn(
 554        Entity<Workspace>,
 555        ViewId,
 556        &mut Option<proto::view::Variant>,
 557        &mut Window,
 558        &mut App,
 559    ) -> Option<Task<Result<Box<dyn FollowableItemHandle>>>>,
 560    to_followable_view: fn(&AnyView) -> Box<dyn FollowableItemHandle>,
 561}
 562
 563impl Global for FollowableViewRegistry {}
 564
 565impl FollowableViewRegistry {
 566    pub fn register<I: FollowableItem>(cx: &mut App) {
 567        cx.default_global::<Self>().0.insert(
 568            TypeId::of::<I>(),
 569            FollowableViewDescriptor {
 570                from_state_proto: |workspace, id, state, window, cx| {
 571                    I::from_state_proto(workspace, id, state, window, cx).map(|task| {
 572                        cx.foreground_executor()
 573                            .spawn(async move { Ok(Box::new(task.await?) as Box<_>) })
 574                    })
 575                },
 576                to_followable_view: |view| Box::new(view.clone().downcast::<I>().unwrap()),
 577            },
 578        );
 579    }
 580
 581    pub fn from_state_proto(
 582        workspace: Entity<Workspace>,
 583        view_id: ViewId,
 584        mut state: Option<proto::view::Variant>,
 585        window: &mut Window,
 586        cx: &mut App,
 587    ) -> Option<Task<Result<Box<dyn FollowableItemHandle>>>> {
 588        cx.update_default_global(|this: &mut Self, cx| {
 589            this.0.values().find_map(|descriptor| {
 590                (descriptor.from_state_proto)(workspace.clone(), view_id, &mut state, window, cx)
 591            })
 592        })
 593    }
 594
 595    pub fn to_followable_view(
 596        view: impl Into<AnyView>,
 597        cx: &App,
 598    ) -> Option<Box<dyn FollowableItemHandle>> {
 599        let this = cx.try_global::<Self>()?;
 600        let view = view.into();
 601        let descriptor = this.0.get(&view.entity_type())?;
 602        Some((descriptor.to_followable_view)(&view))
 603    }
 604}
 605
 606#[derive(Copy, Clone)]
 607struct SerializableItemDescriptor {
 608    deserialize: fn(
 609        Entity<Project>,
 610        WeakEntity<Workspace>,
 611        WorkspaceId,
 612        ItemId,
 613        &mut Window,
 614        &mut Context<Pane>,
 615    ) -> Task<Result<Box<dyn ItemHandle>>>,
 616    cleanup: fn(WorkspaceId, Vec<ItemId>, &mut Window, &mut App) -> Task<Result<()>>,
 617    view_to_serializable_item: fn(AnyView) -> Box<dyn SerializableItemHandle>,
 618}
 619
 620#[derive(Default)]
 621struct SerializableItemRegistry {
 622    descriptors_by_kind: HashMap<Arc<str>, SerializableItemDescriptor>,
 623    descriptors_by_type: HashMap<TypeId, SerializableItemDescriptor>,
 624}
 625
 626impl Global for SerializableItemRegistry {}
 627
 628impl SerializableItemRegistry {
 629    fn deserialize(
 630        item_kind: &str,
 631        project: Entity<Project>,
 632        workspace: WeakEntity<Workspace>,
 633        workspace_id: WorkspaceId,
 634        item_item: ItemId,
 635        window: &mut Window,
 636        cx: &mut Context<Pane>,
 637    ) -> Task<Result<Box<dyn ItemHandle>>> {
 638        let Some(descriptor) = Self::descriptor(item_kind, cx) else {
 639            return Task::ready(Err(anyhow!(
 640                "cannot deserialize {}, descriptor not found",
 641                item_kind
 642            )));
 643        };
 644
 645        (descriptor.deserialize)(project, workspace, workspace_id, item_item, window, cx)
 646    }
 647
 648    fn cleanup(
 649        item_kind: &str,
 650        workspace_id: WorkspaceId,
 651        loaded_items: Vec<ItemId>,
 652        window: &mut Window,
 653        cx: &mut App,
 654    ) -> Task<Result<()>> {
 655        let Some(descriptor) = Self::descriptor(item_kind, cx) else {
 656            return Task::ready(Err(anyhow!(
 657                "cannot cleanup {}, descriptor not found",
 658                item_kind
 659            )));
 660        };
 661
 662        (descriptor.cleanup)(workspace_id, loaded_items, window, cx)
 663    }
 664
 665    fn view_to_serializable_item_handle(
 666        view: AnyView,
 667        cx: &App,
 668    ) -> Option<Box<dyn SerializableItemHandle>> {
 669        let this = cx.try_global::<Self>()?;
 670        let descriptor = this.descriptors_by_type.get(&view.entity_type())?;
 671        Some((descriptor.view_to_serializable_item)(view))
 672    }
 673
 674    fn descriptor(item_kind: &str, cx: &App) -> Option<SerializableItemDescriptor> {
 675        let this = cx.try_global::<Self>()?;
 676        this.descriptors_by_kind.get(item_kind).copied()
 677    }
 678}
 679
 680pub fn register_serializable_item<I: SerializableItem>(cx: &mut App) {
 681    let serialized_item_kind = I::serialized_item_kind();
 682
 683    let registry = cx.default_global::<SerializableItemRegistry>();
 684    let descriptor = SerializableItemDescriptor {
 685        deserialize: |project, workspace, workspace_id, item_id, window, cx| {
 686            let task = I::deserialize(project, workspace, workspace_id, item_id, window, cx);
 687            cx.foreground_executor()
 688                .spawn(async { Ok(Box::new(task.await?) as Box<_>) })
 689        },
 690        cleanup: |workspace_id, loaded_items, window, cx| {
 691            I::cleanup(workspace_id, loaded_items, window, cx)
 692        },
 693        view_to_serializable_item: |view| Box::new(view.downcast::<I>().unwrap()),
 694    };
 695    registry
 696        .descriptors_by_kind
 697        .insert(Arc::from(serialized_item_kind), descriptor);
 698    registry
 699        .descriptors_by_type
 700        .insert(TypeId::of::<I>(), descriptor);
 701}
 702
 703pub struct AppState {
 704    pub languages: Arc<LanguageRegistry>,
 705    pub client: Arc<Client>,
 706    pub user_store: Entity<UserStore>,
 707    pub workspace_store: Entity<WorkspaceStore>,
 708    pub fs: Arc<dyn fs::Fs>,
 709    pub build_window_options: fn(Option<Uuid>, &mut App) -> WindowOptions,
 710    pub node_runtime: NodeRuntime,
 711    pub session: Entity<AppSession>,
 712}
 713
 714struct GlobalAppState(Weak<AppState>);
 715
 716impl Global for GlobalAppState {}
 717
 718pub struct WorkspaceStore {
 719    workspaces: HashSet<WindowHandle<Workspace>>,
 720    client: Arc<Client>,
 721    _subscriptions: Vec<client::Subscription>,
 722}
 723
 724#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, PartialOrd, Ord)]
 725pub enum CollaboratorId {
 726    PeerId(PeerId),
 727    Agent,
 728}
 729
 730impl From<PeerId> for CollaboratorId {
 731    fn from(peer_id: PeerId) -> Self {
 732        CollaboratorId::PeerId(peer_id)
 733    }
 734}
 735
 736impl From<&PeerId> for CollaboratorId {
 737    fn from(peer_id: &PeerId) -> Self {
 738        CollaboratorId::PeerId(*peer_id)
 739    }
 740}
 741
 742#[derive(PartialEq, Eq, PartialOrd, Ord, Debug)]
 743struct Follower {
 744    project_id: Option<u64>,
 745    peer_id: PeerId,
 746}
 747
 748impl AppState {
 749    #[track_caller]
 750    pub fn global(cx: &App) -> Weak<Self> {
 751        cx.global::<GlobalAppState>().0.clone()
 752    }
 753    pub fn try_global(cx: &App) -> Option<Weak<Self>> {
 754        cx.try_global::<GlobalAppState>()
 755            .map(|state| state.0.clone())
 756    }
 757    pub fn set_global(state: Weak<AppState>, cx: &mut App) {
 758        cx.set_global(GlobalAppState(state));
 759    }
 760
 761    #[cfg(any(test, feature = "test-support"))]
 762    pub fn test(cx: &mut App) -> Arc<Self> {
 763        use node_runtime::NodeRuntime;
 764        use session::Session;
 765        use settings::SettingsStore;
 766
 767        if !cx.has_global::<SettingsStore>() {
 768            let settings_store = SettingsStore::test(cx);
 769            cx.set_global(settings_store);
 770        }
 771
 772        let fs = fs::FakeFs::new(cx.background_executor().clone());
 773        let languages = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
 774        let clock = Arc::new(clock::FakeSystemClock::new());
 775        let http_client = http_client::FakeHttpClient::with_404_response();
 776        let client = Client::new(clock, http_client.clone(), cx);
 777        let session = cx.new(|cx| AppSession::new(Session::test(), cx));
 778        let user_store = cx.new(|cx| UserStore::new(client.clone(), cx));
 779        let workspace_store = cx.new(|cx| WorkspaceStore::new(client.clone(), cx));
 780
 781        theme::init(theme::LoadThemes::JustBase, cx);
 782        client::init(&client, cx);
 783        crate::init_settings(cx);
 784
 785        Arc::new(Self {
 786            client,
 787            fs,
 788            languages,
 789            user_store,
 790            workspace_store,
 791            node_runtime: NodeRuntime::unavailable(),
 792            build_window_options: |_, _| Default::default(),
 793            session,
 794        })
 795    }
 796}
 797
 798struct DelayedDebouncedEditAction {
 799    task: Option<Task<()>>,
 800    cancel_channel: Option<oneshot::Sender<()>>,
 801}
 802
 803impl DelayedDebouncedEditAction {
 804    fn new() -> DelayedDebouncedEditAction {
 805        DelayedDebouncedEditAction {
 806            task: None,
 807            cancel_channel: None,
 808        }
 809    }
 810
 811    fn fire_new<F>(
 812        &mut self,
 813        delay: Duration,
 814        window: &mut Window,
 815        cx: &mut Context<Workspace>,
 816        func: F,
 817    ) where
 818        F: 'static
 819            + Send
 820            + FnOnce(&mut Workspace, &mut Window, &mut Context<Workspace>) -> Task<Result<()>>,
 821    {
 822        if let Some(channel) = self.cancel_channel.take() {
 823            _ = channel.send(());
 824        }
 825
 826        let (sender, mut receiver) = oneshot::channel::<()>();
 827        self.cancel_channel = Some(sender);
 828
 829        let previous_task = self.task.take();
 830        self.task = Some(cx.spawn_in(window, async move |workspace, cx| {
 831            let mut timer = cx.background_executor().timer(delay).fuse();
 832            if let Some(previous_task) = previous_task {
 833                previous_task.await;
 834            }
 835
 836            futures::select_biased! {
 837                _ = receiver => return,
 838                    _ = timer => {}
 839            }
 840
 841            if let Some(result) = workspace
 842                .update_in(cx, |workspace, window, cx| (func)(workspace, window, cx))
 843                .log_err()
 844            {
 845                result.await.log_err();
 846            }
 847        }));
 848    }
 849}
 850
 851pub enum Event {
 852    PaneAdded(Entity<Pane>),
 853    PaneRemoved,
 854    ItemAdded {
 855        item: Box<dyn ItemHandle>,
 856    },
 857    ItemRemoved,
 858    ActiveItemChanged,
 859    UserSavedItem {
 860        pane: WeakEntity<Pane>,
 861        item: Box<dyn WeakItemHandle>,
 862        save_intent: SaveIntent,
 863    },
 864    ContactRequestedJoin(u64),
 865    WorkspaceCreated(WeakEntity<Workspace>),
 866    OpenBundledFile {
 867        text: Cow<'static, str>,
 868        title: &'static str,
 869        language: &'static str,
 870    },
 871    ZoomChanged,
 872    ModalOpened,
 873    ClearActivityIndicator,
 874}
 875
 876#[derive(Debug)]
 877pub enum OpenVisible {
 878    All,
 879    None,
 880    OnlyFiles,
 881    OnlyDirectories,
 882}
 883
 884type PromptForNewPath = Box<
 885    dyn Fn(
 886        &mut Workspace,
 887        &mut Window,
 888        &mut Context<Workspace>,
 889    ) -> oneshot::Receiver<Option<ProjectPath>>,
 890>;
 891
 892type PromptForOpenPath = Box<
 893    dyn Fn(
 894        &mut Workspace,
 895        DirectoryLister,
 896        &mut Window,
 897        &mut Context<Workspace>,
 898    ) -> oneshot::Receiver<Option<Vec<PathBuf>>>,
 899>;
 900
 901/// Collects everything project-related for a certain window opened.
 902/// In some way, is a counterpart of a window, as the [`WindowHandle`] could be downcast into `Workspace`.
 903///
 904/// A `Workspace` usually consists of 1 or more projects, a central pane group, 3 docks and a status bar.
 905/// The `Workspace` owns everybody's state and serves as a default, "global context",
 906/// that can be used to register a global action to be triggered from any place in the window.
 907pub struct Workspace {
 908    weak_self: WeakEntity<Self>,
 909    workspace_actions: Vec<Box<dyn Fn(Div, &mut Window, &mut Context<Self>) -> Div>>,
 910    zoomed: Option<AnyWeakView>,
 911    previous_dock_drag_coordinates: Option<Point<Pixels>>,
 912    zoomed_position: Option<DockPosition>,
 913    center: PaneGroup,
 914    left_dock: Entity<Dock>,
 915    bottom_dock: Entity<Dock>,
 916    bottom_dock_layout: BottomDockLayout,
 917    right_dock: Entity<Dock>,
 918    panes: Vec<Entity<Pane>>,
 919    panes_by_item: HashMap<EntityId, WeakEntity<Pane>>,
 920    active_pane: Entity<Pane>,
 921    last_active_center_pane: Option<WeakEntity<Pane>>,
 922    last_active_view_id: Option<proto::ViewId>,
 923    status_bar: Entity<StatusBar>,
 924    modal_layer: Entity<ModalLayer>,
 925    toast_layer: Entity<ToastLayer>,
 926    titlebar_item: Option<AnyView>,
 927    notifications: Notifications,
 928    suppressed_notifications: HashSet<NotificationId>,
 929    project: Entity<Project>,
 930    follower_states: HashMap<CollaboratorId, FollowerState>,
 931    last_leaders_by_pane: HashMap<WeakEntity<Pane>, CollaboratorId>,
 932    window_edited: bool,
 933    dirty_items: HashMap<EntityId, Subscription>,
 934    active_call: Option<(Entity<ActiveCall>, Vec<Subscription>)>,
 935    leader_updates_tx: mpsc::UnboundedSender<(PeerId, proto::UpdateFollowers)>,
 936    database_id: Option<WorkspaceId>,
 937    app_state: Arc<AppState>,
 938    dispatching_keystrokes: Rc<RefCell<(HashSet<String>, Vec<Keystroke>)>>,
 939    _subscriptions: Vec<Subscription>,
 940    _apply_leader_updates: Task<Result<()>>,
 941    _observe_current_user: Task<Result<()>>,
 942    _schedule_serialize: Option<Task<()>>,
 943    pane_history_timestamp: Arc<AtomicUsize>,
 944    bounds: Bounds<Pixels>,
 945    pub centered_layout: bool,
 946    bounds_save_task_queued: Option<Task<()>>,
 947    on_prompt_for_new_path: Option<PromptForNewPath>,
 948    on_prompt_for_open_path: Option<PromptForOpenPath>,
 949    terminal_provider: Option<Box<dyn TerminalProvider>>,
 950    debugger_provider: Option<Box<dyn DebuggerProvider>>,
 951    serializable_items_tx: UnboundedSender<Box<dyn SerializableItemHandle>>,
 952    serialized_ssh_project: Option<SerializedSshProject>,
 953    _items_serializer: Task<Result<()>>,
 954    session_id: Option<String>,
 955}
 956
 957impl EventEmitter<Event> for Workspace {}
 958
 959#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
 960pub struct ViewId {
 961    pub creator: CollaboratorId,
 962    pub id: u64,
 963}
 964
 965pub struct FollowerState {
 966    center_pane: Entity<Pane>,
 967    dock_pane: Option<Entity<Pane>>,
 968    active_view_id: Option<ViewId>,
 969    items_by_leader_view_id: HashMap<ViewId, FollowerView>,
 970}
 971
 972struct FollowerView {
 973    view: Box<dyn FollowableItemHandle>,
 974    location: Option<proto::PanelId>,
 975}
 976
 977impl Workspace {
 978    const DEFAULT_PADDING: f32 = 0.2;
 979    const MAX_PADDING: f32 = 0.4;
 980
 981    pub fn new(
 982        workspace_id: Option<WorkspaceId>,
 983        project: Entity<Project>,
 984        app_state: Arc<AppState>,
 985        window: &mut Window,
 986        cx: &mut Context<Self>,
 987    ) -> Self {
 988        cx.subscribe_in(&project, window, move |this, _, event, window, cx| {
 989            match event {
 990                project::Event::RemoteIdChanged(_) => {
 991                    this.update_window_title(window, cx);
 992                }
 993
 994                project::Event::CollaboratorLeft(peer_id) => {
 995                    this.collaborator_left(*peer_id, window, cx);
 996                }
 997
 998                project::Event::WorktreeRemoved(_) | project::Event::WorktreeAdded(_) => {
 999                    this.update_window_title(window, cx);
1000                    this.serialize_workspace(window, cx);
1001                    // This event could be triggered by `AddFolderToProject` or `RemoveFromProject`.
1002                    // So we need to update the history.
1003                    this.update_history(cx);
1004                }
1005
1006                project::Event::DisconnectedFromHost => {
1007                    this.update_window_edited(window, cx);
1008                    let leaders_to_unfollow =
1009                        this.follower_states.keys().copied().collect::<Vec<_>>();
1010                    for leader_id in leaders_to_unfollow {
1011                        this.unfollow(leader_id, window, cx);
1012                    }
1013                }
1014
1015                project::Event::DisconnectedFromSshRemote => {
1016                    this.update_window_edited(window, cx);
1017                }
1018
1019                project::Event::Closed => {
1020                    window.remove_window();
1021                }
1022
1023                project::Event::DeletedEntry(_, entry_id) => {
1024                    for pane in this.panes.iter() {
1025                        pane.update(cx, |pane, cx| {
1026                            pane.handle_deleted_project_item(*entry_id, window, cx)
1027                        });
1028                    }
1029                }
1030
1031                project::Event::Toast {
1032                    notification_id,
1033                    message,
1034                } => this.show_notification(
1035                    NotificationId::named(notification_id.clone()),
1036                    cx,
1037                    |cx| cx.new(|cx| MessageNotification::new(message.clone(), cx)),
1038                ),
1039
1040                project::Event::HideToast { notification_id } => {
1041                    this.dismiss_notification(&NotificationId::named(notification_id.clone()), cx)
1042                }
1043
1044                project::Event::LanguageServerPrompt(request) => {
1045                    struct LanguageServerPrompt;
1046
1047                    let mut hasher = DefaultHasher::new();
1048                    request.lsp_name.as_str().hash(&mut hasher);
1049                    let id = hasher.finish();
1050
1051                    this.show_notification(
1052                        NotificationId::composite::<LanguageServerPrompt>(id as usize),
1053                        cx,
1054                        |cx| {
1055                            cx.new(|cx| {
1056                                notifications::LanguageServerPrompt::new(request.clone(), cx)
1057                            })
1058                        },
1059                    );
1060                }
1061
1062                project::Event::AgentLocationChanged => {
1063                    this.handle_agent_location_changed(window, cx)
1064                }
1065
1066                _ => {}
1067            }
1068            cx.notify()
1069        })
1070        .detach();
1071
1072        cx.subscribe_in(
1073            &project.read(cx).breakpoint_store(),
1074            window,
1075            |workspace, _, event, window, cx| match event {
1076                BreakpointStoreEvent::BreakpointsUpdated(_, _)
1077                | BreakpointStoreEvent::BreakpointsCleared(_) => {
1078                    workspace.serialize_workspace(window, cx);
1079                }
1080                BreakpointStoreEvent::SetDebugLine | BreakpointStoreEvent::ClearDebugLines => {}
1081            },
1082        )
1083        .detach();
1084
1085        cx.on_focus_lost(window, |this, window, cx| {
1086            let focus_handle = this.focus_handle(cx);
1087            window.focus(&focus_handle);
1088        })
1089        .detach();
1090
1091        let weak_handle = cx.entity().downgrade();
1092        let pane_history_timestamp = Arc::new(AtomicUsize::new(0));
1093
1094        let center_pane = cx.new(|cx| {
1095            let mut center_pane = Pane::new(
1096                weak_handle.clone(),
1097                project.clone(),
1098                pane_history_timestamp.clone(),
1099                None,
1100                NewFile.boxed_clone(),
1101                window,
1102                cx,
1103            );
1104            center_pane.set_can_split(Some(Arc::new(|_, _, _, _| true)));
1105            center_pane
1106        });
1107        cx.subscribe_in(&center_pane, window, Self::handle_pane_event)
1108            .detach();
1109
1110        window.focus(&center_pane.focus_handle(cx));
1111
1112        cx.emit(Event::PaneAdded(center_pane.clone()));
1113
1114        let window_handle = window.window_handle().downcast::<Workspace>().unwrap();
1115        app_state.workspace_store.update(cx, |store, _| {
1116            store.workspaces.insert(window_handle);
1117        });
1118
1119        let mut current_user = app_state.user_store.read(cx).watch_current_user();
1120        let mut connection_status = app_state.client.status();
1121        let _observe_current_user = cx.spawn_in(window, async move |this, cx| {
1122            current_user.next().await;
1123            connection_status.next().await;
1124            let mut stream =
1125                Stream::map(current_user, drop).merge(Stream::map(connection_status, drop));
1126
1127            while stream.recv().await.is_some() {
1128                this.update(cx, |_, cx| cx.notify())?;
1129            }
1130            anyhow::Ok(())
1131        });
1132
1133        // All leader updates are enqueued and then processed in a single task, so
1134        // that each asynchronous operation can be run in order.
1135        let (leader_updates_tx, mut leader_updates_rx) =
1136            mpsc::unbounded::<(PeerId, proto::UpdateFollowers)>();
1137        let _apply_leader_updates = cx.spawn_in(window, async move |this, cx| {
1138            while let Some((leader_id, update)) = leader_updates_rx.next().await {
1139                Self::process_leader_update(&this, leader_id, update, cx)
1140                    .await
1141                    .log_err();
1142            }
1143
1144            Ok(())
1145        });
1146
1147        cx.emit(Event::WorkspaceCreated(weak_handle.clone()));
1148        let modal_layer = cx.new(|_| ModalLayer::new());
1149        let toast_layer = cx.new(|_| ToastLayer::new());
1150        cx.subscribe(
1151            &modal_layer,
1152            |_, _, _: &modal_layer::ModalOpenedEvent, cx| {
1153                cx.emit(Event::ModalOpened);
1154            },
1155        )
1156        .detach();
1157
1158        let bottom_dock_layout = WorkspaceSettings::get_global(cx).bottom_dock_layout;
1159        let left_dock = Dock::new(DockPosition::Left, modal_layer.clone(), window, cx);
1160        let bottom_dock = Dock::new(DockPosition::Bottom, modal_layer.clone(), window, cx);
1161        let right_dock = Dock::new(DockPosition::Right, modal_layer.clone(), window, cx);
1162        let left_dock_buttons = cx.new(|cx| PanelButtons::new(left_dock.clone(), cx));
1163        let bottom_dock_buttons = cx.new(|cx| PanelButtons::new(bottom_dock.clone(), cx));
1164        let right_dock_buttons = cx.new(|cx| PanelButtons::new(right_dock.clone(), cx));
1165        let status_bar = cx.new(|cx| {
1166            let mut status_bar = StatusBar::new(&center_pane.clone(), window, cx);
1167            status_bar.add_left_item(left_dock_buttons, window, cx);
1168            status_bar.add_right_item(right_dock_buttons, window, cx);
1169            status_bar.add_right_item(bottom_dock_buttons, window, cx);
1170            status_bar
1171        });
1172
1173        let session_id = app_state.session.read(cx).id().to_owned();
1174
1175        let mut active_call = None;
1176        if let Some(call) = ActiveCall::try_global(cx) {
1177            let call = call.clone();
1178            let subscriptions = vec![cx.subscribe_in(&call, window, Self::on_active_call_event)];
1179            active_call = Some((call, subscriptions));
1180        }
1181
1182        let (serializable_items_tx, serializable_items_rx) =
1183            mpsc::unbounded::<Box<dyn SerializableItemHandle>>();
1184        let _items_serializer = cx.spawn_in(window, async move |this, cx| {
1185            Self::serialize_items(&this, serializable_items_rx, cx).await
1186        });
1187
1188        let subscriptions = vec![
1189            cx.observe_window_activation(window, Self::on_window_activation_changed),
1190            cx.observe_window_bounds(window, move |this, window, cx| {
1191                if this.bounds_save_task_queued.is_some() {
1192                    return;
1193                }
1194                this.bounds_save_task_queued = Some(cx.spawn_in(window, async move |this, cx| {
1195                    cx.background_executor()
1196                        .timer(Duration::from_millis(100))
1197                        .await;
1198                    this.update_in(cx, |this, window, cx| {
1199                        if let Some(display) = window.display(cx) {
1200                            if let Ok(display_uuid) = display.uuid() {
1201                                let window_bounds = window.inner_window_bounds();
1202                                if let Some(database_id) = workspace_id {
1203                                    cx.background_executor()
1204                                        .spawn(DB.set_window_open_status(
1205                                            database_id,
1206                                            SerializedWindowBounds(window_bounds),
1207                                            display_uuid,
1208                                        ))
1209                                        .detach_and_log_err(cx);
1210                                }
1211                            }
1212                        }
1213                        this.bounds_save_task_queued.take();
1214                    })
1215                    .ok();
1216                }));
1217                cx.notify();
1218            }),
1219            cx.observe_window_appearance(window, |_, window, cx| {
1220                let window_appearance = window.appearance();
1221
1222                *SystemAppearance::global_mut(cx) = SystemAppearance(window_appearance.into());
1223
1224                ThemeSettings::reload_current_theme(cx);
1225                ThemeSettings::reload_current_icon_theme(cx);
1226            }),
1227            cx.on_release(move |this, cx| {
1228                this.app_state.workspace_store.update(cx, move |store, _| {
1229                    store.workspaces.remove(&window_handle.clone());
1230                })
1231            }),
1232        ];
1233
1234        cx.defer_in(window, |this, window, cx| {
1235            this.update_window_title(window, cx);
1236            this.show_initial_notifications(cx);
1237        });
1238        Workspace {
1239            weak_self: weak_handle.clone(),
1240            zoomed: None,
1241            zoomed_position: None,
1242            previous_dock_drag_coordinates: None,
1243            center: PaneGroup::new(center_pane.clone()),
1244            panes: vec![center_pane.clone()],
1245            panes_by_item: Default::default(),
1246            active_pane: center_pane.clone(),
1247            last_active_center_pane: Some(center_pane.downgrade()),
1248            last_active_view_id: None,
1249            status_bar,
1250            modal_layer,
1251            toast_layer,
1252            titlebar_item: None,
1253            notifications: Notifications::default(),
1254            suppressed_notifications: HashSet::default(),
1255            left_dock,
1256            bottom_dock,
1257            bottom_dock_layout,
1258            right_dock,
1259            project: project.clone(),
1260            follower_states: Default::default(),
1261            last_leaders_by_pane: Default::default(),
1262            dispatching_keystrokes: Default::default(),
1263            window_edited: false,
1264            dirty_items: Default::default(),
1265            active_call,
1266            database_id: workspace_id,
1267            app_state,
1268            _observe_current_user,
1269            _apply_leader_updates,
1270            _schedule_serialize: None,
1271            leader_updates_tx,
1272            _subscriptions: subscriptions,
1273            pane_history_timestamp,
1274            workspace_actions: Default::default(),
1275            // This data will be incorrect, but it will be overwritten by the time it needs to be used.
1276            bounds: Default::default(),
1277            centered_layout: false,
1278            bounds_save_task_queued: None,
1279            on_prompt_for_new_path: None,
1280            on_prompt_for_open_path: None,
1281            terminal_provider: None,
1282            debugger_provider: None,
1283            serializable_items_tx,
1284            _items_serializer,
1285            session_id: Some(session_id),
1286            serialized_ssh_project: None,
1287        }
1288    }
1289
1290    pub fn new_local(
1291        abs_paths: Vec<PathBuf>,
1292        app_state: Arc<AppState>,
1293        requesting_window: Option<WindowHandle<Workspace>>,
1294        env: Option<HashMap<String, String>>,
1295        cx: &mut App,
1296    ) -> Task<
1297        anyhow::Result<(
1298            WindowHandle<Workspace>,
1299            Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
1300        )>,
1301    > {
1302        let project_handle = Project::local(
1303            app_state.client.clone(),
1304            app_state.node_runtime.clone(),
1305            app_state.user_store.clone(),
1306            app_state.languages.clone(),
1307            app_state.fs.clone(),
1308            env,
1309            cx,
1310        );
1311
1312        cx.spawn(async move |cx| {
1313            let mut paths_to_open = Vec::with_capacity(abs_paths.len());
1314            for path in abs_paths.into_iter() {
1315                if let Some(canonical) = app_state.fs.canonicalize(&path).await.ok() {
1316                    paths_to_open.push(canonical)
1317                } else {
1318                    paths_to_open.push(path)
1319                }
1320            }
1321
1322            let serialized_workspace =
1323                persistence::DB.workspace_for_roots(paths_to_open.as_slice());
1324
1325            let workspace_location = serialized_workspace
1326                .as_ref()
1327                .map(|ws| &ws.location)
1328                .and_then(|loc| match loc {
1329                    SerializedWorkspaceLocation::Local(_, order) => {
1330                        Some((loc.sorted_paths(), order.order()))
1331                    }
1332                    _ => None,
1333                });
1334
1335            if let Some((paths, order)) = workspace_location {
1336                paths_to_open = paths.iter().cloned().collect();
1337
1338                if order.iter().enumerate().any(|(i, &j)| i != j) {
1339                    project_handle
1340                        .update(cx, |project, cx| {
1341                            project.set_worktrees_reordered(true, cx);
1342                        })
1343                        .log_err();
1344                }
1345            }
1346
1347            // Get project paths for all of the abs_paths
1348            let mut project_paths: Vec<(PathBuf, Option<ProjectPath>)> =
1349                Vec::with_capacity(paths_to_open.len());
1350
1351            for path in paths_to_open.into_iter() {
1352                if let Some((_, project_entry)) = cx
1353                    .update(|cx| {
1354                        Workspace::project_path_for_path(project_handle.clone(), &path, true, cx)
1355                    })?
1356                    .await
1357                    .log_err()
1358                {
1359                    project_paths.push((path, Some(project_entry)));
1360                } else {
1361                    project_paths.push((path, None));
1362                }
1363            }
1364
1365            let workspace_id = if let Some(serialized_workspace) = serialized_workspace.as_ref() {
1366                serialized_workspace.id
1367            } else {
1368                DB.next_id().await.unwrap_or_else(|_| Default::default())
1369            };
1370
1371            let toolchains = DB.toolchains(workspace_id).await?;
1372            for (toolchain, worktree_id, path) in toolchains {
1373                project_handle
1374                    .update(cx, |this, cx| {
1375                        this.activate_toolchain(ProjectPath { worktree_id, path }, toolchain, cx)
1376                    })?
1377                    .await;
1378            }
1379            let window = if let Some(window) = requesting_window {
1380                cx.update_window(window.into(), |_, window, cx| {
1381                    window.replace_root(cx, |window, cx| {
1382                        Workspace::new(
1383                            Some(workspace_id),
1384                            project_handle.clone(),
1385                            app_state.clone(),
1386                            window,
1387                            cx,
1388                        )
1389                    });
1390                })?;
1391                window
1392            } else {
1393                let window_bounds_override = window_bounds_env_override();
1394
1395                let (window_bounds, display) = if let Some(bounds) = window_bounds_override {
1396                    (Some(WindowBounds::Windowed(bounds)), None)
1397                } else {
1398                    let restorable_bounds = serialized_workspace
1399                        .as_ref()
1400                        .and_then(|workspace| Some((workspace.display?, workspace.window_bounds?)))
1401                        .or_else(|| {
1402                            let (display, window_bounds) = DB.last_window().log_err()?;
1403                            Some((display?, window_bounds?))
1404                        });
1405
1406                    if let Some((serialized_display, serialized_status)) = restorable_bounds {
1407                        (Some(serialized_status.0), Some(serialized_display))
1408                    } else {
1409                        (None, None)
1410                    }
1411                };
1412
1413                // Use the serialized workspace to construct the new window
1414                let mut options = cx.update(|cx| (app_state.build_window_options)(display, cx))?;
1415                options.window_bounds = window_bounds;
1416                let centered_layout = serialized_workspace
1417                    .as_ref()
1418                    .map(|w| w.centered_layout)
1419                    .unwrap_or(false);
1420                cx.open_window(options, {
1421                    let app_state = app_state.clone();
1422                    let project_handle = project_handle.clone();
1423                    move |window, cx| {
1424                        cx.new(|cx| {
1425                            let mut workspace = Workspace::new(
1426                                Some(workspace_id),
1427                                project_handle,
1428                                app_state,
1429                                window,
1430                                cx,
1431                            );
1432                            workspace.centered_layout = centered_layout;
1433                            workspace
1434                        })
1435                    }
1436                })?
1437            };
1438
1439            notify_if_database_failed(window, cx);
1440            let opened_items = window
1441                .update(cx, |_workspace, window, cx| {
1442                    open_items(serialized_workspace, project_paths, window, cx)
1443                })?
1444                .await
1445                .unwrap_or_default();
1446
1447            window
1448                .update(cx, |workspace, window, cx| {
1449                    window.activate_window();
1450                    workspace.update_history(cx);
1451                })
1452                .log_err();
1453            Ok((window, opened_items))
1454        })
1455    }
1456
1457    pub fn weak_handle(&self) -> WeakEntity<Self> {
1458        self.weak_self.clone()
1459    }
1460
1461    pub fn left_dock(&self) -> &Entity<Dock> {
1462        &self.left_dock
1463    }
1464
1465    pub fn bottom_dock(&self) -> &Entity<Dock> {
1466        &self.bottom_dock
1467    }
1468
1469    pub fn bottom_dock_layout(&self) -> BottomDockLayout {
1470        self.bottom_dock_layout
1471    }
1472
1473    pub fn set_bottom_dock_layout(
1474        &mut self,
1475        layout: BottomDockLayout,
1476        window: &mut Window,
1477        cx: &mut Context<Self>,
1478    ) {
1479        let fs = self.project().read(cx).fs();
1480        settings::update_settings_file::<WorkspaceSettings>(fs.clone(), cx, move |content, _cx| {
1481            content.bottom_dock_layout = Some(layout);
1482        });
1483
1484        self.bottom_dock_layout = layout;
1485        cx.notify();
1486        self.serialize_workspace(window, cx);
1487    }
1488
1489    pub fn right_dock(&self) -> &Entity<Dock> {
1490        &self.right_dock
1491    }
1492
1493    pub fn all_docks(&self) -> [&Entity<Dock>; 3] {
1494        [&self.left_dock, &self.bottom_dock, &self.right_dock]
1495    }
1496
1497    pub fn dock_at_position(&self, position: DockPosition) -> &Entity<Dock> {
1498        match position {
1499            DockPosition::Left => &self.left_dock,
1500            DockPosition::Bottom => &self.bottom_dock,
1501            DockPosition::Right => &self.right_dock,
1502        }
1503    }
1504
1505    pub fn is_edited(&self) -> bool {
1506        self.window_edited
1507    }
1508
1509    pub fn add_panel<T: Panel>(
1510        &mut self,
1511        panel: Entity<T>,
1512        window: &mut Window,
1513        cx: &mut Context<Self>,
1514    ) {
1515        let focus_handle = panel.panel_focus_handle(cx);
1516        cx.on_focus_in(&focus_handle, window, Self::handle_panel_focused)
1517            .detach();
1518
1519        let dock_position = panel.position(window, cx);
1520        let dock = self.dock_at_position(dock_position);
1521
1522        dock.update(cx, |dock, cx| {
1523            dock.add_panel(panel, self.weak_self.clone(), window, cx)
1524        });
1525    }
1526
1527    pub fn status_bar(&self) -> &Entity<StatusBar> {
1528        &self.status_bar
1529    }
1530
1531    pub fn app_state(&self) -> &Arc<AppState> {
1532        &self.app_state
1533    }
1534
1535    pub fn user_store(&self) -> &Entity<UserStore> {
1536        &self.app_state.user_store
1537    }
1538
1539    pub fn project(&self) -> &Entity<Project> {
1540        &self.project
1541    }
1542
1543    pub fn recently_activated_items(&self, cx: &App) -> HashMap<EntityId, usize> {
1544        let mut history: HashMap<EntityId, usize> = HashMap::default();
1545
1546        for pane_handle in &self.panes {
1547            let pane = pane_handle.read(cx);
1548
1549            for entry in pane.activation_history() {
1550                history.insert(
1551                    entry.entity_id,
1552                    history
1553                        .get(&entry.entity_id)
1554                        .cloned()
1555                        .unwrap_or(0)
1556                        .max(entry.timestamp),
1557                );
1558            }
1559        }
1560
1561        history
1562    }
1563
1564    pub fn recent_navigation_history_iter(
1565        &self,
1566        cx: &App,
1567    ) -> impl Iterator<Item = (ProjectPath, Option<PathBuf>)> {
1568        let mut abs_paths_opened: HashMap<PathBuf, HashSet<ProjectPath>> = HashMap::default();
1569        let mut history: HashMap<ProjectPath, (Option<PathBuf>, usize)> = HashMap::default();
1570
1571        for pane in &self.panes {
1572            let pane = pane.read(cx);
1573
1574            pane.nav_history()
1575                .for_each_entry(cx, |entry, (project_path, fs_path)| {
1576                    if let Some(fs_path) = &fs_path {
1577                        abs_paths_opened
1578                            .entry(fs_path.clone())
1579                            .or_default()
1580                            .insert(project_path.clone());
1581                    }
1582                    let timestamp = entry.timestamp;
1583                    match history.entry(project_path) {
1584                        hash_map::Entry::Occupied(mut entry) => {
1585                            let (_, old_timestamp) = entry.get();
1586                            if &timestamp > old_timestamp {
1587                                entry.insert((fs_path, timestamp));
1588                            }
1589                        }
1590                        hash_map::Entry::Vacant(entry) => {
1591                            entry.insert((fs_path, timestamp));
1592                        }
1593                    }
1594                });
1595
1596            if let Some(item) = pane.active_item() {
1597                if let Some(project_path) = item.project_path(cx) {
1598                    let fs_path = self.project.read(cx).absolute_path(&project_path, cx);
1599
1600                    if let Some(fs_path) = &fs_path {
1601                        abs_paths_opened
1602                            .entry(fs_path.clone())
1603                            .or_default()
1604                            .insert(project_path.clone());
1605                    }
1606
1607                    history.insert(project_path, (fs_path, std::usize::MAX));
1608                }
1609            }
1610        }
1611
1612        history
1613            .into_iter()
1614            .sorted_by_key(|(_, (_, order))| *order)
1615            .map(|(project_path, (fs_path, _))| (project_path, fs_path))
1616            .rev()
1617            .filter(move |(history_path, abs_path)| {
1618                let latest_project_path_opened = abs_path
1619                    .as_ref()
1620                    .and_then(|abs_path| abs_paths_opened.get(abs_path))
1621                    .and_then(|project_paths| {
1622                        project_paths
1623                            .iter()
1624                            .max_by(|b1, b2| b1.worktree_id.cmp(&b2.worktree_id))
1625                    });
1626
1627                match latest_project_path_opened {
1628                    Some(latest_project_path_opened) => latest_project_path_opened == history_path,
1629                    None => true,
1630                }
1631            })
1632    }
1633
1634    pub fn recent_navigation_history(
1635        &self,
1636        limit: Option<usize>,
1637        cx: &App,
1638    ) -> Vec<(ProjectPath, Option<PathBuf>)> {
1639        self.recent_navigation_history_iter(cx)
1640            .take(limit.unwrap_or(usize::MAX))
1641            .collect()
1642    }
1643
1644    fn navigate_history(
1645        &mut self,
1646        pane: WeakEntity<Pane>,
1647        mode: NavigationMode,
1648        window: &mut Window,
1649        cx: &mut Context<Workspace>,
1650    ) -> Task<Result<()>> {
1651        let to_load = if let Some(pane) = pane.upgrade() {
1652            pane.update(cx, |pane, cx| {
1653                window.focus(&pane.focus_handle(cx));
1654                loop {
1655                    // Retrieve the weak item handle from the history.
1656                    let entry = pane.nav_history_mut().pop(mode, cx)?;
1657
1658                    // If the item is still present in this pane, then activate it.
1659                    if let Some(index) = entry
1660                        .item
1661                        .upgrade()
1662                        .and_then(|v| pane.index_for_item(v.as_ref()))
1663                    {
1664                        let prev_active_item_index = pane.active_item_index();
1665                        pane.nav_history_mut().set_mode(mode);
1666                        pane.activate_item(index, true, true, window, cx);
1667                        pane.nav_history_mut().set_mode(NavigationMode::Normal);
1668
1669                        let mut navigated = prev_active_item_index != pane.active_item_index();
1670                        if let Some(data) = entry.data {
1671                            navigated |= pane.active_item()?.navigate(data, window, cx);
1672                        }
1673
1674                        if navigated {
1675                            break None;
1676                        }
1677                    } else {
1678                        // If the item is no longer present in this pane, then retrieve its
1679                        // path info in order to reopen it.
1680                        break pane
1681                            .nav_history()
1682                            .path_for_item(entry.item.id())
1683                            .map(|(project_path, abs_path)| (project_path, abs_path, entry));
1684                    }
1685                }
1686            })
1687        } else {
1688            None
1689        };
1690
1691        if let Some((project_path, abs_path, entry)) = to_load {
1692            // If the item was no longer present, then load it again from its previous path, first try the local path
1693            let open_by_project_path = self.load_path(project_path.clone(), window, cx);
1694
1695            cx.spawn_in(window, async move  |workspace, cx| {
1696                let open_by_project_path = open_by_project_path.await;
1697                let mut navigated = false;
1698                match open_by_project_path
1699                    .with_context(|| format!("Navigating to {project_path:?}"))
1700                {
1701                    Ok((project_entry_id, build_item)) => {
1702                        let prev_active_item_id = pane.update(cx, |pane, _| {
1703                            pane.nav_history_mut().set_mode(mode);
1704                            pane.active_item().map(|p| p.item_id())
1705                        })?;
1706
1707                        pane.update_in(cx, |pane, window, cx| {
1708                            let item = pane.open_item(
1709                                project_entry_id,
1710                                project_path,
1711                                true,
1712                                entry.is_preview,
1713                                true,
1714                                None,
1715                                window, cx,
1716                                build_item,
1717                            );
1718                            navigated |= Some(item.item_id()) != prev_active_item_id;
1719                            pane.nav_history_mut().set_mode(NavigationMode::Normal);
1720                            if let Some(data) = entry.data {
1721                                navigated |= item.navigate(data, window, cx);
1722                            }
1723                        })?;
1724                    }
1725                    Err(open_by_project_path_e) => {
1726                        // Fall back to opening by abs path, in case an external file was opened and closed,
1727                        // and its worktree is now dropped
1728                        if let Some(abs_path) = abs_path {
1729                            let prev_active_item_id = pane.update(cx, |pane, _| {
1730                                pane.nav_history_mut().set_mode(mode);
1731                                pane.active_item().map(|p| p.item_id())
1732                            })?;
1733                            let open_by_abs_path = workspace.update_in(cx, |workspace, window, cx| {
1734                                workspace.open_abs_path(abs_path.clone(), OpenOptions { visible: Some(OpenVisible::None), ..Default::default() }, window, cx)
1735                            })?;
1736                            match open_by_abs_path
1737                                .await
1738                                .with_context(|| format!("Navigating to {abs_path:?}"))
1739                            {
1740                                Ok(item) => {
1741                                    pane.update_in(cx, |pane, window, cx| {
1742                                        navigated |= Some(item.item_id()) != prev_active_item_id;
1743                                        pane.nav_history_mut().set_mode(NavigationMode::Normal);
1744                                        if let Some(data) = entry.data {
1745                                            navigated |= item.navigate(data, window, cx);
1746                                        }
1747                                    })?;
1748                                }
1749                                Err(open_by_abs_path_e) => {
1750                                    log::error!("Failed to navigate history: {open_by_project_path_e:#} and {open_by_abs_path_e:#}");
1751                                }
1752                            }
1753                        }
1754                    }
1755                }
1756
1757                if !navigated {
1758                    workspace
1759                        .update_in(cx, |workspace, window, cx| {
1760                            Self::navigate_history(workspace, pane, mode, window, cx)
1761                        })?
1762                        .await?;
1763                }
1764
1765                Ok(())
1766            })
1767        } else {
1768            Task::ready(Ok(()))
1769        }
1770    }
1771
1772    pub fn go_back(
1773        &mut self,
1774        pane: WeakEntity<Pane>,
1775        window: &mut Window,
1776        cx: &mut Context<Workspace>,
1777    ) -> Task<Result<()>> {
1778        self.navigate_history(pane, NavigationMode::GoingBack, window, cx)
1779    }
1780
1781    pub fn go_forward(
1782        &mut self,
1783        pane: WeakEntity<Pane>,
1784        window: &mut Window,
1785        cx: &mut Context<Workspace>,
1786    ) -> Task<Result<()>> {
1787        self.navigate_history(pane, NavigationMode::GoingForward, window, cx)
1788    }
1789
1790    pub fn reopen_closed_item(
1791        &mut self,
1792        window: &mut Window,
1793        cx: &mut Context<Workspace>,
1794    ) -> Task<Result<()>> {
1795        self.navigate_history(
1796            self.active_pane().downgrade(),
1797            NavigationMode::ReopeningClosedItem,
1798            window,
1799            cx,
1800        )
1801    }
1802
1803    pub fn client(&self) -> &Arc<Client> {
1804        &self.app_state.client
1805    }
1806
1807    pub fn set_titlebar_item(&mut self, item: AnyView, _: &mut Window, cx: &mut Context<Self>) {
1808        self.titlebar_item = Some(item);
1809        cx.notify();
1810    }
1811
1812    pub fn set_prompt_for_new_path(&mut self, prompt: PromptForNewPath) {
1813        self.on_prompt_for_new_path = Some(prompt)
1814    }
1815
1816    pub fn set_prompt_for_open_path(&mut self, prompt: PromptForOpenPath) {
1817        self.on_prompt_for_open_path = Some(prompt)
1818    }
1819
1820    pub fn set_terminal_provider(&mut self, provider: impl TerminalProvider + 'static) {
1821        self.terminal_provider = Some(Box::new(provider));
1822    }
1823
1824    pub fn set_debugger_provider(&mut self, provider: impl DebuggerProvider + 'static) {
1825        self.debugger_provider = Some(Box::new(provider));
1826    }
1827
1828    pub fn serialized_ssh_project(&self) -> Option<SerializedSshProject> {
1829        self.serialized_ssh_project.clone()
1830    }
1831
1832    pub fn set_serialized_ssh_project(&mut self, serialized_ssh_project: SerializedSshProject) {
1833        self.serialized_ssh_project = Some(serialized_ssh_project);
1834    }
1835
1836    pub fn prompt_for_open_path(
1837        &mut self,
1838        path_prompt_options: PathPromptOptions,
1839        lister: DirectoryLister,
1840        window: &mut Window,
1841        cx: &mut Context<Self>,
1842    ) -> oneshot::Receiver<Option<Vec<PathBuf>>> {
1843        if !lister.is_local(cx) || !WorkspaceSettings::get_global(cx).use_system_path_prompts {
1844            let prompt = self.on_prompt_for_open_path.take().unwrap();
1845            let rx = prompt(self, lister, window, cx);
1846            self.on_prompt_for_open_path = Some(prompt);
1847            rx
1848        } else {
1849            let (tx, rx) = oneshot::channel();
1850            let abs_path = cx.prompt_for_paths(path_prompt_options);
1851
1852            cx.spawn_in(window, async move |this, cx| {
1853                let Ok(result) = abs_path.await else {
1854                    return Ok(());
1855                };
1856
1857                match result {
1858                    Ok(result) => {
1859                        tx.send(result).log_err();
1860                    }
1861                    Err(err) => {
1862                        let rx = this.update_in(cx, |this, window, cx| {
1863                            this.show_portal_error(err.to_string(), cx);
1864                            let prompt = this.on_prompt_for_open_path.take().unwrap();
1865                            let rx = prompt(this, lister, window, cx);
1866                            this.on_prompt_for_open_path = Some(prompt);
1867                            rx
1868                        })?;
1869                        if let Ok(path) = rx.await {
1870                            tx.send(path).log_err();
1871                        }
1872                    }
1873                };
1874                anyhow::Ok(())
1875            })
1876            .detach();
1877
1878            rx
1879        }
1880    }
1881
1882    pub fn prompt_for_new_path(
1883        &mut self,
1884        window: &mut Window,
1885        cx: &mut Context<Self>,
1886    ) -> oneshot::Receiver<Option<ProjectPath>> {
1887        if (self.project.read(cx).is_via_collab() || self.project.read(cx).is_via_ssh())
1888            || !WorkspaceSettings::get_global(cx).use_system_path_prompts
1889        {
1890            let prompt = self.on_prompt_for_new_path.take().unwrap();
1891            let rx = prompt(self, window, cx);
1892            self.on_prompt_for_new_path = Some(prompt);
1893            return rx;
1894        }
1895
1896        let (tx, rx) = oneshot::channel();
1897        cx.spawn_in(window, async move |this, cx| {
1898            let abs_path = this.update(cx, |this, cx| {
1899                let mut relative_to = this
1900                    .most_recent_active_path(cx)
1901                    .and_then(|p| p.parent().map(|p| p.to_path_buf()));
1902                if relative_to.is_none() {
1903                    let project = this.project.read(cx);
1904                    relative_to = project
1905                        .visible_worktrees(cx)
1906                        .filter_map(|worktree| {
1907                            Some(worktree.read(cx).as_local()?.abs_path().to_path_buf())
1908                        })
1909                        .next()
1910                };
1911
1912                cx.prompt_for_new_path(&relative_to.unwrap_or_else(|| PathBuf::from("")))
1913            })?;
1914            let abs_path = match abs_path.await? {
1915                Ok(path) => path,
1916                Err(err) => {
1917                    let rx = this.update_in(cx, |this, window, cx| {
1918                        this.show_portal_error(err.to_string(), cx);
1919
1920                        let prompt = this.on_prompt_for_new_path.take().unwrap();
1921                        let rx = prompt(this, window, cx);
1922                        this.on_prompt_for_new_path = Some(prompt);
1923                        rx
1924                    })?;
1925                    if let Ok(path) = rx.await {
1926                        tx.send(path).log_err();
1927                    }
1928                    return anyhow::Ok(());
1929                }
1930            };
1931
1932            let project_path = abs_path.and_then(|abs_path| {
1933                this.update(cx, |this, cx| {
1934                    this.project.update(cx, |project, cx| {
1935                        project.find_or_create_worktree(abs_path, true, cx)
1936                    })
1937                })
1938                .ok()
1939            });
1940
1941            if let Some(project_path) = project_path {
1942                let (worktree, path) = project_path.await?;
1943                let worktree_id = worktree.read_with(cx, |worktree, _| worktree.id())?;
1944                tx.send(Some(ProjectPath {
1945                    worktree_id,
1946                    path: path.into(),
1947                }))
1948                .ok();
1949            } else {
1950                tx.send(None).ok();
1951            }
1952            anyhow::Ok(())
1953        })
1954        .detach_and_log_err(cx);
1955
1956        rx
1957    }
1958
1959    pub fn titlebar_item(&self) -> Option<AnyView> {
1960        self.titlebar_item.clone()
1961    }
1962
1963    /// Call the given callback with a workspace whose project is local.
1964    ///
1965    /// If the given workspace has a local project, then it will be passed
1966    /// to the callback. Otherwise, a new empty window will be created.
1967    pub fn with_local_workspace<T, F>(
1968        &mut self,
1969        window: &mut Window,
1970        cx: &mut Context<Self>,
1971        callback: F,
1972    ) -> Task<Result<T>>
1973    where
1974        T: 'static,
1975        F: 'static + FnOnce(&mut Workspace, &mut Window, &mut Context<Workspace>) -> T,
1976    {
1977        if self.project.read(cx).is_local() {
1978            Task::ready(Ok(callback(self, window, cx)))
1979        } else {
1980            let env = self.project.read(cx).cli_environment(cx);
1981            let task = Self::new_local(Vec::new(), self.app_state.clone(), None, env, cx);
1982            cx.spawn_in(window, async move |_vh, cx| {
1983                let (workspace, _) = task.await?;
1984                workspace.update(cx, callback)
1985            })
1986        }
1987    }
1988
1989    pub fn worktrees<'a>(&self, cx: &'a App) -> impl 'a + Iterator<Item = Entity<Worktree>> {
1990        self.project.read(cx).worktrees(cx)
1991    }
1992
1993    pub fn visible_worktrees<'a>(
1994        &self,
1995        cx: &'a App,
1996    ) -> impl 'a + Iterator<Item = Entity<Worktree>> {
1997        self.project.read(cx).visible_worktrees(cx)
1998    }
1999
2000    #[cfg(any(test, feature = "test-support"))]
2001    pub fn worktree_scans_complete(&self, cx: &App) -> impl Future<Output = ()> + 'static + use<> {
2002        let futures = self
2003            .worktrees(cx)
2004            .filter_map(|worktree| worktree.read(cx).as_local())
2005            .map(|worktree| worktree.scan_complete())
2006            .collect::<Vec<_>>();
2007        async move {
2008            for future in futures {
2009                future.await;
2010            }
2011        }
2012    }
2013
2014    pub fn close_global(_: &CloseWindow, cx: &mut App) {
2015        cx.defer(|cx| {
2016            cx.windows().iter().find(|window| {
2017                window
2018                    .update(cx, |_, window, _| {
2019                        if window.is_window_active() {
2020                            //This can only get called when the window's project connection has been lost
2021                            //so we don't need to prompt the user for anything and instead just close the window
2022                            window.remove_window();
2023                            true
2024                        } else {
2025                            false
2026                        }
2027                    })
2028                    .unwrap_or(false)
2029            });
2030        });
2031    }
2032
2033    pub fn close_window(&mut self, _: &CloseWindow, window: &mut Window, cx: &mut Context<Self>) {
2034        let prepare = self.prepare_to_close(CloseIntent::CloseWindow, window, cx);
2035        cx.spawn_in(window, async move |_, cx| {
2036            if prepare.await? {
2037                cx.update(|window, _cx| window.remove_window())?;
2038            }
2039            anyhow::Ok(())
2040        })
2041        .detach_and_log_err(cx)
2042    }
2043
2044    pub fn move_focused_panel_to_next_position(
2045        &mut self,
2046        _: &MoveFocusedPanelToNextPosition,
2047        window: &mut Window,
2048        cx: &mut Context<Self>,
2049    ) {
2050        let docks = self.all_docks();
2051        let active_dock = docks
2052            .into_iter()
2053            .find(|dock| dock.focus_handle(cx).contains_focused(window, cx));
2054
2055        if let Some(dock) = active_dock {
2056            dock.update(cx, |dock, cx| {
2057                let active_panel = dock
2058                    .active_panel()
2059                    .filter(|panel| panel.panel_focus_handle(cx).contains_focused(window, cx));
2060
2061                if let Some(panel) = active_panel {
2062                    panel.move_to_next_position(window, cx);
2063                }
2064            })
2065        }
2066    }
2067
2068    pub fn prepare_to_close(
2069        &mut self,
2070        close_intent: CloseIntent,
2071        window: &mut Window,
2072        cx: &mut Context<Self>,
2073    ) -> Task<Result<bool>> {
2074        let active_call = self.active_call().cloned();
2075
2076        // On Linux and Windows, closing the last window should restore the last workspace.
2077        let save_last_workspace = cfg!(not(target_os = "macos"))
2078            && close_intent != CloseIntent::ReplaceWindow
2079            && cx.windows().len() == 1;
2080
2081        cx.spawn_in(window, async move |this, cx| {
2082            let workspace_count = cx.update(|_window, cx| {
2083                cx.windows()
2084                    .iter()
2085                    .filter(|window| window.downcast::<Workspace>().is_some())
2086                    .count()
2087            })?;
2088
2089            if let Some(active_call) = active_call {
2090                if close_intent != CloseIntent::Quit
2091                    && workspace_count == 1
2092                    && active_call.read_with(cx, |call, _| call.room().is_some())?
2093                {
2094                    let answer = cx.update(|window, cx| {
2095                        window.prompt(
2096                            PromptLevel::Warning,
2097                            "Do you want to leave the current call?",
2098                            None,
2099                            &["Close window and hang up", "Cancel"],
2100                            cx,
2101                        )
2102                    })?;
2103
2104                    if answer.await.log_err() == Some(1) {
2105                        return anyhow::Ok(false);
2106                    } else {
2107                        active_call
2108                            .update(cx, |call, cx| call.hang_up(cx))?
2109                            .await
2110                            .log_err();
2111                    }
2112                }
2113            }
2114
2115            let save_result = this
2116                .update_in(cx, |this, window, cx| {
2117                    this.save_all_internal(SaveIntent::Close, window, cx)
2118                })?
2119                .await;
2120
2121            // If we're not quitting, but closing, we remove the workspace from
2122            // the current session.
2123            if close_intent != CloseIntent::Quit
2124                && !save_last_workspace
2125                && save_result.as_ref().map_or(false, |&res| res)
2126            {
2127                this.update_in(cx, |this, window, cx| this.remove_from_session(window, cx))?
2128                    .await;
2129            }
2130
2131            save_result
2132        })
2133    }
2134
2135    fn save_all(&mut self, action: &SaveAll, window: &mut Window, cx: &mut Context<Self>) {
2136        self.save_all_internal(
2137            action.save_intent.unwrap_or(SaveIntent::SaveAll),
2138            window,
2139            cx,
2140        )
2141        .detach_and_log_err(cx);
2142    }
2143
2144    fn send_keystrokes(
2145        &mut self,
2146        action: &SendKeystrokes,
2147        window: &mut Window,
2148        cx: &mut Context<Self>,
2149    ) {
2150        let mut state = self.dispatching_keystrokes.borrow_mut();
2151        if !state.0.insert(action.0.clone()) {
2152            cx.propagate();
2153            return;
2154        }
2155        let mut keystrokes: Vec<Keystroke> = action
2156            .0
2157            .split(' ')
2158            .flat_map(|k| Keystroke::parse(k).log_err())
2159            .collect();
2160        keystrokes.reverse();
2161
2162        state.1.append(&mut keystrokes);
2163        drop(state);
2164
2165        let keystrokes = self.dispatching_keystrokes.clone();
2166        window
2167            .spawn(cx, async move |cx| {
2168                // limit to 100 keystrokes to avoid infinite recursion.
2169                for _ in 0..100 {
2170                    let Some(keystroke) = keystrokes.borrow_mut().1.pop() else {
2171                        keystrokes.borrow_mut().0.clear();
2172                        return Ok(());
2173                    };
2174                    cx.update(|window, cx| {
2175                        let focused = window.focused(cx);
2176                        window.dispatch_keystroke(keystroke.clone(), cx);
2177                        if window.focused(cx) != focused {
2178                            // dispatch_keystroke may cause the focus to change.
2179                            // draw's side effect is to schedule the FocusChanged events in the current flush effect cycle
2180                            // And we need that to happen before the next keystroke to keep vim mode happy...
2181                            // (Note that the tests always do this implicitly, so you must manually test with something like:
2182                            //   "bindings": { "g z": ["workspace::SendKeystrokes", ": j <enter> u"]}
2183                            // )
2184                            window.draw(cx);
2185                        }
2186                    })?;
2187                }
2188
2189                *keystrokes.borrow_mut() = Default::default();
2190                Err(anyhow!("over 100 keystrokes passed to send_keystrokes"))
2191            })
2192            .detach_and_log_err(cx);
2193    }
2194
2195    fn save_all_internal(
2196        &mut self,
2197        mut save_intent: SaveIntent,
2198        window: &mut Window,
2199        cx: &mut Context<Self>,
2200    ) -> Task<Result<bool>> {
2201        if self.project.read(cx).is_disconnected(cx) {
2202            return Task::ready(Ok(true));
2203        }
2204        let dirty_items = self
2205            .panes
2206            .iter()
2207            .flat_map(|pane| {
2208                pane.read(cx).items().filter_map(|item| {
2209                    if item.is_dirty(cx) {
2210                        item.tab_content_text(0, cx);
2211                        Some((pane.downgrade(), item.boxed_clone()))
2212                    } else {
2213                        None
2214                    }
2215                })
2216            })
2217            .collect::<Vec<_>>();
2218
2219        let project = self.project.clone();
2220        cx.spawn_in(window, async move |workspace, cx| {
2221            let dirty_items = if save_intent == SaveIntent::Close && !dirty_items.is_empty() {
2222                let (serialize_tasks, remaining_dirty_items) =
2223                    workspace.update_in(cx, |workspace, window, cx| {
2224                        let mut remaining_dirty_items = Vec::new();
2225                        let mut serialize_tasks = Vec::new();
2226                        for (pane, item) in dirty_items {
2227                            if let Some(task) = item
2228                                .to_serializable_item_handle(cx)
2229                                .and_then(|handle| handle.serialize(workspace, true, window, cx))
2230                            {
2231                                serialize_tasks.push(task);
2232                            } else {
2233                                remaining_dirty_items.push((pane, item));
2234                            }
2235                        }
2236                        (serialize_tasks, remaining_dirty_items)
2237                    })?;
2238
2239                futures::future::try_join_all(serialize_tasks).await?;
2240
2241                if remaining_dirty_items.len() > 1 {
2242                    let answer = workspace.update_in(cx, |_, window, cx| {
2243                        let detail = Pane::file_names_for_prompt(
2244                            &mut remaining_dirty_items.iter().map(|(_, handle)| handle),
2245                            cx,
2246                        );
2247                        window.prompt(
2248                            PromptLevel::Warning,
2249                            &"Do you want to save all changes in the following files?",
2250                            Some(&detail),
2251                            &["Save all", "Discard all", "Cancel"],
2252                            cx,
2253                        )
2254                    })?;
2255                    match answer.await.log_err() {
2256                        Some(0) => save_intent = SaveIntent::SaveAll,
2257                        Some(1) => save_intent = SaveIntent::Skip,
2258                        Some(2) => return Ok(false),
2259                        _ => {}
2260                    }
2261                }
2262
2263                remaining_dirty_items
2264            } else {
2265                dirty_items
2266            };
2267
2268            for (pane, item) in dirty_items {
2269                let (singleton, project_entry_ids) =
2270                    cx.update(|_, cx| (item.is_singleton(cx), item.project_entry_ids(cx)))?;
2271                if singleton || !project_entry_ids.is_empty() {
2272                    if !Pane::save_item(project.clone(), &pane, &*item, save_intent, cx).await? {
2273                        return Ok(false);
2274                    }
2275                }
2276            }
2277            Ok(true)
2278        })
2279    }
2280
2281    pub fn open_workspace_for_paths(
2282        &mut self,
2283        replace_current_window: bool,
2284        paths: Vec<PathBuf>,
2285        window: &mut Window,
2286        cx: &mut Context<Self>,
2287    ) -> Task<Result<()>> {
2288        let window_handle = window.window_handle().downcast::<Self>();
2289        let is_remote = self.project.read(cx).is_via_collab();
2290        let has_worktree = self.project.read(cx).worktrees(cx).next().is_some();
2291        let has_dirty_items = self.items(cx).any(|item| item.is_dirty(cx));
2292
2293        let window_to_replace = if replace_current_window {
2294            window_handle
2295        } else if is_remote || has_worktree || has_dirty_items {
2296            None
2297        } else {
2298            window_handle
2299        };
2300        let app_state = self.app_state.clone();
2301
2302        cx.spawn(async move |_, cx| {
2303            cx.update(|cx| {
2304                open_paths(
2305                    &paths,
2306                    app_state,
2307                    OpenOptions {
2308                        replace_window: window_to_replace,
2309                        ..Default::default()
2310                    },
2311                    cx,
2312                )
2313            })?
2314            .await?;
2315            Ok(())
2316        })
2317    }
2318
2319    #[allow(clippy::type_complexity)]
2320    pub fn open_paths(
2321        &mut self,
2322        mut abs_paths: Vec<PathBuf>,
2323        options: OpenOptions,
2324        pane: Option<WeakEntity<Pane>>,
2325        window: &mut Window,
2326        cx: &mut Context<Self>,
2327    ) -> Task<Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>> {
2328        log::info!("open paths {abs_paths:?}");
2329
2330        let fs = self.app_state.fs.clone();
2331
2332        // Sort the paths to ensure we add worktrees for parents before their children.
2333        abs_paths.sort_unstable();
2334        cx.spawn_in(window, async move |this, cx| {
2335            let mut tasks = Vec::with_capacity(abs_paths.len());
2336
2337            for abs_path in &abs_paths {
2338                let visible = match options.visible.as_ref().unwrap_or(&OpenVisible::None) {
2339                    OpenVisible::All => Some(true),
2340                    OpenVisible::None => Some(false),
2341                    OpenVisible::OnlyFiles => match fs.metadata(abs_path).await.log_err() {
2342                        Some(Some(metadata)) => Some(!metadata.is_dir),
2343                        Some(None) => Some(true),
2344                        None => None,
2345                    },
2346                    OpenVisible::OnlyDirectories => match fs.metadata(abs_path).await.log_err() {
2347                        Some(Some(metadata)) => Some(metadata.is_dir),
2348                        Some(None) => Some(false),
2349                        None => None,
2350                    },
2351                };
2352                let project_path = match visible {
2353                    Some(visible) => match this
2354                        .update(cx, |this, cx| {
2355                            Workspace::project_path_for_path(
2356                                this.project.clone(),
2357                                abs_path,
2358                                visible,
2359                                cx,
2360                            )
2361                        })
2362                        .log_err()
2363                    {
2364                        Some(project_path) => project_path.await.log_err(),
2365                        None => None,
2366                    },
2367                    None => None,
2368                };
2369
2370                let this = this.clone();
2371                let abs_path: Arc<Path> = SanitizedPath::from(abs_path.clone()).into();
2372                let fs = fs.clone();
2373                let pane = pane.clone();
2374                let task = cx.spawn(async move |cx| {
2375                    let (worktree, project_path) = project_path?;
2376                    if fs.is_dir(&abs_path).await {
2377                        this.update(cx, |workspace, cx| {
2378                            let worktree = worktree.read(cx);
2379                            let worktree_abs_path = worktree.abs_path();
2380                            let entry_id = if abs_path.as_ref() == worktree_abs_path.as_ref() {
2381                                worktree.root_entry()
2382                            } else {
2383                                abs_path
2384                                    .strip_prefix(worktree_abs_path.as_ref())
2385                                    .ok()
2386                                    .and_then(|relative_path| {
2387                                        worktree.entry_for_path(relative_path)
2388                                    })
2389                            }
2390                            .map(|entry| entry.id);
2391                            if let Some(entry_id) = entry_id {
2392                                workspace.project.update(cx, |_, cx| {
2393                                    cx.emit(project::Event::ActiveEntryChanged(Some(entry_id)));
2394                                })
2395                            }
2396                        })
2397                        .log_err()?;
2398                        None
2399                    } else {
2400                        Some(
2401                            this.update_in(cx, |this, window, cx| {
2402                                this.open_path(
2403                                    project_path,
2404                                    pane,
2405                                    options.focus.unwrap_or(true),
2406                                    window,
2407                                    cx,
2408                                )
2409                            })
2410                            .log_err()?
2411                            .await,
2412                        )
2413                    }
2414                });
2415                tasks.push(task);
2416            }
2417
2418            futures::future::join_all(tasks).await
2419        })
2420    }
2421
2422    pub fn open_resolved_path(
2423        &mut self,
2424        path: ResolvedPath,
2425        window: &mut Window,
2426        cx: &mut Context<Self>,
2427    ) -> Task<anyhow::Result<Box<dyn ItemHandle>>> {
2428        match path {
2429            ResolvedPath::ProjectPath { project_path, .. } => {
2430                self.open_path(project_path, None, true, window, cx)
2431            }
2432            ResolvedPath::AbsPath { path, .. } => self.open_abs_path(
2433                path,
2434                OpenOptions {
2435                    visible: Some(OpenVisible::None),
2436                    ..Default::default()
2437                },
2438                window,
2439                cx,
2440            ),
2441        }
2442    }
2443
2444    pub fn absolute_path_of_worktree(
2445        &self,
2446        worktree_id: WorktreeId,
2447        cx: &mut Context<Self>,
2448    ) -> Option<PathBuf> {
2449        self.project
2450            .read(cx)
2451            .worktree_for_id(worktree_id, cx)
2452            // TODO: use `abs_path` or `root_dir`
2453            .map(|wt| wt.read(cx).abs_path().as_ref().to_path_buf())
2454    }
2455
2456    fn add_folder_to_project(
2457        &mut self,
2458        _: &AddFolderToProject,
2459        window: &mut Window,
2460        cx: &mut Context<Self>,
2461    ) {
2462        let project = self.project.read(cx);
2463        if project.is_via_collab() {
2464            self.show_error(
2465                &anyhow!("You cannot add folders to someone else's project"),
2466                cx,
2467            );
2468            return;
2469        }
2470        let paths = self.prompt_for_open_path(
2471            PathPromptOptions {
2472                files: false,
2473                directories: true,
2474                multiple: true,
2475            },
2476            DirectoryLister::Project(self.project.clone()),
2477            window,
2478            cx,
2479        );
2480        cx.spawn_in(window, async move |this, cx| {
2481            if let Some(paths) = paths.await.log_err().flatten() {
2482                let results = this
2483                    .update_in(cx, |this, window, cx| {
2484                        this.open_paths(
2485                            paths,
2486                            OpenOptions {
2487                                visible: Some(OpenVisible::All),
2488                                ..Default::default()
2489                            },
2490                            None,
2491                            window,
2492                            cx,
2493                        )
2494                    })?
2495                    .await;
2496                for result in results.into_iter().flatten() {
2497                    result.log_err();
2498                }
2499            }
2500            anyhow::Ok(())
2501        })
2502        .detach_and_log_err(cx);
2503    }
2504
2505    pub fn project_path_for_path(
2506        project: Entity<Project>,
2507        abs_path: &Path,
2508        visible: bool,
2509        cx: &mut App,
2510    ) -> Task<Result<(Entity<Worktree>, ProjectPath)>> {
2511        let entry = project.update(cx, |project, cx| {
2512            project.find_or_create_worktree(abs_path, visible, cx)
2513        });
2514        cx.spawn(async move |cx| {
2515            let (worktree, path) = entry.await?;
2516            let worktree_id = worktree.update(cx, |t, _| t.id())?;
2517            Ok((
2518                worktree,
2519                ProjectPath {
2520                    worktree_id,
2521                    path: path.into(),
2522                },
2523            ))
2524        })
2525    }
2526
2527    pub fn items<'a>(&'a self, cx: &'a App) -> impl 'a + Iterator<Item = &'a Box<dyn ItemHandle>> {
2528        self.panes.iter().flat_map(|pane| pane.read(cx).items())
2529    }
2530
2531    pub fn item_of_type<T: Item>(&self, cx: &App) -> Option<Entity<T>> {
2532        self.items_of_type(cx).max_by_key(|item| item.item_id())
2533    }
2534
2535    pub fn items_of_type<'a, T: Item>(
2536        &'a self,
2537        cx: &'a App,
2538    ) -> impl 'a + Iterator<Item = Entity<T>> {
2539        self.panes
2540            .iter()
2541            .flat_map(|pane| pane.read(cx).items_of_type())
2542    }
2543
2544    pub fn active_item(&self, cx: &App) -> Option<Box<dyn ItemHandle>> {
2545        self.active_pane().read(cx).active_item()
2546    }
2547
2548    pub fn active_item_as<I: 'static>(&self, cx: &App) -> Option<Entity<I>> {
2549        let item = self.active_item(cx)?;
2550        item.to_any().downcast::<I>().ok()
2551    }
2552
2553    fn active_project_path(&self, cx: &App) -> Option<ProjectPath> {
2554        self.active_item(cx).and_then(|item| item.project_path(cx))
2555    }
2556
2557    pub fn most_recent_active_path(&self, cx: &App) -> Option<PathBuf> {
2558        self.recent_navigation_history_iter(cx)
2559            .filter_map(|(path, abs_path)| {
2560                let worktree = self
2561                    .project
2562                    .read(cx)
2563                    .worktree_for_id(path.worktree_id, cx)?;
2564                if worktree.read(cx).is_visible() {
2565                    abs_path
2566                } else {
2567                    None
2568                }
2569            })
2570            .next()
2571    }
2572
2573    pub fn save_active_item(
2574        &mut self,
2575        save_intent: SaveIntent,
2576        window: &mut Window,
2577        cx: &mut App,
2578    ) -> Task<Result<()>> {
2579        let project = self.project.clone();
2580        let pane = self.active_pane();
2581        let item = pane.read(cx).active_item();
2582        let pane = pane.downgrade();
2583
2584        window.spawn(cx, async move |mut cx| {
2585            if let Some(item) = item {
2586                Pane::save_item(project, &pane, item.as_ref(), save_intent, &mut cx)
2587                    .await
2588                    .map(|_| ())
2589            } else {
2590                Ok(())
2591            }
2592        })
2593    }
2594
2595    pub fn close_inactive_items_and_panes(
2596        &mut self,
2597        action: &CloseInactiveTabsAndPanes,
2598        window: &mut Window,
2599        cx: &mut Context<Self>,
2600    ) {
2601        if let Some(task) = self.close_all_internal(
2602            true,
2603            action.save_intent.unwrap_or(SaveIntent::Close),
2604            window,
2605            cx,
2606        ) {
2607            task.detach_and_log_err(cx)
2608        }
2609    }
2610
2611    pub fn close_all_items_and_panes(
2612        &mut self,
2613        action: &CloseAllItemsAndPanes,
2614        window: &mut Window,
2615        cx: &mut Context<Self>,
2616    ) {
2617        if let Some(task) = self.close_all_internal(
2618            false,
2619            action.save_intent.unwrap_or(SaveIntent::Close),
2620            window,
2621            cx,
2622        ) {
2623            task.detach_and_log_err(cx)
2624        }
2625    }
2626
2627    fn close_all_internal(
2628        &mut self,
2629        retain_active_pane: bool,
2630        save_intent: SaveIntent,
2631        window: &mut Window,
2632        cx: &mut Context<Self>,
2633    ) -> Option<Task<Result<()>>> {
2634        let current_pane = self.active_pane();
2635
2636        let mut tasks = Vec::new();
2637
2638        if retain_active_pane {
2639            if let Some(current_pane_close) = current_pane.update(cx, |pane, cx| {
2640                pane.close_inactive_items(
2641                    &CloseInactiveItems {
2642                        save_intent: None,
2643                        close_pinned: false,
2644                    },
2645                    window,
2646                    cx,
2647                )
2648            }) {
2649                tasks.push(current_pane_close);
2650            };
2651        }
2652
2653        for pane in self.panes() {
2654            if retain_active_pane && pane.entity_id() == current_pane.entity_id() {
2655                continue;
2656            }
2657
2658            if let Some(close_pane_items) = pane.update(cx, |pane: &mut Pane, cx| {
2659                pane.close_all_items(
2660                    &CloseAllItems {
2661                        save_intent: Some(save_intent),
2662                        close_pinned: false,
2663                    },
2664                    window,
2665                    cx,
2666                )
2667            }) {
2668                tasks.push(close_pane_items)
2669            }
2670        }
2671
2672        if tasks.is_empty() {
2673            None
2674        } else {
2675            Some(cx.spawn_in(window, async move |_, _| {
2676                for task in tasks {
2677                    task.await?
2678                }
2679                Ok(())
2680            }))
2681        }
2682    }
2683
2684    pub fn is_dock_at_position_open(&self, position: DockPosition, cx: &mut Context<Self>) -> bool {
2685        self.dock_at_position(position).read(cx).is_open()
2686    }
2687
2688    pub fn toggle_dock(
2689        &mut self,
2690        dock_side: DockPosition,
2691        window: &mut Window,
2692        cx: &mut Context<Self>,
2693    ) {
2694        let dock = self.dock_at_position(dock_side);
2695        let mut focus_center = false;
2696        let mut reveal_dock = false;
2697        dock.update(cx, |dock, cx| {
2698            let other_is_zoomed = self.zoomed.is_some() && self.zoomed_position != Some(dock_side);
2699            let was_visible = dock.is_open() && !other_is_zoomed;
2700            dock.set_open(!was_visible, window, cx);
2701
2702            if dock.active_panel().is_none() {
2703                let Some(panel_ix) = dock
2704                    .first_enabled_panel_idx(cx)
2705                    .log_with_level(log::Level::Info)
2706                else {
2707                    return;
2708                };
2709                dock.activate_panel(panel_ix, window, cx);
2710            }
2711
2712            if let Some(active_panel) = dock.active_panel() {
2713                if was_visible {
2714                    if active_panel
2715                        .panel_focus_handle(cx)
2716                        .contains_focused(window, cx)
2717                    {
2718                        focus_center = true;
2719                    }
2720                } else {
2721                    let focus_handle = &active_panel.panel_focus_handle(cx);
2722                    window.focus(focus_handle);
2723                    reveal_dock = true;
2724                }
2725            }
2726        });
2727
2728        if reveal_dock {
2729            self.dismiss_zoomed_items_to_reveal(Some(dock_side), window, cx);
2730        }
2731
2732        if focus_center {
2733            self.active_pane
2734                .update(cx, |pane, cx| window.focus(&pane.focus_handle(cx)))
2735        }
2736
2737        cx.notify();
2738        self.serialize_workspace(window, cx);
2739    }
2740
2741    fn active_dock(&self, window: &Window, cx: &Context<Self>) -> Option<&Entity<Dock>> {
2742        self.all_docks().into_iter().find(|&dock| {
2743            dock.read(cx).is_open() && dock.focus_handle(cx).contains_focused(window, cx)
2744        })
2745    }
2746
2747    fn close_active_dock(&mut self, window: &mut Window, cx: &mut Context<Self>) {
2748        if let Some(dock) = self.active_dock(window, cx) {
2749            dock.update(cx, |dock, cx| {
2750                dock.set_open(false, window, cx);
2751            });
2752        }
2753    }
2754
2755    pub fn close_all_docks(&mut self, window: &mut Window, cx: &mut Context<Self>) {
2756        for dock in self.all_docks() {
2757            dock.update(cx, |dock, cx| {
2758                dock.set_open(false, window, cx);
2759            });
2760        }
2761
2762        cx.focus_self(window);
2763        cx.notify();
2764        self.serialize_workspace(window, cx);
2765    }
2766
2767    /// Transfer focus to the panel of the given type.
2768    pub fn focus_panel<T: Panel>(
2769        &mut self,
2770        window: &mut Window,
2771        cx: &mut Context<Self>,
2772    ) -> Option<Entity<T>> {
2773        let panel = self.focus_or_unfocus_panel::<T>(window, cx, |_, _, _| true)?;
2774        panel.to_any().downcast().ok()
2775    }
2776
2777    /// Focus the panel of the given type if it isn't already focused. If it is
2778    /// already focused, then transfer focus back to the workspace center.
2779    pub fn toggle_panel_focus<T: Panel>(
2780        &mut self,
2781        window: &mut Window,
2782        cx: &mut Context<Self>,
2783    ) -> bool {
2784        let mut did_focus_panel = false;
2785        self.focus_or_unfocus_panel::<T>(window, cx, |panel, window, cx| {
2786            did_focus_panel = !panel.panel_focus_handle(cx).contains_focused(window, cx);
2787            did_focus_panel
2788        });
2789        did_focus_panel
2790    }
2791
2792    pub fn activate_panel_for_proto_id(
2793        &mut self,
2794        panel_id: PanelId,
2795        window: &mut Window,
2796        cx: &mut Context<Self>,
2797    ) -> Option<Arc<dyn PanelHandle>> {
2798        let mut panel = None;
2799        for dock in self.all_docks() {
2800            if let Some(panel_index) = dock.read(cx).panel_index_for_proto_id(panel_id) {
2801                panel = dock.update(cx, |dock, cx| {
2802                    dock.activate_panel(panel_index, window, cx);
2803                    dock.set_open(true, window, cx);
2804                    dock.active_panel().cloned()
2805                });
2806                break;
2807            }
2808        }
2809
2810        if panel.is_some() {
2811            cx.notify();
2812            self.serialize_workspace(window, cx);
2813        }
2814
2815        panel
2816    }
2817
2818    /// Focus or unfocus the given panel type, depending on the given callback.
2819    fn focus_or_unfocus_panel<T: Panel>(
2820        &mut self,
2821        window: &mut Window,
2822        cx: &mut Context<Self>,
2823        mut should_focus: impl FnMut(&dyn PanelHandle, &mut Window, &mut Context<Dock>) -> bool,
2824    ) -> Option<Arc<dyn PanelHandle>> {
2825        let mut result_panel = None;
2826        let mut serialize = false;
2827        for dock in self.all_docks() {
2828            if let Some(panel_index) = dock.read(cx).panel_index_for_type::<T>() {
2829                let mut focus_center = false;
2830                let panel = dock.update(cx, |dock, cx| {
2831                    dock.activate_panel(panel_index, window, cx);
2832
2833                    let panel = dock.active_panel().cloned();
2834                    if let Some(panel) = panel.as_ref() {
2835                        if should_focus(&**panel, window, cx) {
2836                            dock.set_open(true, window, cx);
2837                            panel.panel_focus_handle(cx).focus(window);
2838                        } else {
2839                            focus_center = true;
2840                        }
2841                    }
2842                    panel
2843                });
2844
2845                if focus_center {
2846                    self.active_pane
2847                        .update(cx, |pane, cx| window.focus(&pane.focus_handle(cx)))
2848                }
2849
2850                result_panel = panel;
2851                serialize = true;
2852                break;
2853            }
2854        }
2855
2856        if serialize {
2857            self.serialize_workspace(window, cx);
2858        }
2859
2860        cx.notify();
2861        result_panel
2862    }
2863
2864    /// Open the panel of the given type
2865    pub fn open_panel<T: Panel>(&mut self, window: &mut Window, cx: &mut Context<Self>) {
2866        for dock in self.all_docks() {
2867            if let Some(panel_index) = dock.read(cx).panel_index_for_type::<T>() {
2868                dock.update(cx, |dock, cx| {
2869                    dock.activate_panel(panel_index, window, cx);
2870                    dock.set_open(true, window, cx);
2871                });
2872            }
2873        }
2874    }
2875
2876    pub fn panel<T: Panel>(&self, cx: &App) -> Option<Entity<T>> {
2877        self.all_docks()
2878            .iter()
2879            .find_map(|dock| dock.read(cx).panel::<T>())
2880    }
2881
2882    fn dismiss_zoomed_items_to_reveal(
2883        &mut self,
2884        dock_to_reveal: Option<DockPosition>,
2885        window: &mut Window,
2886        cx: &mut Context<Self>,
2887    ) {
2888        // If a center pane is zoomed, unzoom it.
2889        for pane in &self.panes {
2890            if pane != &self.active_pane || dock_to_reveal.is_some() {
2891                pane.update(cx, |pane, cx| pane.set_zoomed(false, cx));
2892            }
2893        }
2894
2895        // If another dock is zoomed, hide it.
2896        let mut focus_center = false;
2897        for dock in self.all_docks() {
2898            dock.update(cx, |dock, cx| {
2899                if Some(dock.position()) != dock_to_reveal {
2900                    if let Some(panel) = dock.active_panel() {
2901                        if panel.is_zoomed(window, cx) {
2902                            focus_center |=
2903                                panel.panel_focus_handle(cx).contains_focused(window, cx);
2904                            dock.set_open(false, window, cx);
2905                        }
2906                    }
2907                }
2908            });
2909        }
2910
2911        if focus_center {
2912            self.active_pane
2913                .update(cx, |pane, cx| window.focus(&pane.focus_handle(cx)))
2914        }
2915
2916        if self.zoomed_position != dock_to_reveal {
2917            self.zoomed = None;
2918            self.zoomed_position = None;
2919            cx.emit(Event::ZoomChanged);
2920        }
2921
2922        cx.notify();
2923    }
2924
2925    fn add_pane(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Entity<Pane> {
2926        let pane = cx.new(|cx| {
2927            let mut pane = Pane::new(
2928                self.weak_handle(),
2929                self.project.clone(),
2930                self.pane_history_timestamp.clone(),
2931                None,
2932                NewFile.boxed_clone(),
2933                window,
2934                cx,
2935            );
2936            pane.set_can_split(Some(Arc::new(|_, _, _, _| true)));
2937            pane
2938        });
2939        cx.subscribe_in(&pane, window, Self::handle_pane_event)
2940            .detach();
2941        self.panes.push(pane.clone());
2942
2943        window.focus(&pane.focus_handle(cx));
2944
2945        cx.emit(Event::PaneAdded(pane.clone()));
2946        pane
2947    }
2948
2949    pub fn add_item_to_center(
2950        &mut self,
2951        item: Box<dyn ItemHandle>,
2952        window: &mut Window,
2953        cx: &mut Context<Self>,
2954    ) -> bool {
2955        if let Some(center_pane) = self.last_active_center_pane.clone() {
2956            if let Some(center_pane) = center_pane.upgrade() {
2957                center_pane.update(cx, |pane, cx| {
2958                    pane.add_item(item, true, true, None, window, cx)
2959                });
2960                true
2961            } else {
2962                false
2963            }
2964        } else {
2965            false
2966        }
2967    }
2968
2969    pub fn add_item_to_active_pane(
2970        &mut self,
2971        item: Box<dyn ItemHandle>,
2972        destination_index: Option<usize>,
2973        focus_item: bool,
2974        window: &mut Window,
2975        cx: &mut App,
2976    ) {
2977        self.add_item(
2978            self.active_pane.clone(),
2979            item,
2980            destination_index,
2981            false,
2982            focus_item,
2983            window,
2984            cx,
2985        )
2986    }
2987
2988    pub fn add_item(
2989        &mut self,
2990        pane: Entity<Pane>,
2991        item: Box<dyn ItemHandle>,
2992        destination_index: Option<usize>,
2993        activate_pane: bool,
2994        focus_item: bool,
2995        window: &mut Window,
2996        cx: &mut App,
2997    ) {
2998        if let Some(text) = item.telemetry_event_text(cx) {
2999            telemetry::event!(text);
3000        }
3001
3002        pane.update(cx, |pane, cx| {
3003            pane.add_item(
3004                item,
3005                activate_pane,
3006                focus_item,
3007                destination_index,
3008                window,
3009                cx,
3010            )
3011        });
3012    }
3013
3014    pub fn split_item(
3015        &mut self,
3016        split_direction: SplitDirection,
3017        item: Box<dyn ItemHandle>,
3018        window: &mut Window,
3019        cx: &mut Context<Self>,
3020    ) {
3021        let new_pane = self.split_pane(self.active_pane.clone(), split_direction, window, cx);
3022        self.add_item(new_pane, item, None, true, true, window, cx);
3023    }
3024
3025    pub fn open_abs_path(
3026        &mut self,
3027        abs_path: PathBuf,
3028        options: OpenOptions,
3029        window: &mut Window,
3030        cx: &mut Context<Self>,
3031    ) -> Task<anyhow::Result<Box<dyn ItemHandle>>> {
3032        cx.spawn_in(window, async move |workspace, cx| {
3033            let open_paths_task_result = workspace
3034                .update_in(cx, |workspace, window, cx| {
3035                    workspace.open_paths(vec![abs_path.clone()], options, None, window, cx)
3036                })
3037                .with_context(|| format!("open abs path {abs_path:?} task spawn"))?
3038                .await;
3039            anyhow::ensure!(
3040                open_paths_task_result.len() == 1,
3041                "open abs path {abs_path:?} task returned incorrect number of results"
3042            );
3043            match open_paths_task_result
3044                .into_iter()
3045                .next()
3046                .expect("ensured single task result")
3047            {
3048                Some(open_result) => {
3049                    open_result.with_context(|| format!("open abs path {abs_path:?} task join"))
3050                }
3051                None => anyhow::bail!("open abs path {abs_path:?} task returned None"),
3052            }
3053        })
3054    }
3055
3056    pub fn split_abs_path(
3057        &mut self,
3058        abs_path: PathBuf,
3059        visible: bool,
3060        window: &mut Window,
3061        cx: &mut Context<Self>,
3062    ) -> Task<anyhow::Result<Box<dyn ItemHandle>>> {
3063        let project_path_task =
3064            Workspace::project_path_for_path(self.project.clone(), &abs_path, visible, cx);
3065        cx.spawn_in(window, async move |this, cx| {
3066            let (_, path) = project_path_task.await?;
3067            this.update_in(cx, |this, window, cx| this.split_path(path, window, cx))?
3068                .await
3069        })
3070    }
3071
3072    pub fn open_path(
3073        &mut self,
3074        path: impl Into<ProjectPath>,
3075        pane: Option<WeakEntity<Pane>>,
3076        focus_item: bool,
3077        window: &mut Window,
3078        cx: &mut App,
3079    ) -> Task<Result<Box<dyn ItemHandle>, anyhow::Error>> {
3080        self.open_path_preview(path, pane, focus_item, false, true, window, cx)
3081    }
3082
3083    pub fn open_path_preview(
3084        &mut self,
3085        path: impl Into<ProjectPath>,
3086        pane: Option<WeakEntity<Pane>>,
3087        focus_item: bool,
3088        allow_preview: bool,
3089        activate: bool,
3090        window: &mut Window,
3091        cx: &mut App,
3092    ) -> Task<Result<Box<dyn ItemHandle>, anyhow::Error>> {
3093        let pane = pane.unwrap_or_else(|| {
3094            self.last_active_center_pane.clone().unwrap_or_else(|| {
3095                self.panes
3096                    .first()
3097                    .expect("There must be an active pane")
3098                    .downgrade()
3099            })
3100        });
3101
3102        let project_path = path.into();
3103        let task = self.load_path(project_path.clone(), window, cx);
3104        window.spawn(cx, async move |cx| {
3105            let (project_entry_id, build_item) = task.await?;
3106            let result = pane.update_in(cx, |pane, window, cx| {
3107                let result = pane.open_item(
3108                    project_entry_id,
3109                    project_path,
3110                    focus_item,
3111                    allow_preview,
3112                    activate,
3113                    None,
3114                    window,
3115                    cx,
3116                    build_item,
3117                );
3118
3119                result
3120            });
3121            result
3122        })
3123    }
3124
3125    pub fn split_path(
3126        &mut self,
3127        path: impl Into<ProjectPath>,
3128        window: &mut Window,
3129        cx: &mut Context<Self>,
3130    ) -> Task<Result<Box<dyn ItemHandle>, anyhow::Error>> {
3131        self.split_path_preview(path, false, None, window, cx)
3132    }
3133
3134    pub fn split_path_preview(
3135        &mut self,
3136        path: impl Into<ProjectPath>,
3137        allow_preview: bool,
3138        split_direction: Option<SplitDirection>,
3139        window: &mut Window,
3140        cx: &mut Context<Self>,
3141    ) -> Task<Result<Box<dyn ItemHandle>, anyhow::Error>> {
3142        let pane = self.last_active_center_pane.clone().unwrap_or_else(|| {
3143            self.panes
3144                .first()
3145                .expect("There must be an active pane")
3146                .downgrade()
3147        });
3148
3149        if let Member::Pane(center_pane) = &self.center.root {
3150            if center_pane.read(cx).items_len() == 0 {
3151                return self.open_path(path, Some(pane), true, window, cx);
3152            }
3153        }
3154
3155        let project_path = path.into();
3156        let task = self.load_path(project_path.clone(), window, cx);
3157        cx.spawn_in(window, async move |this, cx| {
3158            let (project_entry_id, build_item) = task.await?;
3159            this.update_in(cx, move |this, window, cx| -> Option<_> {
3160                let pane = pane.upgrade()?;
3161                let new_pane = this.split_pane(
3162                    pane,
3163                    split_direction.unwrap_or(SplitDirection::Right),
3164                    window,
3165                    cx,
3166                );
3167                new_pane.update(cx, |new_pane, cx| {
3168                    Some(new_pane.open_item(
3169                        project_entry_id,
3170                        project_path,
3171                        true,
3172                        allow_preview,
3173                        true,
3174                        None,
3175                        window,
3176                        cx,
3177                        build_item,
3178                    ))
3179                })
3180            })
3181            .map(|option| option.ok_or_else(|| anyhow!("pane was dropped")))?
3182        })
3183    }
3184
3185    fn load_path(
3186        &mut self,
3187        path: ProjectPath,
3188        window: &mut Window,
3189        cx: &mut App,
3190    ) -> Task<Result<(Option<ProjectEntryId>, WorkspaceItemBuilder)>> {
3191        let project = self.project().clone();
3192        let registry = cx.default_global::<ProjectItemRegistry>().clone();
3193        registry.open_path(&project, &path, window, cx)
3194    }
3195
3196    pub fn find_project_item<T>(
3197        &self,
3198        pane: &Entity<Pane>,
3199        project_item: &Entity<T::Item>,
3200        cx: &App,
3201    ) -> Option<Entity<T>>
3202    where
3203        T: ProjectItem,
3204    {
3205        use project::ProjectItem as _;
3206        let project_item = project_item.read(cx);
3207        let entry_id = project_item.entry_id(cx);
3208        let project_path = project_item.project_path(cx);
3209
3210        let mut item = None;
3211        if let Some(entry_id) = entry_id {
3212            item = pane.read(cx).item_for_entry(entry_id, cx);
3213        }
3214        if item.is_none() {
3215            if let Some(project_path) = project_path {
3216                item = pane.read(cx).item_for_path(project_path, cx);
3217            }
3218        }
3219
3220        item.and_then(|item| item.downcast::<T>())
3221    }
3222
3223    pub fn is_project_item_open<T>(
3224        &self,
3225        pane: &Entity<Pane>,
3226        project_item: &Entity<T::Item>,
3227        cx: &App,
3228    ) -> bool
3229    where
3230        T: ProjectItem,
3231    {
3232        self.find_project_item::<T>(pane, project_item, cx)
3233            .is_some()
3234    }
3235
3236    pub fn open_project_item<T>(
3237        &mut self,
3238        pane: Entity<Pane>,
3239        project_item: Entity<T::Item>,
3240        activate_pane: bool,
3241        focus_item: bool,
3242        window: &mut Window,
3243        cx: &mut Context<Self>,
3244    ) -> Entity<T>
3245    where
3246        T: ProjectItem,
3247    {
3248        if let Some(item) = self.find_project_item(&pane, &project_item, cx) {
3249            self.activate_item(&item, activate_pane, focus_item, window, cx);
3250            return item;
3251        }
3252
3253        let item = pane.update(cx, |pane, cx| {
3254            cx.new(|cx| {
3255                T::for_project_item(self.project().clone(), Some(pane), project_item, window, cx)
3256            })
3257        });
3258        let item_id = item.item_id();
3259        let mut destination_index = None;
3260        pane.update(cx, |pane, cx| {
3261            if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
3262                if let Some(preview_item_id) = pane.preview_item_id() {
3263                    if preview_item_id != item_id {
3264                        destination_index = pane.close_current_preview_item(window, cx);
3265                    }
3266                }
3267            }
3268            pane.set_preview_item_id(Some(item.item_id()), cx)
3269        });
3270
3271        self.add_item(
3272            pane,
3273            Box::new(item.clone()),
3274            destination_index,
3275            activate_pane,
3276            focus_item,
3277            window,
3278            cx,
3279        );
3280        item
3281    }
3282
3283    pub fn open_shared_screen(
3284        &mut self,
3285        peer_id: PeerId,
3286        window: &mut Window,
3287        cx: &mut Context<Self>,
3288    ) {
3289        if let Some(shared_screen) =
3290            self.shared_screen_for_peer(peer_id, &self.active_pane, window, cx)
3291        {
3292            self.active_pane.update(cx, |pane, cx| {
3293                pane.add_item(Box::new(shared_screen), false, true, None, window, cx)
3294            });
3295        }
3296    }
3297
3298    pub fn activate_item(
3299        &mut self,
3300        item: &dyn ItemHandle,
3301        activate_pane: bool,
3302        focus_item: bool,
3303        window: &mut Window,
3304        cx: &mut App,
3305    ) -> bool {
3306        let result = self.panes.iter().find_map(|pane| {
3307            pane.read(cx)
3308                .index_for_item(item)
3309                .map(|ix| (pane.clone(), ix))
3310        });
3311        if let Some((pane, ix)) = result {
3312            pane.update(cx, |pane, cx| {
3313                pane.activate_item(ix, activate_pane, focus_item, window, cx)
3314            });
3315            true
3316        } else {
3317            false
3318        }
3319    }
3320
3321    fn activate_pane_at_index(
3322        &mut self,
3323        action: &ActivatePane,
3324        window: &mut Window,
3325        cx: &mut Context<Self>,
3326    ) {
3327        let panes = self.center.panes();
3328        if let Some(pane) = panes.get(action.0).map(|p| (*p).clone()) {
3329            window.focus(&pane.focus_handle(cx));
3330        } else {
3331            self.split_and_clone(self.active_pane.clone(), SplitDirection::Right, window, cx);
3332        }
3333    }
3334
3335    fn move_item_to_pane_at_index(
3336        &mut self,
3337        action: &MoveItemToPane,
3338        window: &mut Window,
3339        cx: &mut Context<Self>,
3340    ) {
3341        let Some(&target_pane) = self.center.panes().get(action.destination) else {
3342            return;
3343        };
3344        move_active_item(
3345            &self.active_pane,
3346            target_pane,
3347            action.focus,
3348            true,
3349            window,
3350            cx,
3351        );
3352    }
3353
3354    pub fn activate_next_pane(&mut self, window: &mut Window, cx: &mut App) {
3355        let panes = self.center.panes();
3356        if let Some(ix) = panes.iter().position(|pane| **pane == self.active_pane) {
3357            let next_ix = (ix + 1) % panes.len();
3358            let next_pane = panes[next_ix].clone();
3359            window.focus(&next_pane.focus_handle(cx));
3360        }
3361    }
3362
3363    pub fn activate_previous_pane(&mut self, window: &mut Window, cx: &mut App) {
3364        let panes = self.center.panes();
3365        if let Some(ix) = panes.iter().position(|pane| **pane == self.active_pane) {
3366            let prev_ix = cmp::min(ix.wrapping_sub(1), panes.len() - 1);
3367            let prev_pane = panes[prev_ix].clone();
3368            window.focus(&prev_pane.focus_handle(cx));
3369        }
3370    }
3371
3372    pub fn activate_pane_in_direction(
3373        &mut self,
3374        direction: SplitDirection,
3375        window: &mut Window,
3376        cx: &mut App,
3377    ) {
3378        use ActivateInDirectionTarget as Target;
3379        enum Origin {
3380            LeftDock,
3381            RightDock,
3382            BottomDock,
3383            Center,
3384        }
3385
3386        let origin: Origin = [
3387            (&self.left_dock, Origin::LeftDock),
3388            (&self.right_dock, Origin::RightDock),
3389            (&self.bottom_dock, Origin::BottomDock),
3390        ]
3391        .into_iter()
3392        .find_map(|(dock, origin)| {
3393            if dock.focus_handle(cx).contains_focused(window, cx) && dock.read(cx).is_open() {
3394                Some(origin)
3395            } else {
3396                None
3397            }
3398        })
3399        .unwrap_or(Origin::Center);
3400
3401        let get_last_active_pane = || {
3402            let pane = self
3403                .last_active_center_pane
3404                .clone()
3405                .unwrap_or_else(|| {
3406                    self.panes
3407                        .first()
3408                        .expect("There must be an active pane")
3409                        .downgrade()
3410                })
3411                .upgrade()?;
3412            (pane.read(cx).items_len() != 0).then_some(pane)
3413        };
3414
3415        let try_dock =
3416            |dock: &Entity<Dock>| dock.read(cx).is_open().then(|| Target::Dock(dock.clone()));
3417
3418        let target = match (origin, direction) {
3419            // We're in the center, so we first try to go to a different pane,
3420            // otherwise try to go to a dock.
3421            (Origin::Center, direction) => {
3422                if let Some(pane) = self.find_pane_in_direction(direction, cx) {
3423                    Some(Target::Pane(pane))
3424                } else {
3425                    match direction {
3426                        SplitDirection::Up => None,
3427                        SplitDirection::Down => try_dock(&self.bottom_dock),
3428                        SplitDirection::Left => try_dock(&self.left_dock),
3429                        SplitDirection::Right => try_dock(&self.right_dock),
3430                    }
3431                }
3432            }
3433
3434            (Origin::LeftDock, SplitDirection::Right) => {
3435                if let Some(last_active_pane) = get_last_active_pane() {
3436                    Some(Target::Pane(last_active_pane))
3437                } else {
3438                    try_dock(&self.bottom_dock).or_else(|| try_dock(&self.right_dock))
3439                }
3440            }
3441
3442            (Origin::LeftDock, SplitDirection::Down)
3443            | (Origin::RightDock, SplitDirection::Down) => try_dock(&self.bottom_dock),
3444
3445            (Origin::BottomDock, SplitDirection::Up) => get_last_active_pane().map(Target::Pane),
3446            (Origin::BottomDock, SplitDirection::Left) => try_dock(&self.left_dock),
3447            (Origin::BottomDock, SplitDirection::Right) => try_dock(&self.right_dock),
3448
3449            (Origin::RightDock, SplitDirection::Left) => {
3450                if let Some(last_active_pane) = get_last_active_pane() {
3451                    Some(Target::Pane(last_active_pane))
3452                } else {
3453                    try_dock(&self.bottom_dock).or_else(|| try_dock(&self.left_dock))
3454                }
3455            }
3456
3457            _ => None,
3458        };
3459
3460        match target {
3461            Some(ActivateInDirectionTarget::Pane(pane)) => {
3462                window.focus(&pane.focus_handle(cx));
3463            }
3464            Some(ActivateInDirectionTarget::Dock(dock)) => {
3465                // Defer this to avoid a panic when the dock's active panel is already on the stack.
3466                window.defer(cx, move |window, cx| {
3467                    let dock = dock.read(cx);
3468                    if let Some(panel) = dock.active_panel() {
3469                        panel.panel_focus_handle(cx).focus(window);
3470                    } else {
3471                        log::error!("Could not find a focus target when in switching focus in {direction} direction for a {:?} dock", dock.position());
3472                    }
3473                })
3474            }
3475            None => {}
3476        }
3477    }
3478
3479    pub fn move_item_to_pane_in_direction(
3480        &mut self,
3481        action: &MoveItemToPaneInDirection,
3482        window: &mut Window,
3483        cx: &mut App,
3484    ) {
3485        if let Some(destination) = self.find_pane_in_direction(action.direction, cx) {
3486            move_active_item(
3487                &self.active_pane,
3488                &destination,
3489                action.focus,
3490                true,
3491                window,
3492                cx,
3493            );
3494        }
3495    }
3496
3497    pub fn bounding_box_for_pane(&self, pane: &Entity<Pane>) -> Option<Bounds<Pixels>> {
3498        self.center.bounding_box_for_pane(pane)
3499    }
3500
3501    pub fn find_pane_in_direction(
3502        &mut self,
3503        direction: SplitDirection,
3504        cx: &App,
3505    ) -> Option<Entity<Pane>> {
3506        self.center
3507            .find_pane_in_direction(&self.active_pane, direction, cx)
3508            .cloned()
3509    }
3510
3511    pub fn swap_pane_in_direction(&mut self, direction: SplitDirection, cx: &mut Context<Self>) {
3512        if let Some(to) = self.find_pane_in_direction(direction, cx) {
3513            self.center.swap(&self.active_pane, &to);
3514            cx.notify();
3515        }
3516    }
3517
3518    pub fn resize_pane(
3519        &mut self,
3520        axis: gpui::Axis,
3521        amount: Pixels,
3522        window: &mut Window,
3523        cx: &mut Context<Self>,
3524    ) {
3525        let docks = self.all_docks();
3526        let active_dock = docks
3527            .into_iter()
3528            .find(|dock| dock.focus_handle(cx).contains_focused(window, cx));
3529
3530        if let Some(dock) = active_dock {
3531            let Some(panel_size) = dock.read(cx).active_panel_size(window, cx) else {
3532                return;
3533            };
3534            match dock.read(cx).position() {
3535                DockPosition::Left => resize_left_dock(panel_size + amount, self, window, cx),
3536                DockPosition::Bottom => resize_bottom_dock(panel_size + amount, self, window, cx),
3537                DockPosition::Right => resize_right_dock(panel_size + amount, self, window, cx),
3538            }
3539        } else {
3540            self.center
3541                .resize(&self.active_pane, axis, amount, &self.bounds);
3542        }
3543        cx.notify();
3544    }
3545
3546    pub fn reset_pane_sizes(&mut self, cx: &mut Context<Self>) {
3547        self.center.reset_pane_sizes();
3548        cx.notify();
3549    }
3550
3551    fn handle_pane_focused(
3552        &mut self,
3553        pane: Entity<Pane>,
3554        window: &mut Window,
3555        cx: &mut Context<Self>,
3556    ) {
3557        // This is explicitly hoisted out of the following check for pane identity as
3558        // terminal panel panes are not registered as a center panes.
3559        self.status_bar.update(cx, |status_bar, cx| {
3560            status_bar.set_active_pane(&pane, window, cx);
3561        });
3562        if self.active_pane != pane {
3563            self.set_active_pane(&pane, window, cx);
3564        }
3565
3566        if self.last_active_center_pane.is_none() {
3567            self.last_active_center_pane = Some(pane.downgrade());
3568        }
3569
3570        self.dismiss_zoomed_items_to_reveal(None, window, cx);
3571        if pane.read(cx).is_zoomed() {
3572            self.zoomed = Some(pane.downgrade().into());
3573        } else {
3574            self.zoomed = None;
3575        }
3576        self.zoomed_position = None;
3577        cx.emit(Event::ZoomChanged);
3578        self.update_active_view_for_followers(window, cx);
3579        pane.update(cx, |pane, _| {
3580            pane.track_alternate_file_items();
3581        });
3582
3583        cx.notify();
3584    }
3585
3586    fn set_active_pane(
3587        &mut self,
3588        pane: &Entity<Pane>,
3589        window: &mut Window,
3590        cx: &mut Context<Self>,
3591    ) {
3592        self.active_pane = pane.clone();
3593        self.active_item_path_changed(window, cx);
3594        self.last_active_center_pane = Some(pane.downgrade());
3595    }
3596
3597    fn handle_panel_focused(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3598        self.update_active_view_for_followers(window, cx);
3599    }
3600
3601    fn handle_pane_event(
3602        &mut self,
3603        pane: &Entity<Pane>,
3604        event: &pane::Event,
3605        window: &mut Window,
3606        cx: &mut Context<Self>,
3607    ) {
3608        let mut serialize_workspace = true;
3609        match event {
3610            pane::Event::AddItem { item } => {
3611                item.added_to_pane(self, pane.clone(), window, cx);
3612                cx.emit(Event::ItemAdded {
3613                    item: item.boxed_clone(),
3614                });
3615            }
3616            pane::Event::Split(direction) => {
3617                self.split_and_clone(pane.clone(), *direction, window, cx);
3618            }
3619            pane::Event::JoinIntoNext => {
3620                self.join_pane_into_next(pane.clone(), window, cx);
3621            }
3622            pane::Event::JoinAll => {
3623                self.join_all_panes(window, cx);
3624            }
3625            pane::Event::Remove { focus_on_pane } => {
3626                self.remove_pane(pane.clone(), focus_on_pane.clone(), window, cx);
3627            }
3628            pane::Event::ActivateItem {
3629                local,
3630                focus_changed,
3631            } => {
3632                cx.on_next_frame(window, |_, window, _| {
3633                    window.invalidate_character_coordinates();
3634                });
3635
3636                pane.update(cx, |pane, _| {
3637                    pane.track_alternate_file_items();
3638                });
3639                if *local {
3640                    self.unfollow_in_pane(&pane, window, cx);
3641                }
3642                if pane == self.active_pane() {
3643                    self.active_item_path_changed(window, cx);
3644                    self.update_active_view_for_followers(window, cx);
3645                }
3646                serialize_workspace = *focus_changed || pane != self.active_pane();
3647            }
3648            pane::Event::UserSavedItem { item, save_intent } => {
3649                cx.emit(Event::UserSavedItem {
3650                    pane: pane.downgrade(),
3651                    item: item.boxed_clone(),
3652                    save_intent: *save_intent,
3653                });
3654                serialize_workspace = false;
3655            }
3656            pane::Event::ChangeItemTitle => {
3657                if *pane == self.active_pane {
3658                    self.active_item_path_changed(window, cx);
3659                }
3660                serialize_workspace = false;
3661            }
3662            pane::Event::RemoveItem { .. } => {}
3663            pane::Event::RemovedItem { item } => {
3664                cx.emit(Event::ActiveItemChanged);
3665                self.update_window_edited(window, cx);
3666                if let hash_map::Entry::Occupied(entry) = self.panes_by_item.entry(item.item_id()) {
3667                    if entry.get().entity_id() == pane.entity_id() {
3668                        entry.remove();
3669                    }
3670                }
3671            }
3672            pane::Event::Focus => {
3673                cx.on_next_frame(window, |_, window, _| {
3674                    window.invalidate_character_coordinates();
3675                });
3676                self.handle_pane_focused(pane.clone(), window, cx);
3677            }
3678            pane::Event::ZoomIn => {
3679                if *pane == self.active_pane {
3680                    pane.update(cx, |pane, cx| pane.set_zoomed(true, cx));
3681                    if pane.read(cx).has_focus(window, cx) {
3682                        self.zoomed = Some(pane.downgrade().into());
3683                        self.zoomed_position = None;
3684                        cx.emit(Event::ZoomChanged);
3685                    }
3686                    cx.notify();
3687                }
3688            }
3689            pane::Event::ZoomOut => {
3690                pane.update(cx, |pane, cx| pane.set_zoomed(false, cx));
3691                if self.zoomed_position.is_none() {
3692                    self.zoomed = None;
3693                    cx.emit(Event::ZoomChanged);
3694                }
3695                cx.notify();
3696            }
3697        }
3698
3699        if serialize_workspace {
3700            self.serialize_workspace(window, cx);
3701        }
3702    }
3703
3704    pub fn unfollow_in_pane(
3705        &mut self,
3706        pane: &Entity<Pane>,
3707        window: &mut Window,
3708        cx: &mut Context<Workspace>,
3709    ) -> Option<CollaboratorId> {
3710        let leader_id = self.leader_for_pane(pane)?;
3711        self.unfollow(leader_id, window, cx);
3712        Some(leader_id)
3713    }
3714
3715    pub fn split_pane(
3716        &mut self,
3717        pane_to_split: Entity<Pane>,
3718        split_direction: SplitDirection,
3719        window: &mut Window,
3720        cx: &mut Context<Self>,
3721    ) -> Entity<Pane> {
3722        let new_pane = self.add_pane(window, cx);
3723        self.center
3724            .split(&pane_to_split, &new_pane, split_direction)
3725            .unwrap();
3726        cx.notify();
3727        new_pane
3728    }
3729
3730    pub fn split_and_clone(
3731        &mut self,
3732        pane: Entity<Pane>,
3733        direction: SplitDirection,
3734        window: &mut Window,
3735        cx: &mut Context<Self>,
3736    ) -> Option<Entity<Pane>> {
3737        let item = pane.read(cx).active_item()?;
3738        let maybe_pane_handle =
3739            if let Some(clone) = item.clone_on_split(self.database_id(), window, cx) {
3740                let new_pane = self.add_pane(window, cx);
3741                new_pane.update(cx, |pane, cx| {
3742                    pane.add_item(clone, true, true, None, window, cx)
3743                });
3744                self.center.split(&pane, &new_pane, direction).unwrap();
3745                Some(new_pane)
3746            } else {
3747                None
3748            };
3749        cx.notify();
3750        maybe_pane_handle
3751    }
3752
3753    pub fn split_pane_with_item(
3754        &mut self,
3755        pane_to_split: WeakEntity<Pane>,
3756        split_direction: SplitDirection,
3757        from: WeakEntity<Pane>,
3758        item_id_to_move: EntityId,
3759        window: &mut Window,
3760        cx: &mut Context<Self>,
3761    ) {
3762        let Some(pane_to_split) = pane_to_split.upgrade() else {
3763            return;
3764        };
3765        let Some(from) = from.upgrade() else {
3766            return;
3767        };
3768
3769        let new_pane = self.add_pane(window, cx);
3770        move_item(&from, &new_pane, item_id_to_move, 0, window, cx);
3771        self.center
3772            .split(&pane_to_split, &new_pane, split_direction)
3773            .unwrap();
3774        cx.notify();
3775    }
3776
3777    pub fn split_pane_with_project_entry(
3778        &mut self,
3779        pane_to_split: WeakEntity<Pane>,
3780        split_direction: SplitDirection,
3781        project_entry: ProjectEntryId,
3782        window: &mut Window,
3783        cx: &mut Context<Self>,
3784    ) -> Option<Task<Result<()>>> {
3785        let pane_to_split = pane_to_split.upgrade()?;
3786        let new_pane = self.add_pane(window, cx);
3787        self.center
3788            .split(&pane_to_split, &new_pane, split_direction)
3789            .unwrap();
3790
3791        let path = self.project.read(cx).path_for_entry(project_entry, cx)?;
3792        let task = self.open_path(path, Some(new_pane.downgrade()), true, window, cx);
3793        Some(cx.foreground_executor().spawn(async move {
3794            task.await?;
3795            Ok(())
3796        }))
3797    }
3798
3799    pub fn join_all_panes(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3800        let active_item = self.active_pane.read(cx).active_item();
3801        for pane in &self.panes {
3802            join_pane_into_active(&self.active_pane, pane, window, cx);
3803        }
3804        if let Some(active_item) = active_item {
3805            self.activate_item(active_item.as_ref(), true, true, window, cx);
3806        }
3807        cx.notify();
3808    }
3809
3810    pub fn join_pane_into_next(
3811        &mut self,
3812        pane: Entity<Pane>,
3813        window: &mut Window,
3814        cx: &mut Context<Self>,
3815    ) {
3816        let next_pane = self
3817            .find_pane_in_direction(SplitDirection::Right, cx)
3818            .or_else(|| self.find_pane_in_direction(SplitDirection::Down, cx))
3819            .or_else(|| self.find_pane_in_direction(SplitDirection::Left, cx))
3820            .or_else(|| self.find_pane_in_direction(SplitDirection::Up, cx));
3821        let Some(next_pane) = next_pane else {
3822            return;
3823        };
3824        move_all_items(&pane, &next_pane, window, cx);
3825        cx.notify();
3826    }
3827
3828    fn remove_pane(
3829        &mut self,
3830        pane: Entity<Pane>,
3831        focus_on: Option<Entity<Pane>>,
3832        window: &mut Window,
3833        cx: &mut Context<Self>,
3834    ) {
3835        if self.center.remove(&pane).unwrap() {
3836            self.force_remove_pane(&pane, &focus_on, window, cx);
3837            self.unfollow_in_pane(&pane, window, cx);
3838            self.last_leaders_by_pane.remove(&pane.downgrade());
3839            for removed_item in pane.read(cx).items() {
3840                self.panes_by_item.remove(&removed_item.item_id());
3841            }
3842
3843            cx.notify();
3844        } else {
3845            self.active_item_path_changed(window, cx);
3846        }
3847        cx.emit(Event::PaneRemoved);
3848    }
3849
3850    pub fn panes(&self) -> &[Entity<Pane>] {
3851        &self.panes
3852    }
3853
3854    pub fn active_pane(&self) -> &Entity<Pane> {
3855        &self.active_pane
3856    }
3857
3858    pub fn focused_pane(&self, window: &Window, cx: &App) -> Entity<Pane> {
3859        for dock in self.all_docks() {
3860            if dock.focus_handle(cx).contains_focused(window, cx) {
3861                if let Some(pane) = dock
3862                    .read(cx)
3863                    .active_panel()
3864                    .and_then(|panel| panel.pane(cx))
3865                {
3866                    return pane;
3867                }
3868            }
3869        }
3870        self.active_pane().clone()
3871    }
3872
3873    pub fn adjacent_pane(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Entity<Pane> {
3874        self.find_pane_in_direction(SplitDirection::Right, cx)
3875            .or_else(|| self.find_pane_in_direction(SplitDirection::Left, cx))
3876            .unwrap_or_else(|| {
3877                self.split_pane(self.active_pane.clone(), SplitDirection::Right, window, cx)
3878            })
3879            .clone()
3880    }
3881
3882    pub fn pane_for(&self, handle: &dyn ItemHandle) -> Option<Entity<Pane>> {
3883        let weak_pane = self.panes_by_item.get(&handle.item_id())?;
3884        weak_pane.upgrade()
3885    }
3886
3887    fn collaborator_left(&mut self, peer_id: PeerId, window: &mut Window, cx: &mut Context<Self>) {
3888        self.follower_states.retain(|leader_id, state| {
3889            if *leader_id == CollaboratorId::PeerId(peer_id) {
3890                for item in state.items_by_leader_view_id.values() {
3891                    item.view.set_leader_id(None, window, cx);
3892                }
3893                false
3894            } else {
3895                true
3896            }
3897        });
3898        cx.notify();
3899    }
3900
3901    pub fn start_following(
3902        &mut self,
3903        leader_id: impl Into<CollaboratorId>,
3904        window: &mut Window,
3905        cx: &mut Context<Self>,
3906    ) -> Option<Task<Result<()>>> {
3907        let leader_id = leader_id.into();
3908        let pane = self.active_pane().clone();
3909
3910        self.last_leaders_by_pane
3911            .insert(pane.downgrade(), leader_id);
3912        self.unfollow(leader_id, window, cx);
3913        self.unfollow_in_pane(&pane, window, cx);
3914        self.follower_states.insert(
3915            leader_id,
3916            FollowerState {
3917                center_pane: pane.clone(),
3918                dock_pane: None,
3919                active_view_id: None,
3920                items_by_leader_view_id: Default::default(),
3921            },
3922        );
3923        cx.notify();
3924
3925        match leader_id {
3926            CollaboratorId::PeerId(leader_peer_id) => {
3927                let room_id = self.active_call()?.read(cx).room()?.read(cx).id();
3928                let project_id = self.project.read(cx).remote_id();
3929                let request = self.app_state.client.request(proto::Follow {
3930                    room_id,
3931                    project_id,
3932                    leader_id: Some(leader_peer_id),
3933                });
3934
3935                Some(cx.spawn_in(window, async move |this, cx| {
3936                    let response = request.await?;
3937                    this.update(cx, |this, _| {
3938                        let state = this
3939                            .follower_states
3940                            .get_mut(&leader_id)
3941                            .ok_or_else(|| anyhow!("following interrupted"))?;
3942                        state.active_view_id = response
3943                            .active_view
3944                            .as_ref()
3945                            .and_then(|view| ViewId::from_proto(view.id.clone()?).ok());
3946                        Ok::<_, anyhow::Error>(())
3947                    })??;
3948                    if let Some(view) = response.active_view {
3949                        Self::add_view_from_leader(this.clone(), leader_peer_id, &view, cx).await?;
3950                    }
3951                    this.update_in(cx, |this, window, cx| {
3952                        this.leader_updated(leader_id, window, cx)
3953                    })?;
3954                    Ok(())
3955                }))
3956            }
3957            CollaboratorId::Agent => {
3958                self.leader_updated(leader_id, window, cx)?;
3959                Some(Task::ready(Ok(())))
3960            }
3961        }
3962    }
3963
3964    pub fn follow_next_collaborator(
3965        &mut self,
3966        _: &FollowNextCollaborator,
3967        window: &mut Window,
3968        cx: &mut Context<Self>,
3969    ) {
3970        let collaborators = self.project.read(cx).collaborators();
3971        let next_leader_id = if let Some(leader_id) = self.leader_for_pane(&self.active_pane) {
3972            let mut collaborators = collaborators.keys().copied();
3973            for peer_id in collaborators.by_ref() {
3974                if CollaboratorId::PeerId(peer_id) == leader_id {
3975                    break;
3976                }
3977            }
3978            collaborators.next().map(CollaboratorId::PeerId)
3979        } else if let Some(last_leader_id) =
3980            self.last_leaders_by_pane.get(&self.active_pane.downgrade())
3981        {
3982            match last_leader_id {
3983                CollaboratorId::PeerId(peer_id) => {
3984                    if collaborators.contains_key(peer_id) {
3985                        Some(*last_leader_id)
3986                    } else {
3987                        None
3988                    }
3989                }
3990                CollaboratorId::Agent => Some(CollaboratorId::Agent),
3991            }
3992        } else {
3993            None
3994        };
3995
3996        let pane = self.active_pane.clone();
3997        let Some(leader_id) = next_leader_id.or_else(|| {
3998            Some(CollaboratorId::PeerId(
3999                collaborators.keys().copied().next()?,
4000            ))
4001        }) else {
4002            return;
4003        };
4004        if self.unfollow_in_pane(&pane, window, cx) == Some(leader_id) {
4005            return;
4006        }
4007        if let Some(task) = self.start_following(leader_id, window, cx) {
4008            task.detach_and_log_err(cx)
4009        }
4010    }
4011
4012    pub fn follow(
4013        &mut self,
4014        leader_id: impl Into<CollaboratorId>,
4015        window: &mut Window,
4016        cx: &mut Context<Self>,
4017    ) {
4018        let leader_id = leader_id.into();
4019
4020        if let CollaboratorId::PeerId(peer_id) = leader_id {
4021            let Some(room) = ActiveCall::global(cx).read(cx).room() else {
4022                return;
4023            };
4024            let room = room.read(cx);
4025            let Some(remote_participant) = room.remote_participant_for_peer_id(peer_id) else {
4026                return;
4027            };
4028
4029            let project = self.project.read(cx);
4030
4031            let other_project_id = match remote_participant.location {
4032                call::ParticipantLocation::External => None,
4033                call::ParticipantLocation::UnsharedProject => None,
4034                call::ParticipantLocation::SharedProject { project_id } => {
4035                    if Some(project_id) == project.remote_id() {
4036                        None
4037                    } else {
4038                        Some(project_id)
4039                    }
4040                }
4041            };
4042
4043            // if they are active in another project, follow there.
4044            if let Some(project_id) = other_project_id {
4045                let app_state = self.app_state.clone();
4046                crate::join_in_room_project(project_id, remote_participant.user.id, app_state, cx)
4047                    .detach_and_log_err(cx);
4048            }
4049        }
4050
4051        // if you're already following, find the right pane and focus it.
4052        if let Some(follower_state) = self.follower_states.get(&leader_id) {
4053            window.focus(&follower_state.pane().focus_handle(cx));
4054
4055            return;
4056        }
4057
4058        // Otherwise, follow.
4059        if let Some(task) = self.start_following(leader_id, window, cx) {
4060            task.detach_and_log_err(cx)
4061        }
4062    }
4063
4064    pub fn unfollow(
4065        &mut self,
4066        leader_id: impl Into<CollaboratorId>,
4067        window: &mut Window,
4068        cx: &mut Context<Self>,
4069    ) -> Option<()> {
4070        cx.notify();
4071
4072        let leader_id = leader_id.into();
4073        let state = self.follower_states.remove(&leader_id)?;
4074        for (_, item) in state.items_by_leader_view_id {
4075            item.view.set_leader_id(None, window, cx);
4076        }
4077
4078        if let CollaboratorId::PeerId(leader_peer_id) = leader_id {
4079            let project_id = self.project.read(cx).remote_id();
4080            let room_id = self.active_call()?.read(cx).room()?.read(cx).id();
4081            self.app_state
4082                .client
4083                .send(proto::Unfollow {
4084                    room_id,
4085                    project_id,
4086                    leader_id: Some(leader_peer_id),
4087                })
4088                .log_err();
4089        }
4090
4091        Some(())
4092    }
4093
4094    pub fn is_being_followed(&self, id: impl Into<CollaboratorId>) -> bool {
4095        self.follower_states.contains_key(&id.into())
4096    }
4097
4098    fn active_item_path_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4099        cx.emit(Event::ActiveItemChanged);
4100        let active_entry = self.active_project_path(cx);
4101        self.project
4102            .update(cx, |project, cx| project.set_active_path(active_entry, cx));
4103
4104        self.update_window_title(window, cx);
4105    }
4106
4107    fn update_window_title(&mut self, window: &mut Window, cx: &mut App) {
4108        let project = self.project().read(cx);
4109        let mut title = String::new();
4110
4111        for (i, name) in project.worktree_root_names(cx).enumerate() {
4112            if i > 0 {
4113                title.push_str(", ");
4114            }
4115            title.push_str(name);
4116        }
4117
4118        if title.is_empty() {
4119            title = "empty project".to_string();
4120        }
4121
4122        if let Some(path) = self.active_item(cx).and_then(|item| item.project_path(cx)) {
4123            let filename = path
4124                .path
4125                .file_name()
4126                .map(|s| s.to_string_lossy())
4127                .or_else(|| {
4128                    Some(Cow::Borrowed(
4129                        project
4130                            .worktree_for_id(path.worktree_id, cx)?
4131                            .read(cx)
4132                            .root_name(),
4133                    ))
4134                });
4135
4136            if let Some(filename) = filename {
4137                title.push_str("");
4138                title.push_str(filename.as_ref());
4139            }
4140        }
4141
4142        if project.is_via_collab() {
4143            title.push_str("");
4144        } else if project.is_shared() {
4145            title.push_str("");
4146        }
4147
4148        window.set_window_title(&title);
4149    }
4150
4151    fn update_window_edited(&mut self, window: &mut Window, cx: &mut App) {
4152        let is_edited = !self.project.read(cx).is_disconnected(cx) && !self.dirty_items.is_empty();
4153        if is_edited != self.window_edited {
4154            self.window_edited = is_edited;
4155            window.set_window_edited(self.window_edited)
4156        }
4157    }
4158
4159    fn update_item_dirty_state(
4160        &mut self,
4161        item: &dyn ItemHandle,
4162        window: &mut Window,
4163        cx: &mut App,
4164    ) {
4165        let is_dirty = item.is_dirty(cx);
4166        let item_id = item.item_id();
4167        let was_dirty = self.dirty_items.contains_key(&item_id);
4168        if is_dirty == was_dirty {
4169            return;
4170        }
4171        if was_dirty {
4172            self.dirty_items.remove(&item_id);
4173            self.update_window_edited(window, cx);
4174            return;
4175        }
4176        if let Some(window_handle) = window.window_handle().downcast::<Self>() {
4177            let s = item.on_release(
4178                cx,
4179                Box::new(move |cx| {
4180                    window_handle
4181                        .update(cx, |this, window, cx| {
4182                            this.dirty_items.remove(&item_id);
4183                            this.update_window_edited(window, cx)
4184                        })
4185                        .ok();
4186                }),
4187            );
4188            self.dirty_items.insert(item_id, s);
4189            self.update_window_edited(window, cx);
4190        }
4191    }
4192
4193    fn render_notifications(&self, _window: &mut Window, _cx: &mut Context<Self>) -> Option<Div> {
4194        if self.notifications.is_empty() {
4195            None
4196        } else {
4197            Some(
4198                div()
4199                    .absolute()
4200                    .right_3()
4201                    .bottom_3()
4202                    .w_112()
4203                    .h_full()
4204                    .flex()
4205                    .flex_col()
4206                    .justify_end()
4207                    .gap_2()
4208                    .children(
4209                        self.notifications
4210                            .iter()
4211                            .map(|(_, notification)| notification.clone().into_any()),
4212                    ),
4213            )
4214        }
4215    }
4216
4217    // RPC handlers
4218
4219    fn active_view_for_follower(
4220        &self,
4221        follower_project_id: Option<u64>,
4222        window: &mut Window,
4223        cx: &mut Context<Self>,
4224    ) -> Option<proto::View> {
4225        let (item, panel_id) = self.active_item_for_followers(window, cx);
4226        let item = item?;
4227        let leader_id = self
4228            .pane_for(&*item)
4229            .and_then(|pane| self.leader_for_pane(&pane));
4230        let leader_peer_id = match leader_id {
4231            Some(CollaboratorId::PeerId(peer_id)) => Some(peer_id),
4232            Some(CollaboratorId::Agent) | None => None,
4233        };
4234
4235        let item_handle = item.to_followable_item_handle(cx)?;
4236        let id = item_handle.remote_id(&self.app_state.client, window, cx)?;
4237        let variant = item_handle.to_state_proto(window, cx)?;
4238
4239        if item_handle.is_project_item(window, cx)
4240            && (follower_project_id.is_none()
4241                || follower_project_id != self.project.read(cx).remote_id())
4242        {
4243            return None;
4244        }
4245
4246        Some(proto::View {
4247            id: id.to_proto(),
4248            leader_id: leader_peer_id,
4249            variant: Some(variant),
4250            panel_id: panel_id.map(|id| id as i32),
4251        })
4252    }
4253
4254    fn handle_follow(
4255        &mut self,
4256        follower_project_id: Option<u64>,
4257        window: &mut Window,
4258        cx: &mut Context<Self>,
4259    ) -> proto::FollowResponse {
4260        let active_view = self.active_view_for_follower(follower_project_id, window, cx);
4261
4262        cx.notify();
4263        proto::FollowResponse {
4264            // TODO: Remove after version 0.145.x stabilizes.
4265            active_view_id: active_view.as_ref().and_then(|view| view.id.clone()),
4266            views: active_view.iter().cloned().collect(),
4267            active_view,
4268        }
4269    }
4270
4271    fn handle_update_followers(
4272        &mut self,
4273        leader_id: PeerId,
4274        message: proto::UpdateFollowers,
4275        _window: &mut Window,
4276        _cx: &mut Context<Self>,
4277    ) {
4278        self.leader_updates_tx
4279            .unbounded_send((leader_id, message))
4280            .ok();
4281    }
4282
4283    async fn process_leader_update(
4284        this: &WeakEntity<Self>,
4285        leader_id: PeerId,
4286        update: proto::UpdateFollowers,
4287        cx: &mut AsyncWindowContext,
4288    ) -> Result<()> {
4289        match update.variant.ok_or_else(|| anyhow!("invalid update"))? {
4290            proto::update_followers::Variant::CreateView(view) => {
4291                let view_id = ViewId::from_proto(view.id.clone().context("invalid view id")?)?;
4292                let should_add_view = this.update(cx, |this, _| {
4293                    if let Some(state) = this.follower_states.get_mut(&leader_id.into()) {
4294                        anyhow::Ok(!state.items_by_leader_view_id.contains_key(&view_id))
4295                    } else {
4296                        anyhow::Ok(false)
4297                    }
4298                })??;
4299
4300                if should_add_view {
4301                    Self::add_view_from_leader(this.clone(), leader_id, &view, cx).await?
4302                }
4303            }
4304            proto::update_followers::Variant::UpdateActiveView(update_active_view) => {
4305                let should_add_view = this.update(cx, |this, _| {
4306                    if let Some(state) = this.follower_states.get_mut(&leader_id.into()) {
4307                        state.active_view_id = update_active_view
4308                            .view
4309                            .as_ref()
4310                            .and_then(|view| ViewId::from_proto(view.id.clone()?).ok());
4311
4312                        if state.active_view_id.is_some_and(|view_id| {
4313                            !state.items_by_leader_view_id.contains_key(&view_id)
4314                        }) {
4315                            anyhow::Ok(true)
4316                        } else {
4317                            anyhow::Ok(false)
4318                        }
4319                    } else {
4320                        anyhow::Ok(false)
4321                    }
4322                })??;
4323
4324                if should_add_view {
4325                    if let Some(view) = update_active_view.view {
4326                        Self::add_view_from_leader(this.clone(), leader_id, &view, cx).await?
4327                    }
4328                }
4329            }
4330            proto::update_followers::Variant::UpdateView(update_view) => {
4331                let variant = update_view
4332                    .variant
4333                    .ok_or_else(|| anyhow!("missing update view variant"))?;
4334                let id = update_view
4335                    .id
4336                    .ok_or_else(|| anyhow!("missing update view id"))?;
4337                let mut tasks = Vec::new();
4338                this.update_in(cx, |this, window, cx| {
4339                    let project = this.project.clone();
4340                    if let Some(state) = this.follower_states.get(&leader_id.into()) {
4341                        let view_id = ViewId::from_proto(id.clone())?;
4342                        if let Some(item) = state.items_by_leader_view_id.get(&view_id) {
4343                            tasks.push(item.view.apply_update_proto(
4344                                &project,
4345                                variant.clone(),
4346                                window,
4347                                cx,
4348                            ));
4349                        }
4350                    }
4351                    anyhow::Ok(())
4352                })??;
4353                try_join_all(tasks).await.log_err();
4354            }
4355        }
4356        this.update_in(cx, |this, window, cx| {
4357            this.leader_updated(leader_id, window, cx)
4358        })?;
4359        Ok(())
4360    }
4361
4362    async fn add_view_from_leader(
4363        this: WeakEntity<Self>,
4364        leader_id: PeerId,
4365        view: &proto::View,
4366        cx: &mut AsyncWindowContext,
4367    ) -> Result<()> {
4368        let this = this.upgrade().context("workspace dropped")?;
4369
4370        let Some(id) = view.id.clone() else {
4371            return Err(anyhow!("no id for view"));
4372        };
4373        let id = ViewId::from_proto(id)?;
4374        let panel_id = view.panel_id.and_then(proto::PanelId::from_i32);
4375
4376        let pane = this.update(cx, |this, _cx| {
4377            let state = this
4378                .follower_states
4379                .get(&leader_id.into())
4380                .context("stopped following")?;
4381            anyhow::Ok(state.pane().clone())
4382        })??;
4383        let existing_item = pane.update_in(cx, |pane, window, cx| {
4384            let client = this.read(cx).client().clone();
4385            pane.items().find_map(|item| {
4386                let item = item.to_followable_item_handle(cx)?;
4387                if item.remote_id(&client, window, cx) == Some(id) {
4388                    Some(item)
4389                } else {
4390                    None
4391                }
4392            })
4393        })?;
4394        let item = if let Some(existing_item) = existing_item {
4395            existing_item
4396        } else {
4397            let variant = view.variant.clone();
4398            if variant.is_none() {
4399                Err(anyhow!("missing view variant"))?;
4400            }
4401
4402            let task = cx.update(|window, cx| {
4403                FollowableViewRegistry::from_state_proto(this.clone(), id, variant, window, cx)
4404            })?;
4405
4406            let Some(task) = task else {
4407                return Err(anyhow!(
4408                    "failed to construct view from leader (maybe from a different version of zed?)"
4409                ));
4410            };
4411
4412            let mut new_item = task.await?;
4413            pane.update_in(cx, |pane, window, cx| {
4414                let mut item_to_remove = None;
4415                for (ix, item) in pane.items().enumerate() {
4416                    if let Some(item) = item.to_followable_item_handle(cx) {
4417                        match new_item.dedup(item.as_ref(), window, cx) {
4418                            Some(item::Dedup::KeepExisting) => {
4419                                new_item =
4420                                    item.boxed_clone().to_followable_item_handle(cx).unwrap();
4421                                break;
4422                            }
4423                            Some(item::Dedup::ReplaceExisting) => {
4424                                item_to_remove = Some((ix, item.item_id()));
4425                                break;
4426                            }
4427                            None => {}
4428                        }
4429                    }
4430                }
4431
4432                if let Some((ix, id)) = item_to_remove {
4433                    pane.remove_item(id, false, false, window, cx);
4434                    pane.add_item(new_item.boxed_clone(), false, false, Some(ix), window, cx);
4435                }
4436            })?;
4437
4438            new_item
4439        };
4440
4441        this.update_in(cx, |this, window, cx| {
4442            let state = this.follower_states.get_mut(&leader_id.into())?;
4443            item.set_leader_id(Some(leader_id.into()), window, cx);
4444            state.items_by_leader_view_id.insert(
4445                id,
4446                FollowerView {
4447                    view: item,
4448                    location: panel_id,
4449                },
4450            );
4451
4452            Some(())
4453        })?;
4454
4455        Ok(())
4456    }
4457
4458    fn handle_agent_location_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4459        let Some(follower_state) = self.follower_states.get_mut(&CollaboratorId::Agent) else {
4460            return;
4461        };
4462
4463        if let Some(agent_location) = self.project.read(cx).agent_location() {
4464            let buffer_entity_id = agent_location.buffer.entity_id();
4465            let view_id = ViewId {
4466                creator: CollaboratorId::Agent,
4467                id: buffer_entity_id.as_u64(),
4468            };
4469            follower_state.active_view_id = Some(view_id);
4470
4471            let item = match follower_state.items_by_leader_view_id.entry(view_id) {
4472                hash_map::Entry::Occupied(entry) => Some(entry.into_mut()),
4473                hash_map::Entry::Vacant(entry) => {
4474                    let existing_view =
4475                        follower_state
4476                            .center_pane
4477                            .read(cx)
4478                            .items()
4479                            .find_map(|item| {
4480                                let item = item.to_followable_item_handle(cx)?;
4481                                if item.is_singleton(cx)
4482                                    && item.project_item_model_ids(cx).as_slice()
4483                                        == [buffer_entity_id]
4484                                {
4485                                    Some(item)
4486                                } else {
4487                                    None
4488                                }
4489                            });
4490                    let view = existing_view.or_else(|| {
4491                        agent_location.buffer.upgrade().and_then(|buffer| {
4492                            cx.update_default_global(|registry: &mut ProjectItemRegistry, cx| {
4493                                registry.build_item(buffer, self.project.clone(), None, window, cx)
4494                            })?
4495                            .to_followable_item_handle(cx)
4496                        })
4497                    });
4498
4499                    if let Some(view) = view {
4500                        Some(entry.insert(FollowerView {
4501                            view,
4502                            location: None,
4503                        }))
4504                    } else {
4505                        None
4506                    }
4507                }
4508            };
4509
4510            if let Some(item) = item {
4511                item.view
4512                    .set_leader_id(Some(CollaboratorId::Agent), window, cx);
4513                item.view
4514                    .update_agent_location(agent_location.position, window, cx);
4515            }
4516        } else {
4517            follower_state.active_view_id = None;
4518        }
4519
4520        self.leader_updated(CollaboratorId::Agent, window, cx);
4521    }
4522
4523    pub fn update_active_view_for_followers(&mut self, window: &mut Window, cx: &mut App) {
4524        let mut is_project_item = true;
4525        let mut update = proto::UpdateActiveView::default();
4526        if window.is_window_active() {
4527            let (active_item, panel_id) = self.active_item_for_followers(window, cx);
4528
4529            if let Some(item) = active_item {
4530                if item.item_focus_handle(cx).contains_focused(window, cx) {
4531                    let leader_id = self
4532                        .pane_for(&*item)
4533                        .and_then(|pane| self.leader_for_pane(&pane));
4534                    let leader_peer_id = match leader_id {
4535                        Some(CollaboratorId::PeerId(peer_id)) => Some(peer_id),
4536                        Some(CollaboratorId::Agent) | None => None,
4537                    };
4538
4539                    if let Some(item) = item.to_followable_item_handle(cx) {
4540                        let id = item
4541                            .remote_id(&self.app_state.client, window, cx)
4542                            .map(|id| id.to_proto());
4543
4544                        if let Some(id) = id.clone() {
4545                            if let Some(variant) = item.to_state_proto(window, cx) {
4546                                let view = Some(proto::View {
4547                                    id: id.clone(),
4548                                    leader_id: leader_peer_id,
4549                                    variant: Some(variant),
4550                                    panel_id: panel_id.map(|id| id as i32),
4551                                });
4552
4553                                is_project_item = item.is_project_item(window, cx);
4554                                update = proto::UpdateActiveView {
4555                                    view,
4556                                    // TODO: Remove after version 0.145.x stabilizes.
4557                                    id: id.clone(),
4558                                    leader_id: leader_peer_id,
4559                                };
4560                            }
4561                        };
4562                    }
4563                }
4564            }
4565        }
4566
4567        let active_view_id = update.view.as_ref().and_then(|view| view.id.as_ref());
4568        if active_view_id != self.last_active_view_id.as_ref() {
4569            self.last_active_view_id = active_view_id.cloned();
4570            self.update_followers(
4571                is_project_item,
4572                proto::update_followers::Variant::UpdateActiveView(update),
4573                window,
4574                cx,
4575            );
4576        }
4577    }
4578
4579    fn active_item_for_followers(
4580        &self,
4581        window: &mut Window,
4582        cx: &mut App,
4583    ) -> (Option<Box<dyn ItemHandle>>, Option<proto::PanelId>) {
4584        let mut active_item = None;
4585        let mut panel_id = None;
4586        for dock in self.all_docks() {
4587            if dock.focus_handle(cx).contains_focused(window, cx) {
4588                if let Some(panel) = dock.read(cx).active_panel() {
4589                    if let Some(pane) = panel.pane(cx) {
4590                        if let Some(item) = pane.read(cx).active_item() {
4591                            active_item = Some(item);
4592                            panel_id = panel.remote_id();
4593                            break;
4594                        }
4595                    }
4596                }
4597            }
4598        }
4599
4600        if active_item.is_none() {
4601            active_item = self.active_pane().read(cx).active_item();
4602        }
4603        (active_item, panel_id)
4604    }
4605
4606    fn update_followers(
4607        &self,
4608        project_only: bool,
4609        update: proto::update_followers::Variant,
4610        _: &mut Window,
4611        cx: &mut App,
4612    ) -> Option<()> {
4613        // If this update only applies to for followers in the current project,
4614        // then skip it unless this project is shared. If it applies to all
4615        // followers, regardless of project, then set `project_id` to none,
4616        // indicating that it goes to all followers.
4617        let project_id = if project_only {
4618            Some(self.project.read(cx).remote_id()?)
4619        } else {
4620            None
4621        };
4622        self.app_state().workspace_store.update(cx, |store, cx| {
4623            store.update_followers(project_id, update, cx)
4624        })
4625    }
4626
4627    pub fn leader_for_pane(&self, pane: &Entity<Pane>) -> Option<CollaboratorId> {
4628        self.follower_states.iter().find_map(|(leader_id, state)| {
4629            if state.center_pane == *pane || state.dock_pane.as_ref() == Some(pane) {
4630                Some(*leader_id)
4631            } else {
4632                None
4633            }
4634        })
4635    }
4636
4637    fn leader_updated(
4638        &mut self,
4639        leader_id: impl Into<CollaboratorId>,
4640        window: &mut Window,
4641        cx: &mut Context<Self>,
4642    ) -> Option<Box<dyn ItemHandle>> {
4643        cx.notify();
4644
4645        let leader_id = leader_id.into();
4646        let (panel_id, item) = match leader_id {
4647            CollaboratorId::PeerId(peer_id) => self.active_item_for_peer(peer_id, window, cx)?,
4648            CollaboratorId::Agent => (None, self.active_item_for_agent()?),
4649        };
4650
4651        let state = self.follower_states.get(&leader_id)?;
4652        let mut transfer_focus = state.center_pane.read(cx).has_focus(window, cx);
4653        let pane;
4654        if let Some(panel_id) = panel_id {
4655            pane = self
4656                .activate_panel_for_proto_id(panel_id, window, cx)?
4657                .pane(cx)?;
4658            let state = self.follower_states.get_mut(&leader_id)?;
4659            state.dock_pane = Some(pane.clone());
4660        } else {
4661            pane = state.center_pane.clone();
4662            let state = self.follower_states.get_mut(&leader_id)?;
4663            if let Some(dock_pane) = state.dock_pane.take() {
4664                transfer_focus |= dock_pane.focus_handle(cx).contains_focused(window, cx);
4665            }
4666        }
4667
4668        pane.update(cx, |pane, cx| {
4669            let focus_active_item = pane.has_focus(window, cx) || transfer_focus;
4670            if let Some(index) = pane.index_for_item(item.as_ref()) {
4671                pane.activate_item(index, false, false, window, cx);
4672            } else {
4673                pane.add_item(item.boxed_clone(), false, false, None, window, cx)
4674            }
4675
4676            if focus_active_item {
4677                pane.focus_active_item(window, cx)
4678            }
4679        });
4680
4681        Some(item)
4682    }
4683
4684    fn active_item_for_agent(&self) -> Option<Box<dyn ItemHandle>> {
4685        let state = self.follower_states.get(&CollaboratorId::Agent)?;
4686        let active_view_id = state.active_view_id?;
4687        Some(
4688            state
4689                .items_by_leader_view_id
4690                .get(&active_view_id)?
4691                .view
4692                .boxed_clone(),
4693        )
4694    }
4695
4696    fn active_item_for_peer(
4697        &self,
4698        peer_id: PeerId,
4699        window: &mut Window,
4700        cx: &mut Context<Self>,
4701    ) -> Option<(Option<PanelId>, Box<dyn ItemHandle>)> {
4702        let call = self.active_call()?;
4703        let room = call.read(cx).room()?.read(cx);
4704        let participant = room.remote_participant_for_peer_id(peer_id)?;
4705        let leader_in_this_app;
4706        let leader_in_this_project;
4707        match participant.location {
4708            call::ParticipantLocation::SharedProject { project_id } => {
4709                leader_in_this_app = true;
4710                leader_in_this_project = Some(project_id) == self.project.read(cx).remote_id();
4711            }
4712            call::ParticipantLocation::UnsharedProject => {
4713                leader_in_this_app = true;
4714                leader_in_this_project = false;
4715            }
4716            call::ParticipantLocation::External => {
4717                leader_in_this_app = false;
4718                leader_in_this_project = false;
4719            }
4720        };
4721        let state = self.follower_states.get(&peer_id.into())?;
4722        let mut item_to_activate = None;
4723        if let (Some(active_view_id), true) = (state.active_view_id, leader_in_this_app) {
4724            if let Some(item) = state.items_by_leader_view_id.get(&active_view_id) {
4725                if leader_in_this_project || !item.view.is_project_item(window, cx) {
4726                    item_to_activate = Some((item.location, item.view.boxed_clone()));
4727                }
4728            }
4729        } else if let Some(shared_screen) =
4730            self.shared_screen_for_peer(peer_id, &state.center_pane, window, cx)
4731        {
4732            item_to_activate = Some((None, Box::new(shared_screen)));
4733        }
4734        item_to_activate
4735    }
4736
4737    fn shared_screen_for_peer(
4738        &self,
4739        peer_id: PeerId,
4740        pane: &Entity<Pane>,
4741        window: &mut Window,
4742        cx: &mut App,
4743    ) -> Option<Entity<SharedScreen>> {
4744        let call = self.active_call()?;
4745        let room = call.read(cx).room()?.clone();
4746        let participant = room.read(cx).remote_participant_for_peer_id(peer_id)?;
4747        let track = participant.video_tracks.values().next()?.clone();
4748        let user = participant.user.clone();
4749
4750        for item in pane.read(cx).items_of_type::<SharedScreen>() {
4751            if item.read(cx).peer_id == peer_id {
4752                return Some(item);
4753            }
4754        }
4755
4756        Some(cx.new(|cx| SharedScreen::new(track, peer_id, user.clone(), room.clone(), window, cx)))
4757    }
4758
4759    pub fn on_window_activation_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4760        if window.is_window_active() {
4761            self.update_active_view_for_followers(window, cx);
4762
4763            if let Some(database_id) = self.database_id {
4764                cx.background_spawn(persistence::DB.update_timestamp(database_id))
4765                    .detach();
4766            }
4767        } else {
4768            for pane in &self.panes {
4769                pane.update(cx, |pane, cx| {
4770                    if let Some(item) = pane.active_item() {
4771                        item.workspace_deactivated(window, cx);
4772                    }
4773                    for item in pane.items() {
4774                        if matches!(
4775                            item.workspace_settings(cx).autosave,
4776                            AutosaveSetting::OnWindowChange | AutosaveSetting::OnFocusChange
4777                        ) {
4778                            Pane::autosave_item(item.as_ref(), self.project.clone(), window, cx)
4779                                .detach_and_log_err(cx);
4780                        }
4781                    }
4782                });
4783            }
4784        }
4785    }
4786
4787    pub fn active_call(&self) -> Option<&Entity<ActiveCall>> {
4788        self.active_call.as_ref().map(|(call, _)| call)
4789    }
4790
4791    fn on_active_call_event(
4792        &mut self,
4793        _: &Entity<ActiveCall>,
4794        event: &call::room::Event,
4795        window: &mut Window,
4796        cx: &mut Context<Self>,
4797    ) {
4798        match event {
4799            call::room::Event::ParticipantLocationChanged { participant_id }
4800            | call::room::Event::RemoteVideoTracksChanged { participant_id } => {
4801                self.leader_updated(participant_id, window, cx);
4802            }
4803            _ => {}
4804        }
4805    }
4806
4807    pub fn database_id(&self) -> Option<WorkspaceId> {
4808        self.database_id
4809    }
4810
4811    pub fn session_id(&self) -> Option<String> {
4812        self.session_id.clone()
4813    }
4814
4815    fn local_paths(&self, cx: &App) -> Option<Vec<Arc<Path>>> {
4816        let project = self.project().read(cx);
4817
4818        if project.is_local() {
4819            Some(
4820                project
4821                    .visible_worktrees(cx)
4822                    .map(|worktree| worktree.read(cx).abs_path())
4823                    .collect::<Vec<_>>(),
4824            )
4825        } else {
4826            None
4827        }
4828    }
4829
4830    fn remove_panes(&mut self, member: Member, window: &mut Window, cx: &mut Context<Workspace>) {
4831        match member {
4832            Member::Axis(PaneAxis { members, .. }) => {
4833                for child in members.iter() {
4834                    self.remove_panes(child.clone(), window, cx)
4835                }
4836            }
4837            Member::Pane(pane) => {
4838                self.force_remove_pane(&pane, &None, window, cx);
4839            }
4840        }
4841    }
4842
4843    fn remove_from_session(&mut self, window: &mut Window, cx: &mut App) -> Task<()> {
4844        self.session_id.take();
4845        self.serialize_workspace_internal(window, cx)
4846    }
4847
4848    fn force_remove_pane(
4849        &mut self,
4850        pane: &Entity<Pane>,
4851        focus_on: &Option<Entity<Pane>>,
4852        window: &mut Window,
4853        cx: &mut Context<Workspace>,
4854    ) {
4855        self.panes.retain(|p| p != pane);
4856        if let Some(focus_on) = focus_on {
4857            focus_on.update(cx, |pane, cx| window.focus(&pane.focus_handle(cx)));
4858        } else {
4859            if self.active_pane() == pane {
4860                self.panes
4861                    .last()
4862                    .unwrap()
4863                    .update(cx, |pane, cx| window.focus(&pane.focus_handle(cx)));
4864            }
4865        }
4866        if self.last_active_center_pane == Some(pane.downgrade()) {
4867            self.last_active_center_pane = None;
4868        }
4869        cx.notify();
4870    }
4871
4872    fn serialize_workspace(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4873        if self._schedule_serialize.is_none() {
4874            self._schedule_serialize = Some(cx.spawn_in(window, async move |this, cx| {
4875                cx.background_executor()
4876                    .timer(Duration::from_millis(100))
4877                    .await;
4878                this.update_in(cx, |this, window, cx| {
4879                    this.serialize_workspace_internal(window, cx).detach();
4880                    this._schedule_serialize.take();
4881                })
4882                .log_err();
4883            }));
4884        }
4885    }
4886
4887    fn serialize_workspace_internal(&self, window: &mut Window, cx: &mut App) -> Task<()> {
4888        let Some(database_id) = self.database_id() else {
4889            return Task::ready(());
4890        };
4891
4892        fn serialize_pane_handle(
4893            pane_handle: &Entity<Pane>,
4894            window: &mut Window,
4895            cx: &mut App,
4896        ) -> SerializedPane {
4897            let (items, active, pinned_count) = {
4898                let pane = pane_handle.read(cx);
4899                let active_item_id = pane.active_item().map(|item| item.item_id());
4900                (
4901                    pane.items()
4902                        .filter_map(|handle| {
4903                            let handle = handle.to_serializable_item_handle(cx)?;
4904
4905                            Some(SerializedItem {
4906                                kind: Arc::from(handle.serialized_item_kind()),
4907                                item_id: handle.item_id().as_u64(),
4908                                active: Some(handle.item_id()) == active_item_id,
4909                                preview: pane.is_active_preview_item(handle.item_id()),
4910                            })
4911                        })
4912                        .collect::<Vec<_>>(),
4913                    pane.has_focus(window, cx),
4914                    pane.pinned_count(),
4915                )
4916            };
4917
4918            SerializedPane::new(items, active, pinned_count)
4919        }
4920
4921        fn build_serialized_pane_group(
4922            pane_group: &Member,
4923            window: &mut Window,
4924            cx: &mut App,
4925        ) -> SerializedPaneGroup {
4926            match pane_group {
4927                Member::Axis(PaneAxis {
4928                    axis,
4929                    members,
4930                    flexes,
4931                    bounding_boxes: _,
4932                }) => SerializedPaneGroup::Group {
4933                    axis: SerializedAxis(*axis),
4934                    children: members
4935                        .iter()
4936                        .map(|member| build_serialized_pane_group(member, window, cx))
4937                        .collect::<Vec<_>>(),
4938                    flexes: Some(flexes.lock().clone()),
4939                },
4940                Member::Pane(pane_handle) => {
4941                    SerializedPaneGroup::Pane(serialize_pane_handle(pane_handle, window, cx))
4942                }
4943            }
4944        }
4945
4946        fn build_serialized_docks(
4947            this: &Workspace,
4948            window: &mut Window,
4949            cx: &mut App,
4950        ) -> DockStructure {
4951            let left_dock = this.left_dock.read(cx);
4952            let left_visible = left_dock.is_open();
4953            let left_active_panel = left_dock
4954                .active_panel()
4955                .map(|panel| panel.persistent_name().to_string());
4956            let left_dock_zoom = left_dock
4957                .active_panel()
4958                .map(|panel| panel.is_zoomed(window, cx))
4959                .unwrap_or(false);
4960
4961            let right_dock = this.right_dock.read(cx);
4962            let right_visible = right_dock.is_open();
4963            let right_active_panel = right_dock
4964                .active_panel()
4965                .map(|panel| panel.persistent_name().to_string());
4966            let right_dock_zoom = right_dock
4967                .active_panel()
4968                .map(|panel| panel.is_zoomed(window, cx))
4969                .unwrap_or(false);
4970
4971            let bottom_dock = this.bottom_dock.read(cx);
4972            let bottom_visible = bottom_dock.is_open();
4973            let bottom_active_panel = bottom_dock
4974                .active_panel()
4975                .map(|panel| panel.persistent_name().to_string());
4976            let bottom_dock_zoom = bottom_dock
4977                .active_panel()
4978                .map(|panel| panel.is_zoomed(window, cx))
4979                .unwrap_or(false);
4980
4981            DockStructure {
4982                left: DockData {
4983                    visible: left_visible,
4984                    active_panel: left_active_panel,
4985                    zoom: left_dock_zoom,
4986                },
4987                right: DockData {
4988                    visible: right_visible,
4989                    active_panel: right_active_panel,
4990                    zoom: right_dock_zoom,
4991                },
4992                bottom: DockData {
4993                    visible: bottom_visible,
4994                    active_panel: bottom_active_panel,
4995                    zoom: bottom_dock_zoom,
4996                },
4997            }
4998        }
4999
5000        if let Some(location) = self.serialize_workspace_location(cx) {
5001            let breakpoints = self.project.update(cx, |project, cx| {
5002                project.breakpoint_store().read(cx).all_breakpoints(cx)
5003            });
5004
5005            let center_group = build_serialized_pane_group(&self.center.root, window, cx);
5006            let docks = build_serialized_docks(self, window, cx);
5007            let window_bounds = Some(SerializedWindowBounds(window.window_bounds()));
5008            let serialized_workspace = SerializedWorkspace {
5009                id: database_id,
5010                location,
5011                center_group,
5012                window_bounds,
5013                display: Default::default(),
5014                docks,
5015                centered_layout: self.centered_layout,
5016                session_id: self.session_id.clone(),
5017                breakpoints,
5018                window_id: Some(window.window_handle().window_id().as_u64()),
5019            };
5020
5021            return window.spawn(cx, async move |_| {
5022                persistence::DB.save_workspace(serialized_workspace).await;
5023            });
5024        }
5025        Task::ready(())
5026    }
5027
5028    fn serialize_workspace_location(&self, cx: &App) -> Option<SerializedWorkspaceLocation> {
5029        if let Some(ssh_project) = &self.serialized_ssh_project {
5030            Some(SerializedWorkspaceLocation::Ssh(ssh_project.clone()))
5031        } else if let Some(local_paths) = self.local_paths(cx) {
5032            if !local_paths.is_empty() {
5033                Some(SerializedWorkspaceLocation::from_local_paths(local_paths))
5034            } else {
5035                None
5036            }
5037        } else {
5038            None
5039        }
5040    }
5041
5042    fn update_history(&self, cx: &mut App) {
5043        let Some(id) = self.database_id() else {
5044            return;
5045        };
5046        let Some(location) = self.serialize_workspace_location(cx) else {
5047            return;
5048        };
5049        if let Some(manager) = HistoryManager::global(cx) {
5050            manager.update(cx, |this, cx| {
5051                this.update_history(id, HistoryManagerEntry::new(id, &location), cx);
5052            });
5053        }
5054    }
5055
5056    async fn serialize_items(
5057        this: &WeakEntity<Self>,
5058        items_rx: UnboundedReceiver<Box<dyn SerializableItemHandle>>,
5059        cx: &mut AsyncWindowContext,
5060    ) -> Result<()> {
5061        const CHUNK_SIZE: usize = 200;
5062
5063        let mut serializable_items = items_rx.ready_chunks(CHUNK_SIZE);
5064
5065        while let Some(items_received) = serializable_items.next().await {
5066            let unique_items =
5067                items_received
5068                    .into_iter()
5069                    .fold(HashMap::default(), |mut acc, item| {
5070                        acc.entry(item.item_id()).or_insert(item);
5071                        acc
5072                    });
5073
5074            // We use into_iter() here so that the references to the items are moved into
5075            // the tasks and not kept alive while we're sleeping.
5076            for (_, item) in unique_items.into_iter() {
5077                if let Ok(Some(task)) = this.update_in(cx, |workspace, window, cx| {
5078                    item.serialize(workspace, false, window, cx)
5079                }) {
5080                    cx.background_spawn(async move { task.await.log_err() })
5081                        .detach();
5082                }
5083            }
5084
5085            cx.background_executor()
5086                .timer(SERIALIZATION_THROTTLE_TIME)
5087                .await;
5088        }
5089
5090        Ok(())
5091    }
5092
5093    pub(crate) fn enqueue_item_serialization(
5094        &mut self,
5095        item: Box<dyn SerializableItemHandle>,
5096    ) -> Result<()> {
5097        self.serializable_items_tx
5098            .unbounded_send(item)
5099            .map_err(|err| anyhow!("failed to send serializable item over channel: {}", err))
5100    }
5101
5102    pub(crate) fn load_workspace(
5103        serialized_workspace: SerializedWorkspace,
5104        paths_to_open: Vec<Option<ProjectPath>>,
5105        window: &mut Window,
5106        cx: &mut Context<Workspace>,
5107    ) -> Task<Result<Vec<Option<Box<dyn ItemHandle>>>>> {
5108        cx.spawn_in(window, async move |workspace, cx| {
5109            let project = workspace.update(cx, |workspace, _| workspace.project().clone())?;
5110
5111            let mut center_group = None;
5112            let mut center_items = None;
5113
5114            // Traverse the splits tree and add to things
5115            if let Some((group, active_pane, items)) = serialized_workspace
5116                .center_group
5117                .deserialize(&project, serialized_workspace.id, workspace.clone(), cx)
5118                .await
5119            {
5120                center_items = Some(items);
5121                center_group = Some((group, active_pane))
5122            }
5123
5124            let mut items_by_project_path = HashMap::default();
5125            let mut item_ids_by_kind = HashMap::default();
5126            let mut all_deserialized_items = Vec::default();
5127            cx.update(|_, cx| {
5128                for item in center_items.unwrap_or_default().into_iter().flatten() {
5129                    if let Some(serializable_item_handle) = item.to_serializable_item_handle(cx) {
5130                        item_ids_by_kind
5131                            .entry(serializable_item_handle.serialized_item_kind())
5132                            .or_insert(Vec::new())
5133                            .push(item.item_id().as_u64() as ItemId);
5134                    }
5135
5136                    if let Some(project_path) = item.project_path(cx) {
5137                        items_by_project_path.insert(project_path, item.clone());
5138                    }
5139                    all_deserialized_items.push(item);
5140                }
5141            })?;
5142
5143            let opened_items = paths_to_open
5144                .into_iter()
5145                .map(|path_to_open| {
5146                    path_to_open
5147                        .and_then(|path_to_open| items_by_project_path.remove(&path_to_open))
5148                })
5149                .collect::<Vec<_>>();
5150
5151            // Remove old panes from workspace panes list
5152            workspace.update_in(cx, |workspace, window, cx| {
5153                if let Some((center_group, active_pane)) = center_group {
5154                    workspace.remove_panes(workspace.center.root.clone(), window, cx);
5155
5156                    // Swap workspace center group
5157                    workspace.center = PaneGroup::with_root(center_group);
5158                    if let Some(active_pane) = active_pane {
5159                        workspace.set_active_pane(&active_pane, window, cx);
5160                        cx.focus_self(window);
5161                    } else {
5162                        workspace.set_active_pane(&workspace.center.first_pane(), window, cx);
5163                    }
5164                }
5165
5166                let docks = serialized_workspace.docks;
5167
5168                for (dock, serialized_dock) in [
5169                    (&mut workspace.right_dock, docks.right),
5170                    (&mut workspace.left_dock, docks.left),
5171                    (&mut workspace.bottom_dock, docks.bottom),
5172                ]
5173                .iter_mut()
5174                {
5175                    dock.update(cx, |dock, cx| {
5176                        dock.serialized_dock = Some(serialized_dock.clone());
5177                        dock.restore_state(window, cx);
5178                    });
5179                }
5180
5181                cx.notify();
5182            })?;
5183
5184            let _ = project
5185                .update(cx, |project, cx| {
5186                    project
5187                        .breakpoint_store()
5188                        .update(cx, |breakpoint_store, cx| {
5189                            breakpoint_store
5190                                .with_serialized_breakpoints(serialized_workspace.breakpoints, cx)
5191                        })
5192                })?
5193                .await;
5194
5195            // Clean up all the items that have _not_ been loaded. Our ItemIds aren't stable. That means
5196            // after loading the items, we might have different items and in order to avoid
5197            // the database filling up, we delete items that haven't been loaded now.
5198            //
5199            // The items that have been loaded, have been saved after they've been added to the workspace.
5200            let clean_up_tasks = workspace.update_in(cx, |_, window, cx| {
5201                item_ids_by_kind
5202                    .into_iter()
5203                    .map(|(item_kind, loaded_items)| {
5204                        SerializableItemRegistry::cleanup(
5205                            item_kind,
5206                            serialized_workspace.id,
5207                            loaded_items,
5208                            window,
5209                            cx,
5210                        )
5211                        .log_err()
5212                    })
5213                    .collect::<Vec<_>>()
5214            })?;
5215
5216            futures::future::join_all(clean_up_tasks).await;
5217
5218            workspace
5219                .update_in(cx, |workspace, window, cx| {
5220                    // Serialize ourself to make sure our timestamps and any pane / item changes are replicated
5221                    workspace.serialize_workspace_internal(window, cx).detach();
5222
5223                    // Ensure that we mark the window as edited if we did load dirty items
5224                    workspace.update_window_edited(window, cx);
5225                })
5226                .ok();
5227
5228            Ok(opened_items)
5229        })
5230    }
5231
5232    fn actions(&self, div: Div, window: &mut Window, cx: &mut Context<Self>) -> Div {
5233        self.add_workspace_actions_listeners(div, window, cx)
5234            .on_action(cx.listener(Self::close_inactive_items_and_panes))
5235            .on_action(cx.listener(Self::close_all_items_and_panes))
5236            .on_action(cx.listener(Self::save_all))
5237            .on_action(cx.listener(Self::send_keystrokes))
5238            .on_action(cx.listener(Self::add_folder_to_project))
5239            .on_action(cx.listener(Self::follow_next_collaborator))
5240            .on_action(cx.listener(Self::close_window))
5241            .on_action(cx.listener(Self::activate_pane_at_index))
5242            .on_action(cx.listener(Self::move_item_to_pane_at_index))
5243            .on_action(cx.listener(Self::move_focused_panel_to_next_position))
5244            .on_action(cx.listener(|workspace, _: &Unfollow, window, cx| {
5245                let pane = workspace.active_pane().clone();
5246                workspace.unfollow_in_pane(&pane, window, cx);
5247            }))
5248            .on_action(cx.listener(|workspace, action: &Save, window, cx| {
5249                workspace
5250                    .save_active_item(action.save_intent.unwrap_or(SaveIntent::Save), window, cx)
5251                    .detach_and_prompt_err("Failed to save", window, cx, |_, _, _| None);
5252            }))
5253            .on_action(cx.listener(|workspace, _: &SaveWithoutFormat, window, cx| {
5254                workspace
5255                    .save_active_item(SaveIntent::SaveWithoutFormat, window, cx)
5256                    .detach_and_prompt_err("Failed to save", window, cx, |_, _, _| None);
5257            }))
5258            .on_action(cx.listener(|workspace, _: &SaveAs, window, cx| {
5259                workspace
5260                    .save_active_item(SaveIntent::SaveAs, window, cx)
5261                    .detach_and_prompt_err("Failed to save", window, cx, |_, _, _| None);
5262            }))
5263            .on_action(
5264                cx.listener(|workspace, _: &ActivatePreviousPane, window, cx| {
5265                    workspace.activate_previous_pane(window, cx)
5266                }),
5267            )
5268            .on_action(cx.listener(|workspace, _: &ActivateNextPane, window, cx| {
5269                workspace.activate_next_pane(window, cx)
5270            }))
5271            .on_action(
5272                cx.listener(|workspace, _: &ActivateNextWindow, _window, cx| {
5273                    workspace.activate_next_window(cx)
5274                }),
5275            )
5276            .on_action(
5277                cx.listener(|workspace, _: &ActivatePreviousWindow, _window, cx| {
5278                    workspace.activate_previous_window(cx)
5279                }),
5280            )
5281            .on_action(cx.listener(|workspace, _: &ActivatePaneLeft, window, cx| {
5282                workspace.activate_pane_in_direction(SplitDirection::Left, window, cx)
5283            }))
5284            .on_action(cx.listener(|workspace, _: &ActivatePaneRight, window, cx| {
5285                workspace.activate_pane_in_direction(SplitDirection::Right, window, cx)
5286            }))
5287            .on_action(cx.listener(|workspace, _: &ActivatePaneUp, window, cx| {
5288                workspace.activate_pane_in_direction(SplitDirection::Up, window, cx)
5289            }))
5290            .on_action(cx.listener(|workspace, _: &ActivatePaneDown, window, cx| {
5291                workspace.activate_pane_in_direction(SplitDirection::Down, window, cx)
5292            }))
5293            .on_action(cx.listener(|workspace, _: &ActivateNextPane, window, cx| {
5294                workspace.activate_next_pane(window, cx)
5295            }))
5296            .on_action(cx.listener(
5297                |workspace, action: &MoveItemToPaneInDirection, window, cx| {
5298                    workspace.move_item_to_pane_in_direction(action, window, cx)
5299                },
5300            ))
5301            .on_action(cx.listener(|workspace, _: &SwapPaneLeft, _, cx| {
5302                workspace.swap_pane_in_direction(SplitDirection::Left, cx)
5303            }))
5304            .on_action(cx.listener(|workspace, _: &SwapPaneRight, _, cx| {
5305                workspace.swap_pane_in_direction(SplitDirection::Right, cx)
5306            }))
5307            .on_action(cx.listener(|workspace, _: &SwapPaneUp, _, cx| {
5308                workspace.swap_pane_in_direction(SplitDirection::Up, cx)
5309            }))
5310            .on_action(cx.listener(|workspace, _: &SwapPaneDown, _, cx| {
5311                workspace.swap_pane_in_direction(SplitDirection::Down, cx)
5312            }))
5313            .on_action(cx.listener(|this, _: &ToggleLeftDock, window, cx| {
5314                this.toggle_dock(DockPosition::Left, window, cx);
5315            }))
5316            .on_action(cx.listener(
5317                |workspace: &mut Workspace, _: &ToggleRightDock, window, cx| {
5318                    workspace.toggle_dock(DockPosition::Right, window, cx);
5319                },
5320            ))
5321            .on_action(cx.listener(
5322                |workspace: &mut Workspace, _: &ToggleBottomDock, window, cx| {
5323                    workspace.toggle_dock(DockPosition::Bottom, window, cx);
5324                },
5325            ))
5326            .on_action(cx.listener(
5327                |workspace: &mut Workspace, _: &CloseActiveDock, window, cx| {
5328                    workspace.close_active_dock(window, cx);
5329                },
5330            ))
5331            .on_action(
5332                cx.listener(|workspace: &mut Workspace, _: &CloseAllDocks, window, cx| {
5333                    workspace.close_all_docks(window, cx);
5334                }),
5335            )
5336            .on_action(cx.listener(
5337                |workspace: &mut Workspace, _: &ClearAllNotifications, _, cx| {
5338                    workspace.clear_all_notifications(cx);
5339                },
5340            ))
5341            .on_action(cx.listener(
5342                |workspace: &mut Workspace, _: &SuppressNotification, _, cx| {
5343                    if let Some((notification_id, _)) = workspace.notifications.pop() {
5344                        workspace.suppress_notification(&notification_id, cx);
5345                    }
5346                },
5347            ))
5348            .on_action(cx.listener(
5349                |workspace: &mut Workspace, _: &ReopenClosedItem, window, cx| {
5350                    workspace.reopen_closed_item(window, cx).detach();
5351                },
5352            ))
5353            .on_action(cx.listener(Workspace::toggle_centered_layout))
5354            .on_action(cx.listener(Workspace::cancel))
5355    }
5356
5357    #[cfg(any(test, feature = "test-support"))]
5358    pub fn test_new(project: Entity<Project>, window: &mut Window, cx: &mut Context<Self>) -> Self {
5359        use node_runtime::NodeRuntime;
5360        use session::Session;
5361
5362        let client = project.read(cx).client();
5363        let user_store = project.read(cx).user_store();
5364
5365        let workspace_store = cx.new(|cx| WorkspaceStore::new(client.clone(), cx));
5366        let session = cx.new(|cx| AppSession::new(Session::test(), cx));
5367        window.activate_window();
5368        let app_state = Arc::new(AppState {
5369            languages: project.read(cx).languages().clone(),
5370            workspace_store,
5371            client,
5372            user_store,
5373            fs: project.read(cx).fs().clone(),
5374            build_window_options: |_, _| Default::default(),
5375            node_runtime: NodeRuntime::unavailable(),
5376            session,
5377        });
5378        let workspace = Self::new(Default::default(), project, app_state, window, cx);
5379        workspace
5380            .active_pane
5381            .update(cx, |pane, cx| window.focus(&pane.focus_handle(cx)));
5382        workspace
5383    }
5384
5385    pub fn register_action<A: Action>(
5386        &mut self,
5387        callback: impl Fn(&mut Self, &A, &mut Window, &mut Context<Self>) + 'static,
5388    ) -> &mut Self {
5389        let callback = Arc::new(callback);
5390
5391        self.workspace_actions.push(Box::new(move |div, _, cx| {
5392            let callback = callback.clone();
5393            div.on_action(cx.listener(move |workspace, event, window, cx| {
5394                (callback)(workspace, event, window, cx)
5395            }))
5396        }));
5397        self
5398    }
5399
5400    fn add_workspace_actions_listeners(
5401        &self,
5402        mut div: Div,
5403        window: &mut Window,
5404        cx: &mut Context<Self>,
5405    ) -> Div {
5406        for action in self.workspace_actions.iter() {
5407            div = (action)(div, window, cx)
5408        }
5409        div
5410    }
5411
5412    pub fn has_active_modal(&self, _: &mut Window, cx: &mut App) -> bool {
5413        self.modal_layer.read(cx).has_active_modal()
5414    }
5415
5416    pub fn active_modal<V: ManagedView + 'static>(&self, cx: &App) -> Option<Entity<V>> {
5417        self.modal_layer.read(cx).active_modal()
5418    }
5419
5420    pub fn toggle_modal<V: ModalView, B>(&mut self, window: &mut Window, cx: &mut App, build: B)
5421    where
5422        B: FnOnce(&mut Window, &mut Context<V>) -> V,
5423    {
5424        self.modal_layer.update(cx, |modal_layer, cx| {
5425            modal_layer.toggle_modal(window, cx, build)
5426        })
5427    }
5428
5429    pub fn toggle_status_toast<V: ToastView>(&mut self, entity: Entity<V>, cx: &mut App) {
5430        self.toast_layer
5431            .update(cx, |toast_layer, cx| toast_layer.toggle_toast(cx, entity))
5432    }
5433
5434    pub fn toggle_centered_layout(
5435        &mut self,
5436        _: &ToggleCenteredLayout,
5437        _: &mut Window,
5438        cx: &mut Context<Self>,
5439    ) {
5440        self.centered_layout = !self.centered_layout;
5441        if let Some(database_id) = self.database_id() {
5442            cx.background_spawn(DB.set_centered_layout(database_id, self.centered_layout))
5443                .detach_and_log_err(cx);
5444        }
5445        cx.notify();
5446    }
5447
5448    fn adjust_padding(padding: Option<f32>) -> f32 {
5449        padding
5450            .unwrap_or(Self::DEFAULT_PADDING)
5451            .clamp(0.0, Self::MAX_PADDING)
5452    }
5453
5454    fn render_dock(
5455        &self,
5456        position: DockPosition,
5457        dock: &Entity<Dock>,
5458        window: &mut Window,
5459        cx: &mut App,
5460    ) -> Option<Div> {
5461        if self.zoomed_position == Some(position) {
5462            return None;
5463        }
5464
5465        let leader_border = dock.read(cx).active_panel().and_then(|panel| {
5466            let pane = panel.pane(cx)?;
5467            let follower_states = &self.follower_states;
5468            leader_border_for_pane(follower_states, &pane, window, cx)
5469        });
5470
5471        Some(
5472            div()
5473                .flex()
5474                .flex_none()
5475                .overflow_hidden()
5476                .child(dock.clone())
5477                .children(leader_border),
5478        )
5479    }
5480
5481    pub fn for_window(window: &mut Window, _: &mut App) -> Option<Entity<Workspace>> {
5482        window.root().flatten()
5483    }
5484
5485    pub fn zoomed_item(&self) -> Option<&AnyWeakView> {
5486        self.zoomed.as_ref()
5487    }
5488
5489    pub fn activate_next_window(&mut self, cx: &mut Context<Self>) {
5490        let Some(current_window_id) = cx.active_window().map(|a| a.window_id()) else {
5491            return;
5492        };
5493        let windows = cx.windows();
5494        let Some(next_window) = windows
5495            .iter()
5496            .cycle()
5497            .skip_while(|window| window.window_id() != current_window_id)
5498            .nth(1)
5499        else {
5500            return;
5501        };
5502        next_window
5503            .update(cx, |_, window, _| window.activate_window())
5504            .ok();
5505    }
5506
5507    pub fn activate_previous_window(&mut self, cx: &mut Context<Self>) {
5508        let Some(current_window_id) = cx.active_window().map(|a| a.window_id()) else {
5509            return;
5510        };
5511        let windows = cx.windows();
5512        let Some(prev_window) = windows
5513            .iter()
5514            .rev()
5515            .cycle()
5516            .skip_while(|window| window.window_id() != current_window_id)
5517            .nth(1)
5518        else {
5519            return;
5520        };
5521        prev_window
5522            .update(cx, |_, window, _| window.activate_window())
5523            .ok();
5524    }
5525
5526    pub fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
5527        if cx.stop_active_drag(window) {
5528            return;
5529        } else if let Some((notification_id, _)) = self.notifications.pop() {
5530            dismiss_app_notification(&notification_id, cx);
5531        } else {
5532            cx.emit(Event::ClearActivityIndicator);
5533            cx.propagate();
5534        }
5535    }
5536}
5537
5538fn leader_border_for_pane(
5539    follower_states: &HashMap<CollaboratorId, FollowerState>,
5540    pane: &Entity<Pane>,
5541    _: &Window,
5542    cx: &App,
5543) -> Option<Div> {
5544    let (leader_id, _follower_state) = follower_states.iter().find_map(|(leader_id, state)| {
5545        if state.pane() == pane {
5546            Some((*leader_id, state))
5547        } else {
5548            None
5549        }
5550    })?;
5551
5552    let mut leader_color = match leader_id {
5553        CollaboratorId::PeerId(leader_peer_id) => {
5554            let room = ActiveCall::try_global(cx)?.read(cx).room()?.read(cx);
5555            let leader = room.remote_participant_for_peer_id(leader_peer_id)?;
5556
5557            cx.theme()
5558                .players()
5559                .color_for_participant(leader.participant_index.0)
5560                .cursor
5561        }
5562        CollaboratorId::Agent => cx.theme().players().agent().cursor,
5563    };
5564    leader_color.fade_out(0.3);
5565    Some(
5566        div()
5567            .absolute()
5568            .size_full()
5569            .left_0()
5570            .top_0()
5571            .border_2()
5572            .border_color(leader_color),
5573    )
5574}
5575
5576fn window_bounds_env_override() -> Option<Bounds<Pixels>> {
5577    ZED_WINDOW_POSITION
5578        .zip(*ZED_WINDOW_SIZE)
5579        .map(|(position, size)| Bounds {
5580            origin: position,
5581            size,
5582        })
5583}
5584
5585fn open_items(
5586    serialized_workspace: Option<SerializedWorkspace>,
5587    mut project_paths_to_open: Vec<(PathBuf, Option<ProjectPath>)>,
5588    window: &mut Window,
5589    cx: &mut Context<Workspace>,
5590) -> impl 'static + Future<Output = Result<Vec<Option<Result<Box<dyn ItemHandle>>>>>> + use<> {
5591    let restored_items = serialized_workspace.map(|serialized_workspace| {
5592        Workspace::load_workspace(
5593            serialized_workspace,
5594            project_paths_to_open
5595                .iter()
5596                .map(|(_, project_path)| project_path)
5597                .cloned()
5598                .collect(),
5599            window,
5600            cx,
5601        )
5602    });
5603
5604    cx.spawn_in(window, async move |workspace, cx| {
5605        let mut opened_items = Vec::with_capacity(project_paths_to_open.len());
5606
5607        if let Some(restored_items) = restored_items {
5608            let restored_items = restored_items.await?;
5609
5610            let restored_project_paths = restored_items
5611                .iter()
5612                .filter_map(|item| {
5613                    cx.update(|_, cx| item.as_ref()?.project_path(cx))
5614                        .ok()
5615                        .flatten()
5616                })
5617                .collect::<HashSet<_>>();
5618
5619            for restored_item in restored_items {
5620                opened_items.push(restored_item.map(Ok));
5621            }
5622
5623            project_paths_to_open
5624                .iter_mut()
5625                .for_each(|(_, project_path)| {
5626                    if let Some(project_path_to_open) = project_path {
5627                        if restored_project_paths.contains(project_path_to_open) {
5628                            *project_path = None;
5629                        }
5630                    }
5631                });
5632        } else {
5633            for _ in 0..project_paths_to_open.len() {
5634                opened_items.push(None);
5635            }
5636        }
5637        assert!(opened_items.len() == project_paths_to_open.len());
5638
5639        let tasks =
5640            project_paths_to_open
5641                .into_iter()
5642                .enumerate()
5643                .map(|(ix, (abs_path, project_path))| {
5644                    let workspace = workspace.clone();
5645                    cx.spawn(async move |cx| {
5646                        let file_project_path = project_path?;
5647                        let abs_path_task = workspace.update(cx, |workspace, cx| {
5648                            workspace.project().update(cx, |project, cx| {
5649                                project.resolve_abs_path(abs_path.to_string_lossy().as_ref(), cx)
5650                            })
5651                        });
5652
5653                        // We only want to open file paths here. If one of the items
5654                        // here is a directory, it was already opened further above
5655                        // with a `find_or_create_worktree`.
5656                        if let Ok(task) = abs_path_task {
5657                            if task.await.map_or(true, |p| p.is_file()) {
5658                                return Some((
5659                                    ix,
5660                                    workspace
5661                                        .update_in(cx, |workspace, window, cx| {
5662                                            workspace.open_path(
5663                                                file_project_path,
5664                                                None,
5665                                                true,
5666                                                window,
5667                                                cx,
5668                                            )
5669                                        })
5670                                        .log_err()?
5671                                        .await,
5672                                ));
5673                            }
5674                        }
5675                        None
5676                    })
5677                });
5678
5679        let tasks = tasks.collect::<Vec<_>>();
5680
5681        let tasks = futures::future::join_all(tasks);
5682        for (ix, path_open_result) in tasks.await.into_iter().flatten() {
5683            opened_items[ix] = Some(path_open_result);
5684        }
5685
5686        Ok(opened_items)
5687    })
5688}
5689
5690enum ActivateInDirectionTarget {
5691    Pane(Entity<Pane>),
5692    Dock(Entity<Dock>),
5693}
5694
5695fn notify_if_database_failed(workspace: WindowHandle<Workspace>, cx: &mut AsyncApp) {
5696    workspace
5697        .update(cx, |workspace, _, cx| {
5698            if (*db::ALL_FILE_DB_FAILED).load(std::sync::atomic::Ordering::Acquire) {
5699                struct DatabaseFailedNotification;
5700
5701                workspace.show_notification(
5702                    NotificationId::unique::<DatabaseFailedNotification>(),
5703                    cx,
5704                    |cx| {
5705                        cx.new(|cx| {
5706                            MessageNotification::new("Failed to load the database file.", cx)
5707                                .primary_message("File an Issue")
5708                                .primary_icon(IconName::Plus)
5709                                .primary_on_click(|window, cx| {
5710                                    window.dispatch_action(Box::new(FileBugReport), cx)
5711                                })
5712                        })
5713                    },
5714                );
5715            }
5716        })
5717        .log_err();
5718}
5719
5720impl Focusable for Workspace {
5721    fn focus_handle(&self, cx: &App) -> FocusHandle {
5722        self.active_pane.focus_handle(cx)
5723    }
5724}
5725
5726#[derive(Clone)]
5727struct DraggedDock(DockPosition);
5728
5729impl Render for DraggedDock {
5730    fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
5731        gpui::Empty
5732    }
5733}
5734
5735impl Render for Workspace {
5736    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
5737        let mut context = KeyContext::new_with_defaults();
5738        context.add("Workspace");
5739        context.set("keyboard_layout", cx.keyboard_layout().name().to_string());
5740        let centered_layout = self.centered_layout
5741            && self.center.panes().len() == 1
5742            && self.active_item(cx).is_some();
5743        let render_padding = |size| {
5744            (size > 0.0).then(|| {
5745                div()
5746                    .h_full()
5747                    .w(relative(size))
5748                    .bg(cx.theme().colors().editor_background)
5749                    .border_color(cx.theme().colors().pane_group_border)
5750            })
5751        };
5752        let paddings = if centered_layout {
5753            let settings = WorkspaceSettings::get_global(cx).centered_layout;
5754            (
5755                render_padding(Self::adjust_padding(settings.left_padding)),
5756                render_padding(Self::adjust_padding(settings.right_padding)),
5757            )
5758        } else {
5759            (None, None)
5760        };
5761        let ui_font = theme::setup_ui_font(window, cx);
5762
5763        let theme = cx.theme().clone();
5764        let colors = theme.colors();
5765        let notification_entities = self
5766            .notifications
5767            .iter()
5768            .map(|(_, notification)| notification.entity_id())
5769            .collect::<Vec<_>>();
5770
5771        client_side_decorations(
5772            self.actions(div(), window, cx)
5773                .key_context(context)
5774                .relative()
5775                .size_full()
5776                .flex()
5777                .flex_col()
5778                .font(ui_font)
5779                .gap_0()
5780                .justify_start()
5781                .items_start()
5782                .text_color(colors.text)
5783                .overflow_hidden()
5784                .children(self.titlebar_item.clone())
5785                .on_modifiers_changed(move |_, _, cx| {
5786                    for &id in &notification_entities {
5787                        cx.notify(id);
5788                    }
5789                })
5790                .child(
5791                    div()
5792                        .size_full()
5793                        .relative()
5794                        .flex_1()
5795                        .flex()
5796                        .flex_col()
5797                        .child(
5798                            div()
5799                                .id("workspace")
5800                                .bg(colors.background)
5801                                .relative()
5802                                .flex_1()
5803                                .w_full()
5804                                .flex()
5805                                .flex_col()
5806                                .overflow_hidden()
5807                                .border_t_1()
5808                                .border_b_1()
5809                                .border_color(colors.border)
5810                                .child({
5811                                    let this = cx.entity().clone();
5812                                    canvas(
5813                                        move |bounds, window, cx| {
5814                                            this.update(cx, |this, cx| {
5815                                                let bounds_changed = this.bounds != bounds;
5816                                                this.bounds = bounds;
5817
5818                                                if bounds_changed {
5819                                                    this.left_dock.update(cx, |dock, cx| {
5820                                                        dock.clamp_panel_size(
5821                                                            bounds.size.width,
5822                                                            window,
5823                                                            cx,
5824                                                        )
5825                                                    });
5826
5827                                                    this.right_dock.update(cx, |dock, cx| {
5828                                                        dock.clamp_panel_size(
5829                                                            bounds.size.width,
5830                                                            window,
5831                                                            cx,
5832                                                        )
5833                                                    });
5834
5835                                                    this.bottom_dock.update(cx, |dock, cx| {
5836                                                        dock.clamp_panel_size(
5837                                                            bounds.size.height,
5838                                                            window,
5839                                                            cx,
5840                                                        )
5841                                                    });
5842                                                }
5843                                            })
5844                                        },
5845                                        |_, _, _, _| {},
5846                                    )
5847                                    .absolute()
5848                                    .size_full()
5849                                })
5850                                .when(self.zoomed.is_none(), |this| {
5851                                    this.on_drag_move(cx.listener(
5852                                        move |workspace,
5853                                              e: &DragMoveEvent<DraggedDock>,
5854                                              window,
5855                                              cx| {
5856                                            if workspace.previous_dock_drag_coordinates
5857                                                != Some(e.event.position)
5858                                            {
5859                                                workspace.previous_dock_drag_coordinates =
5860                                                    Some(e.event.position);
5861                                                match e.drag(cx).0 {
5862                                                    DockPosition::Left => {
5863                                                        resize_left_dock(
5864                                                            e.event.position.x
5865                                                                - workspace.bounds.left(),
5866                                                            workspace,
5867                                                            window,
5868                                                            cx,
5869                                                        );
5870                                                    }
5871                                                    DockPosition::Right => {
5872                                                        resize_right_dock(
5873                                                            workspace.bounds.right()
5874                                                                - e.event.position.x,
5875                                                            workspace,
5876                                                            window,
5877                                                            cx,
5878                                                        );
5879                                                    }
5880                                                    DockPosition::Bottom => {
5881                                                        resize_bottom_dock(
5882                                                            workspace.bounds.bottom()
5883                                                                - e.event.position.y,
5884                                                            workspace,
5885                                                            window,
5886                                                            cx,
5887                                                        );
5888                                                    }
5889                                                };
5890                                                workspace.serialize_workspace(window, cx);
5891                                            }
5892                                        },
5893                                    ))
5894                                })
5895                                .child({
5896                                    match self.bottom_dock_layout {
5897                                        BottomDockLayout::Full => div()
5898                                            .flex()
5899                                            .flex_col()
5900                                            .h_full()
5901                                            .child(
5902                                                div()
5903                                                    .flex()
5904                                                    .flex_row()
5905                                                    .flex_1()
5906                                                    .overflow_hidden()
5907                                                    .children(self.render_dock(
5908                                                        DockPosition::Left,
5909                                                        &self.left_dock,
5910                                                        window,
5911                                                        cx,
5912                                                    ))
5913                                                    .child(
5914                                                        div()
5915                                                            .flex()
5916                                                            .flex_col()
5917                                                            .flex_1()
5918                                                            .overflow_hidden()
5919                                                            .child(
5920                                                                h_flex()
5921                                                                    .flex_1()
5922                                                                    .when_some(
5923                                                                        paddings.0,
5924                                                                        |this, p| {
5925                                                                            this.child(
5926                                                                                p.border_r_1(),
5927                                                                            )
5928                                                                        },
5929                                                                    )
5930                                                                    .child(self.center.render(
5931                                                                        self.zoomed.as_ref(),
5932                                                                        &PaneRenderContext {
5933                                                                            follower_states:
5934                                                                                &self.follower_states,
5935                                                                            active_call: self.active_call(),
5936                                                                            active_pane: &self.active_pane,
5937                                                                            app_state: &self.app_state,
5938                                                                            project: &self.project,
5939                                                                            workspace: &self.weak_self,
5940                                                                        },
5941                                                                        window,
5942                                                                        cx,
5943                                                                    ))
5944                                                                    .when_some(
5945                                                                        paddings.1,
5946                                                                        |this, p| {
5947                                                                            this.child(
5948                                                                                p.border_l_1(),
5949                                                                            )
5950                                                                        },
5951                                                                    ),
5952                                                            ),
5953                                                    )
5954                                                    .children(self.render_dock(
5955                                                        DockPosition::Right,
5956                                                        &self.right_dock,
5957                                                        window,
5958                                                        cx,
5959                                                    )),
5960                                            )
5961                                            .child(div().w_full().children(self.render_dock(
5962                                                DockPosition::Bottom,
5963                                                &self.bottom_dock,
5964                                                window,
5965                                                cx
5966                                            ))),
5967
5968                                        BottomDockLayout::LeftAligned => div()
5969                                            .flex()
5970                                            .flex_row()
5971                                            .h_full()
5972                                            .child(
5973                                                div()
5974                                                    .flex()
5975                                                    .flex_col()
5976                                                    .flex_1()
5977                                                    .h_full()
5978                                                    .child(
5979                                                        div()
5980                                                            .flex()
5981                                                            .flex_row()
5982                                                            .flex_1()
5983                                                            .children(self.render_dock(DockPosition::Left, &self.left_dock, window, cx))
5984                                                            .child(
5985                                                                div()
5986                                                                    .flex()
5987                                                                    .flex_col()
5988                                                                    .flex_1()
5989                                                                    .overflow_hidden()
5990                                                                    .child(
5991                                                                        h_flex()
5992                                                                            .flex_1()
5993                                                                            .when_some(paddings.0, |this, p| this.child(p.border_r_1()))
5994                                                                            .child(self.center.render(
5995                                                                                self.zoomed.as_ref(),
5996                                                                                &PaneRenderContext {
5997                                                                                    follower_states:
5998                                                                                        &self.follower_states,
5999                                                                                    active_call: self.active_call(),
6000                                                                                    active_pane: &self.active_pane,
6001                                                                                    app_state: &self.app_state,
6002                                                                                    project: &self.project,
6003                                                                                    workspace: &self.weak_self,
6004                                                                                },
6005                                                                                window,
6006                                                                                cx,
6007                                                                            ))
6008                                                                            .when_some(paddings.1, |this, p| this.child(p.border_l_1())),
6009                                                                    )
6010                                                            )
6011                                                    )
6012                                                    .child(
6013                                                        div()
6014                                                            .w_full()
6015                                                            .children(self.render_dock(DockPosition::Bottom, &self.bottom_dock, window, cx))
6016                                                    ),
6017                                            )
6018                                            .children(self.render_dock(
6019                                                DockPosition::Right,
6020                                                &self.right_dock,
6021                                                window,
6022                                                cx,
6023                                            )),
6024
6025                                        BottomDockLayout::RightAligned => div()
6026                                            .flex()
6027                                            .flex_row()
6028                                            .h_full()
6029                                            .children(self.render_dock(
6030                                                DockPosition::Left,
6031                                                &self.left_dock,
6032                                                window,
6033                                                cx,
6034                                            ))
6035                                            .child(
6036                                                div()
6037                                                    .flex()
6038                                                    .flex_col()
6039                                                    .flex_1()
6040                                                    .h_full()
6041                                                    .child(
6042                                                        div()
6043                                                            .flex()
6044                                                            .flex_row()
6045                                                            .flex_1()
6046                                                            .child(
6047                                                                div()
6048                                                                    .flex()
6049                                                                    .flex_col()
6050                                                                    .flex_1()
6051                                                                    .overflow_hidden()
6052                                                                    .child(
6053                                                                        h_flex()
6054                                                                            .flex_1()
6055                                                                            .when_some(paddings.0, |this, p| this.child(p.border_r_1()))
6056                                                                            .child(self.center.render(
6057                                                                                self.zoomed.as_ref(),
6058                                                                                &PaneRenderContext {
6059                                                                                    follower_states:
6060                                                                                        &self.follower_states,
6061                                                                                    active_call: self.active_call(),
6062                                                                                    active_pane: &self.active_pane,
6063                                                                                    app_state: &self.app_state,
6064                                                                                    project: &self.project,
6065                                                                                    workspace: &self.weak_self,
6066                                                                                },
6067                                                                                window,
6068                                                                                cx,
6069                                                                            ))
6070                                                                            .when_some(paddings.1, |this, p| this.child(p.border_l_1())),
6071                                                                    )
6072                                                            )
6073                                                            .children(self.render_dock(DockPosition::Right, &self.right_dock, window, cx))
6074                                                    )
6075                                                    .child(
6076                                                        div()
6077                                                            .w_full()
6078                                                            .children(self.render_dock(DockPosition::Bottom, &self.bottom_dock, window, cx))
6079                                                    ),
6080                                            ),
6081
6082                                        BottomDockLayout::Contained => div()
6083                                            .flex()
6084                                            .flex_row()
6085                                            .h_full()
6086                                            .children(self.render_dock(
6087                                                DockPosition::Left,
6088                                                &self.left_dock,
6089                                                window,
6090                                                cx,
6091                                            ))
6092                                            .child(
6093                                                div()
6094                                                    .flex()
6095                                                    .flex_col()
6096                                                    .flex_1()
6097                                                    .overflow_hidden()
6098                                                    .child(
6099                                                        h_flex()
6100                                                            .flex_1()
6101                                                            .when_some(paddings.0, |this, p| {
6102                                                                this.child(p.border_r_1())
6103                                                            })
6104                                                            .child(self.center.render(
6105                                                                self.zoomed.as_ref(),
6106                                                                &PaneRenderContext {
6107                                                                    follower_states:
6108                                                                        &self.follower_states,
6109                                                                    active_call: self.active_call(),
6110                                                                    active_pane: &self.active_pane,
6111                                                                    app_state: &self.app_state,
6112                                                                    project: &self.project,
6113                                                                    workspace: &self.weak_self,
6114                                                                },
6115                                                                window,
6116                                                                cx,
6117                                                            ))
6118                                                            .when_some(paddings.1, |this, p| {
6119                                                                this.child(p.border_l_1())
6120                                                            }),
6121                                                    )
6122                                                    .children(self.render_dock(
6123                                                        DockPosition::Bottom,
6124                                                        &self.bottom_dock,
6125                                                        window,
6126                                                        cx,
6127                                                    )),
6128                                            )
6129                                            .children(self.render_dock(
6130                                                DockPosition::Right,
6131                                                &self.right_dock,
6132                                                window,
6133                                                cx,
6134                                            )),
6135                                    }
6136                                })
6137                                .children(self.zoomed.as_ref().and_then(|view| {
6138                                    let zoomed_view = view.upgrade()?;
6139                                    let div = div()
6140                                        .occlude()
6141                                        .absolute()
6142                                        .overflow_hidden()
6143                                        .border_color(colors.border)
6144                                        .bg(colors.background)
6145                                        .child(zoomed_view)
6146                                        .inset_0()
6147                                        .shadow_lg();
6148
6149                                    Some(match self.zoomed_position {
6150                                        Some(DockPosition::Left) => div.right_2().border_r_1(),
6151                                        Some(DockPosition::Right) => div.left_2().border_l_1(),
6152                                        Some(DockPosition::Bottom) => div.top_2().border_t_1(),
6153                                        None => {
6154                                            div.top_2().bottom_2().left_2().right_2().border_1()
6155                                        }
6156                                    })
6157                                }))
6158                                .children(self.render_notifications(window, cx)),
6159                        )
6160                        .child(self.status_bar.clone())
6161                        .child(self.modal_layer.clone())
6162                        .child(self.toast_layer.clone()),
6163                ),
6164            window,
6165            cx,
6166        )
6167    }
6168}
6169
6170fn resize_bottom_dock(
6171    new_size: Pixels,
6172    workspace: &mut Workspace,
6173    window: &mut Window,
6174    cx: &mut App,
6175) {
6176    let size =
6177        new_size.min(workspace.bounds.bottom() - RESIZE_HANDLE_SIZE - workspace.bounds.top());
6178    workspace.bottom_dock.update(cx, |bottom_dock, cx| {
6179        bottom_dock.resize_active_panel(Some(size), window, cx);
6180    });
6181}
6182
6183fn resize_right_dock(
6184    new_size: Pixels,
6185    workspace: &mut Workspace,
6186    window: &mut Window,
6187    cx: &mut App,
6188) {
6189    let size = new_size.max(workspace.bounds.left() - RESIZE_HANDLE_SIZE);
6190    workspace.right_dock.update(cx, |right_dock, cx| {
6191        right_dock.resize_active_panel(Some(size), window, cx);
6192    });
6193}
6194
6195fn resize_left_dock(
6196    new_size: Pixels,
6197    workspace: &mut Workspace,
6198    window: &mut Window,
6199    cx: &mut App,
6200) {
6201    let size = new_size.min(workspace.bounds.right() - RESIZE_HANDLE_SIZE);
6202
6203    workspace.left_dock.update(cx, |left_dock, cx| {
6204        left_dock.resize_active_panel(Some(size), window, cx);
6205    });
6206}
6207
6208impl WorkspaceStore {
6209    pub fn new(client: Arc<Client>, cx: &mut Context<Self>) -> Self {
6210        Self {
6211            workspaces: Default::default(),
6212            _subscriptions: vec![
6213                client.add_request_handler(cx.weak_entity(), Self::handle_follow),
6214                client.add_message_handler(cx.weak_entity(), Self::handle_update_followers),
6215            ],
6216            client,
6217        }
6218    }
6219
6220    pub fn update_followers(
6221        &self,
6222        project_id: Option<u64>,
6223        update: proto::update_followers::Variant,
6224        cx: &App,
6225    ) -> Option<()> {
6226        let active_call = ActiveCall::try_global(cx)?;
6227        let room_id = active_call.read(cx).room()?.read(cx).id();
6228        self.client
6229            .send(proto::UpdateFollowers {
6230                room_id,
6231                project_id,
6232                variant: Some(update),
6233            })
6234            .log_err()
6235    }
6236
6237    pub async fn handle_follow(
6238        this: Entity<Self>,
6239        envelope: TypedEnvelope<proto::Follow>,
6240        mut cx: AsyncApp,
6241    ) -> Result<proto::FollowResponse> {
6242        this.update(&mut cx, |this, cx| {
6243            let follower = Follower {
6244                project_id: envelope.payload.project_id,
6245                peer_id: envelope.original_sender_id()?,
6246            };
6247
6248            let mut response = proto::FollowResponse::default();
6249            this.workspaces.retain(|workspace| {
6250                workspace
6251                    .update(cx, |workspace, window, cx| {
6252                        let handler_response =
6253                            workspace.handle_follow(follower.project_id, window, cx);
6254                        if let Some(active_view) = handler_response.active_view.clone() {
6255                            if workspace.project.read(cx).remote_id() == follower.project_id {
6256                                response.active_view = Some(active_view)
6257                            }
6258                        }
6259                    })
6260                    .is_ok()
6261            });
6262
6263            Ok(response)
6264        })?
6265    }
6266
6267    async fn handle_update_followers(
6268        this: Entity<Self>,
6269        envelope: TypedEnvelope<proto::UpdateFollowers>,
6270        mut cx: AsyncApp,
6271    ) -> Result<()> {
6272        let leader_id = envelope.original_sender_id()?;
6273        let update = envelope.payload;
6274
6275        this.update(&mut cx, |this, cx| {
6276            this.workspaces.retain(|workspace| {
6277                workspace
6278                    .update(cx, |workspace, window, cx| {
6279                        let project_id = workspace.project.read(cx).remote_id();
6280                        if update.project_id != project_id && update.project_id.is_some() {
6281                            return;
6282                        }
6283                        workspace.handle_update_followers(leader_id, update.clone(), window, cx);
6284                    })
6285                    .is_ok()
6286            });
6287            Ok(())
6288        })?
6289    }
6290}
6291
6292impl ViewId {
6293    pub(crate) fn from_proto(message: proto::ViewId) -> Result<Self> {
6294        Ok(Self {
6295            creator: message
6296                .creator
6297                .map(CollaboratorId::PeerId)
6298                .ok_or_else(|| anyhow!("creator is missing"))?,
6299            id: message.id,
6300        })
6301    }
6302
6303    pub(crate) fn to_proto(self) -> Option<proto::ViewId> {
6304        if let CollaboratorId::PeerId(peer_id) = self.creator {
6305            Some(proto::ViewId {
6306                creator: Some(peer_id),
6307                id: self.id,
6308            })
6309        } else {
6310            None
6311        }
6312    }
6313}
6314
6315impl FollowerState {
6316    fn pane(&self) -> &Entity<Pane> {
6317        self.dock_pane.as_ref().unwrap_or(&self.center_pane)
6318    }
6319}
6320
6321pub trait WorkspaceHandle {
6322    fn file_project_paths(&self, cx: &App) -> Vec<ProjectPath>;
6323}
6324
6325impl WorkspaceHandle for Entity<Workspace> {
6326    fn file_project_paths(&self, cx: &App) -> Vec<ProjectPath> {
6327        self.read(cx)
6328            .worktrees(cx)
6329            .flat_map(|worktree| {
6330                let worktree_id = worktree.read(cx).id();
6331                worktree.read(cx).files(true, 0).map(move |f| ProjectPath {
6332                    worktree_id,
6333                    path: f.path.clone(),
6334                })
6335            })
6336            .collect::<Vec<_>>()
6337    }
6338}
6339
6340impl std::fmt::Debug for OpenPaths {
6341    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
6342        f.debug_struct("OpenPaths")
6343            .field("paths", &self.paths)
6344            .finish()
6345    }
6346}
6347
6348pub async fn last_opened_workspace_location() -> Option<SerializedWorkspaceLocation> {
6349    DB.last_workspace().await.log_err().flatten()
6350}
6351
6352pub fn last_session_workspace_locations(
6353    last_session_id: &str,
6354    last_session_window_stack: Option<Vec<WindowId>>,
6355) -> Option<Vec<SerializedWorkspaceLocation>> {
6356    DB.last_session_workspace_locations(last_session_id, last_session_window_stack)
6357        .log_err()
6358}
6359
6360actions!(
6361    collab,
6362    [
6363        OpenChannelNotes,
6364        Mute,
6365        Deafen,
6366        LeaveCall,
6367        ShareProject,
6368        ScreenShare
6369    ]
6370);
6371actions!(zed, [OpenLog]);
6372
6373async fn join_channel_internal(
6374    channel_id: ChannelId,
6375    app_state: &Arc<AppState>,
6376    requesting_window: Option<WindowHandle<Workspace>>,
6377    active_call: &Entity<ActiveCall>,
6378    cx: &mut AsyncApp,
6379) -> Result<bool> {
6380    let (should_prompt, open_room) = active_call.update(cx, |active_call, cx| {
6381        let Some(room) = active_call.room().map(|room| room.read(cx)) else {
6382            return (false, None);
6383        };
6384
6385        let already_in_channel = room.channel_id() == Some(channel_id);
6386        let should_prompt = room.is_sharing_project()
6387            && !room.remote_participants().is_empty()
6388            && !already_in_channel;
6389        let open_room = if already_in_channel {
6390            active_call.room().cloned()
6391        } else {
6392            None
6393        };
6394        (should_prompt, open_room)
6395    })?;
6396
6397    if let Some(room) = open_room {
6398        let task = room.update(cx, |room, cx| {
6399            if let Some((project, host)) = room.most_active_project(cx) {
6400                return Some(join_in_room_project(project, host, app_state.clone(), cx));
6401            }
6402
6403            None
6404        })?;
6405        if let Some(task) = task {
6406            task.await?;
6407        }
6408        return anyhow::Ok(true);
6409    }
6410
6411    if should_prompt {
6412        if let Some(workspace) = requesting_window {
6413            let answer = workspace
6414                .update(cx, |_, window, cx| {
6415                    window.prompt(
6416                        PromptLevel::Warning,
6417                        "Do you want to switch channels?",
6418                        Some("Leaving this call will unshare your current project."),
6419                        &["Yes, Join Channel", "Cancel"],
6420                        cx,
6421                    )
6422                })?
6423                .await;
6424
6425            if answer == Ok(1) {
6426                return Ok(false);
6427            }
6428        } else {
6429            return Ok(false); // unreachable!() hopefully
6430        }
6431    }
6432
6433    let client = cx.update(|cx| active_call.read(cx).client())?;
6434
6435    let mut client_status = client.status();
6436
6437    // this loop will terminate within client::CONNECTION_TIMEOUT seconds.
6438    'outer: loop {
6439        let Some(status) = client_status.recv().await else {
6440            return Err(anyhow!("error connecting"));
6441        };
6442
6443        match status {
6444            Status::Connecting
6445            | Status::Authenticating
6446            | Status::Reconnecting
6447            | Status::Reauthenticating => continue,
6448            Status::Connected { .. } => break 'outer,
6449            Status::SignedOut => return Err(ErrorCode::SignedOut.into()),
6450            Status::UpgradeRequired => return Err(ErrorCode::UpgradeRequired.into()),
6451            Status::ConnectionError | Status::ConnectionLost | Status::ReconnectionError { .. } => {
6452                return Err(ErrorCode::Disconnected.into());
6453            }
6454        }
6455    }
6456
6457    let room = active_call
6458        .update(cx, |active_call, cx| {
6459            active_call.join_channel(channel_id, cx)
6460        })?
6461        .await?;
6462
6463    let Some(room) = room else {
6464        return anyhow::Ok(true);
6465    };
6466
6467    room.update(cx, |room, _| room.room_update_completed())?
6468        .await;
6469
6470    let task = room.update(cx, |room, cx| {
6471        if let Some((project, host)) = room.most_active_project(cx) {
6472            return Some(join_in_room_project(project, host, app_state.clone(), cx));
6473        }
6474
6475        // If you are the first to join a channel, see if you should share your project.
6476        if room.remote_participants().is_empty() && !room.local_participant_is_guest() {
6477            if let Some(workspace) = requesting_window {
6478                let project = workspace.update(cx, |workspace, _, cx| {
6479                    let project = workspace.project.read(cx);
6480
6481                    if !CallSettings::get_global(cx).share_on_join {
6482                        return None;
6483                    }
6484
6485                    if (project.is_local() || project.is_via_ssh())
6486                        && project.visible_worktrees(cx).any(|tree| {
6487                            tree.read(cx)
6488                                .root_entry()
6489                                .map_or(false, |entry| entry.is_dir())
6490                        })
6491                    {
6492                        Some(workspace.project.clone())
6493                    } else {
6494                        None
6495                    }
6496                });
6497                if let Ok(Some(project)) = project {
6498                    return Some(cx.spawn(async move |room, cx| {
6499                        room.update(cx, |room, cx| room.share_project(project, cx))?
6500                            .await?;
6501                        Ok(())
6502                    }));
6503                }
6504            }
6505        }
6506
6507        None
6508    })?;
6509    if let Some(task) = task {
6510        task.await?;
6511        return anyhow::Ok(true);
6512    }
6513    anyhow::Ok(false)
6514}
6515
6516pub fn join_channel(
6517    channel_id: ChannelId,
6518    app_state: Arc<AppState>,
6519    requesting_window: Option<WindowHandle<Workspace>>,
6520    cx: &mut App,
6521) -> Task<Result<()>> {
6522    let active_call = ActiveCall::global(cx);
6523    cx.spawn(async move |cx| {
6524        let result = join_channel_internal(
6525            channel_id,
6526            &app_state,
6527            requesting_window,
6528            &active_call,
6529             cx,
6530        )
6531            .await;
6532
6533        // join channel succeeded, and opened a window
6534        if matches!(result, Ok(true)) {
6535            return anyhow::Ok(());
6536        }
6537
6538        // find an existing workspace to focus and show call controls
6539        let mut active_window =
6540            requesting_window.or_else(|| activate_any_workspace_window( cx));
6541        if active_window.is_none() {
6542            // no open workspaces, make one to show the error in (blergh)
6543            let (window_handle, _) = cx
6544                .update(|cx| {
6545                    Workspace::new_local(vec![], app_state.clone(), requesting_window, None, cx)
6546                })?
6547                .await?;
6548
6549            if result.is_ok() {
6550                cx.update(|cx| {
6551                    cx.dispatch_action(&OpenChannelNotes);
6552                }).log_err();
6553            }
6554
6555            active_window = Some(window_handle);
6556        }
6557
6558        if let Err(err) = result {
6559            log::error!("failed to join channel: {}", err);
6560            if let Some(active_window) = active_window {
6561                active_window
6562                    .update(cx, |_, window, cx| {
6563                        let detail: SharedString = match err.error_code() {
6564                            ErrorCode::SignedOut => {
6565                                "Please sign in to continue.".into()
6566                            }
6567                            ErrorCode::UpgradeRequired => {
6568                                "Your are running an unsupported version of Zed. Please update to continue.".into()
6569                            }
6570                            ErrorCode::NoSuchChannel => {
6571                                "No matching channel was found. Please check the link and try again.".into()
6572                            }
6573                            ErrorCode::Forbidden => {
6574                                "This channel is private, and you do not have access. Please ask someone to add you and try again.".into()
6575                            }
6576                            ErrorCode::Disconnected => "Please check your internet connection and try again.".into(),
6577                            _ => format!("{}\n\nPlease try again.", err).into(),
6578                        };
6579                        window.prompt(
6580                            PromptLevel::Critical,
6581                            "Failed to join channel",
6582                            Some(&detail),
6583                            &["Ok"],
6584                        cx)
6585                    })?
6586                    .await
6587                    .ok();
6588            }
6589        }
6590
6591        // return ok, we showed the error to the user.
6592        anyhow::Ok(())
6593    })
6594}
6595
6596pub async fn get_any_active_workspace(
6597    app_state: Arc<AppState>,
6598    mut cx: AsyncApp,
6599) -> anyhow::Result<WindowHandle<Workspace>> {
6600    // find an existing workspace to focus and show call controls
6601    let active_window = activate_any_workspace_window(&mut cx);
6602    if active_window.is_none() {
6603        cx.update(|cx| Workspace::new_local(vec![], app_state.clone(), None, None, cx))?
6604            .await?;
6605    }
6606    activate_any_workspace_window(&mut cx).context("could not open zed")
6607}
6608
6609fn activate_any_workspace_window(cx: &mut AsyncApp) -> Option<WindowHandle<Workspace>> {
6610    cx.update(|cx| {
6611        if let Some(workspace_window) = cx
6612            .active_window()
6613            .and_then(|window| window.downcast::<Workspace>())
6614        {
6615            return Some(workspace_window);
6616        }
6617
6618        for window in cx.windows() {
6619            if let Some(workspace_window) = window.downcast::<Workspace>() {
6620                workspace_window
6621                    .update(cx, |_, window, _| window.activate_window())
6622                    .ok();
6623                return Some(workspace_window);
6624            }
6625        }
6626        None
6627    })
6628    .ok()
6629    .flatten()
6630}
6631
6632pub fn local_workspace_windows(cx: &App) -> Vec<WindowHandle<Workspace>> {
6633    cx.windows()
6634        .into_iter()
6635        .filter_map(|window| window.downcast::<Workspace>())
6636        .filter(|workspace| {
6637            workspace
6638                .read(cx)
6639                .is_ok_and(|workspace| workspace.project.read(cx).is_local())
6640        })
6641        .collect()
6642}
6643
6644#[derive(Default)]
6645pub struct OpenOptions {
6646    pub visible: Option<OpenVisible>,
6647    pub focus: Option<bool>,
6648    pub open_new_workspace: Option<bool>,
6649    pub replace_window: Option<WindowHandle<Workspace>>,
6650    pub env: Option<HashMap<String, String>>,
6651}
6652
6653#[allow(clippy::type_complexity)]
6654pub fn open_paths(
6655    abs_paths: &[PathBuf],
6656    app_state: Arc<AppState>,
6657    open_options: OpenOptions,
6658    cx: &mut App,
6659) -> Task<
6660    anyhow::Result<(
6661        WindowHandle<Workspace>,
6662        Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
6663    )>,
6664> {
6665    let abs_paths = abs_paths.to_vec();
6666    let mut existing = None;
6667    let mut best_match = None;
6668    let mut open_visible = OpenVisible::All;
6669
6670    cx.spawn(async move |cx| {
6671        if open_options.open_new_workspace != Some(true) {
6672            let all_paths = abs_paths.iter().map(|path| app_state.fs.metadata(path));
6673            let all_metadatas = futures::future::join_all(all_paths)
6674                .await
6675                .into_iter()
6676                .filter_map(|result| result.ok().flatten())
6677                .collect::<Vec<_>>();
6678
6679            cx.update(|cx| {
6680                for window in local_workspace_windows(&cx) {
6681                    if let Ok(workspace) = window.read(&cx) {
6682                        let m = workspace.project.read(&cx).visibility_for_paths(
6683                            &abs_paths,
6684                            &all_metadatas,
6685                            open_options.open_new_workspace == None,
6686                            cx,
6687                        );
6688                        if m > best_match {
6689                            existing = Some(window);
6690                            best_match = m;
6691                        } else if best_match.is_none()
6692                            && open_options.open_new_workspace == Some(false)
6693                        {
6694                            existing = Some(window)
6695                        }
6696                    }
6697                }
6698            })?;
6699
6700            if open_options.open_new_workspace.is_none() && existing.is_none() {
6701                if all_metadatas.iter().all(|file| !file.is_dir) {
6702                    cx.update(|cx| {
6703                        if let Some(window) = cx
6704                            .active_window()
6705                            .and_then(|window| window.downcast::<Workspace>())
6706                        {
6707                            if let Ok(workspace) = window.read(cx) {
6708                                let project = workspace.project().read(cx);
6709                                if project.is_local() && !project.is_via_collab() {
6710                                    existing = Some(window);
6711                                    open_visible = OpenVisible::None;
6712                                    return;
6713                                }
6714                            }
6715                        }
6716                        for window in local_workspace_windows(cx) {
6717                            if let Ok(workspace) = window.read(cx) {
6718                                let project = workspace.project().read(cx);
6719                                if project.is_via_collab() {
6720                                    continue;
6721                                }
6722                                existing = Some(window);
6723                                open_visible = OpenVisible::None;
6724                                break;
6725                            }
6726                        }
6727                    })?;
6728                }
6729            }
6730        }
6731
6732        if let Some(existing) = existing {
6733            let open_task = existing
6734                .update(cx, |workspace, window, cx| {
6735                    window.activate_window();
6736                    workspace.open_paths(
6737                        abs_paths,
6738                        OpenOptions {
6739                            visible: Some(open_visible),
6740                            ..Default::default()
6741                        },
6742                        None,
6743                        window,
6744                        cx,
6745                    )
6746                })?
6747                .await;
6748
6749            _ = existing.update(cx, |workspace, _, cx| {
6750                for item in open_task.iter().flatten() {
6751                    if let Err(e) = item {
6752                        workspace.show_error(&e, cx);
6753                    }
6754                }
6755            });
6756
6757            Ok((existing, open_task))
6758        } else {
6759            cx.update(move |cx| {
6760                Workspace::new_local(
6761                    abs_paths,
6762                    app_state.clone(),
6763                    open_options.replace_window,
6764                    open_options.env,
6765                    cx,
6766                )
6767            })?
6768            .await
6769        }
6770    })
6771}
6772
6773pub fn open_new(
6774    open_options: OpenOptions,
6775    app_state: Arc<AppState>,
6776    cx: &mut App,
6777    init: impl FnOnce(&mut Workspace, &mut Window, &mut Context<Workspace>) + 'static + Send,
6778) -> Task<anyhow::Result<()>> {
6779    let task = Workspace::new_local(Vec::new(), app_state, None, open_options.env, cx);
6780    cx.spawn(async move |cx| {
6781        let (workspace, opened_paths) = task.await?;
6782        workspace.update(cx, |workspace, window, cx| {
6783            if opened_paths.is_empty() {
6784                init(workspace, window, cx)
6785            }
6786        })?;
6787        Ok(())
6788    })
6789}
6790
6791pub fn create_and_open_local_file(
6792    path: &'static Path,
6793    window: &mut Window,
6794    cx: &mut Context<Workspace>,
6795    default_content: impl 'static + Send + FnOnce() -> Rope,
6796) -> Task<Result<Box<dyn ItemHandle>>> {
6797    cx.spawn_in(window, async move |workspace, cx| {
6798        let fs = workspace.update(cx, |workspace, _| workspace.app_state().fs.clone())?;
6799        if !fs.is_file(path).await {
6800            fs.create_file(path, Default::default()).await?;
6801            fs.save(path, &default_content(), Default::default())
6802                .await?;
6803        }
6804
6805        let mut items = workspace
6806            .update_in(cx, |workspace, window, cx| {
6807                workspace.with_local_workspace(window, cx, |workspace, window, cx| {
6808                    workspace.open_paths(
6809                        vec![path.to_path_buf()],
6810                        OpenOptions {
6811                            visible: Some(OpenVisible::None),
6812                            ..Default::default()
6813                        },
6814                        None,
6815                        window,
6816                        cx,
6817                    )
6818                })
6819            })?
6820            .await?
6821            .await;
6822
6823        let item = items.pop().flatten();
6824        item.ok_or_else(|| anyhow!("path {path:?} is not a file"))?
6825    })
6826}
6827
6828pub fn open_ssh_project_with_new_connection(
6829    window: WindowHandle<Workspace>,
6830    connection_options: SshConnectionOptions,
6831    cancel_rx: oneshot::Receiver<()>,
6832    delegate: Arc<dyn SshClientDelegate>,
6833    app_state: Arc<AppState>,
6834    paths: Vec<PathBuf>,
6835    cx: &mut App,
6836) -> Task<Result<()>> {
6837    cx.spawn(async move |cx| {
6838        let (serialized_ssh_project, workspace_id, serialized_workspace) =
6839            serialize_ssh_project(connection_options.clone(), paths.clone(), &cx).await?;
6840
6841        let session = match cx
6842            .update(|cx| {
6843                remote::SshRemoteClient::new(
6844                    ConnectionIdentifier::Workspace(workspace_id.0),
6845                    connection_options,
6846                    cancel_rx,
6847                    delegate,
6848                    cx,
6849                )
6850            })?
6851            .await?
6852        {
6853            Some(result) => result,
6854            None => return Ok(()),
6855        };
6856
6857        let project = cx.update(|cx| {
6858            project::Project::ssh(
6859                session,
6860                app_state.client.clone(),
6861                app_state.node_runtime.clone(),
6862                app_state.user_store.clone(),
6863                app_state.languages.clone(),
6864                app_state.fs.clone(),
6865                cx,
6866            )
6867        })?;
6868
6869        open_ssh_project_inner(
6870            project,
6871            paths,
6872            serialized_ssh_project,
6873            workspace_id,
6874            serialized_workspace,
6875            app_state,
6876            window,
6877            cx,
6878        )
6879        .await
6880    })
6881}
6882
6883pub fn open_ssh_project_with_existing_connection(
6884    connection_options: SshConnectionOptions,
6885    project: Entity<Project>,
6886    paths: Vec<PathBuf>,
6887    app_state: Arc<AppState>,
6888    window: WindowHandle<Workspace>,
6889    cx: &mut AsyncApp,
6890) -> Task<Result<()>> {
6891    cx.spawn(async move |cx| {
6892        let (serialized_ssh_project, workspace_id, serialized_workspace) =
6893            serialize_ssh_project(connection_options.clone(), paths.clone(), &cx).await?;
6894
6895        open_ssh_project_inner(
6896            project,
6897            paths,
6898            serialized_ssh_project,
6899            workspace_id,
6900            serialized_workspace,
6901            app_state,
6902            window,
6903            cx,
6904        )
6905        .await
6906    })
6907}
6908
6909async fn open_ssh_project_inner(
6910    project: Entity<Project>,
6911    paths: Vec<PathBuf>,
6912    serialized_ssh_project: SerializedSshProject,
6913    workspace_id: WorkspaceId,
6914    serialized_workspace: Option<SerializedWorkspace>,
6915    app_state: Arc<AppState>,
6916    window: WindowHandle<Workspace>,
6917    cx: &mut AsyncApp,
6918) -> Result<()> {
6919    let toolchains = DB.toolchains(workspace_id).await?;
6920    for (toolchain, worktree_id, path) in toolchains {
6921        project
6922            .update(cx, |this, cx| {
6923                this.activate_toolchain(ProjectPath { worktree_id, path }, toolchain, cx)
6924            })?
6925            .await;
6926    }
6927    let mut project_paths_to_open = vec![];
6928    let mut project_path_errors = vec![];
6929
6930    for path in paths {
6931        let result = cx
6932            .update(|cx| Workspace::project_path_for_path(project.clone(), &path, true, cx))?
6933            .await;
6934        match result {
6935            Ok((_, project_path)) => {
6936                project_paths_to_open.push((path.clone(), Some(project_path)));
6937            }
6938            Err(error) => {
6939                project_path_errors.push(error);
6940            }
6941        };
6942    }
6943
6944    if project_paths_to_open.is_empty() {
6945        return Err(project_path_errors
6946            .pop()
6947            .unwrap_or_else(|| anyhow!("no paths given")));
6948    }
6949
6950    cx.update_window(window.into(), |_, window, cx| {
6951        window.replace_root(cx, |window, cx| {
6952            telemetry::event!("SSH Project Opened");
6953
6954            let mut workspace =
6955                Workspace::new(Some(workspace_id), project, app_state.clone(), window, cx);
6956            workspace.set_serialized_ssh_project(serialized_ssh_project);
6957            workspace.update_history(cx);
6958            workspace
6959        });
6960    })?;
6961
6962    window
6963        .update(cx, |_, window, cx| {
6964            window.activate_window();
6965            open_items(serialized_workspace, project_paths_to_open, window, cx)
6966        })?
6967        .await?;
6968
6969    window.update(cx, |workspace, _, cx| {
6970        for error in project_path_errors {
6971            if error.error_code() == proto::ErrorCode::DevServerProjectPathDoesNotExist {
6972                if let Some(path) = error.error_tag("path") {
6973                    workspace.show_error(&anyhow!("'{path}' does not exist"), cx)
6974                }
6975            } else {
6976                workspace.show_error(&error, cx)
6977            }
6978        }
6979    })?;
6980
6981    Ok(())
6982}
6983
6984fn serialize_ssh_project(
6985    connection_options: SshConnectionOptions,
6986    paths: Vec<PathBuf>,
6987    cx: &AsyncApp,
6988) -> Task<
6989    Result<(
6990        SerializedSshProject,
6991        WorkspaceId,
6992        Option<SerializedWorkspace>,
6993    )>,
6994> {
6995    cx.background_spawn(async move {
6996        let serialized_ssh_project = persistence::DB
6997            .get_or_create_ssh_project(
6998                connection_options.host.clone(),
6999                connection_options.port,
7000                paths
7001                    .iter()
7002                    .map(|path| path.to_string_lossy().to_string())
7003                    .collect::<Vec<_>>(),
7004                connection_options.username.clone(),
7005            )
7006            .await?;
7007
7008        let serialized_workspace =
7009            persistence::DB.workspace_for_ssh_project(&serialized_ssh_project);
7010
7011        let workspace_id = if let Some(workspace_id) =
7012            serialized_workspace.as_ref().map(|workspace| workspace.id)
7013        {
7014            workspace_id
7015        } else {
7016            persistence::DB.next_id().await?
7017        };
7018
7019        Ok((serialized_ssh_project, workspace_id, serialized_workspace))
7020    })
7021}
7022
7023pub fn join_in_room_project(
7024    project_id: u64,
7025    follow_user_id: u64,
7026    app_state: Arc<AppState>,
7027    cx: &mut App,
7028) -> Task<Result<()>> {
7029    let windows = cx.windows();
7030    cx.spawn(async move |cx| {
7031        let existing_workspace = windows.into_iter().find_map(|window_handle| {
7032            window_handle
7033                .downcast::<Workspace>()
7034                .and_then(|window_handle| {
7035                    window_handle
7036                        .update(cx, |workspace, _window, cx| {
7037                            if workspace.project().read(cx).remote_id() == Some(project_id) {
7038                                Some(window_handle)
7039                            } else {
7040                                None
7041                            }
7042                        })
7043                        .unwrap_or(None)
7044                })
7045        });
7046
7047        let workspace = if let Some(existing_workspace) = existing_workspace {
7048            existing_workspace
7049        } else {
7050            let active_call = cx.update(|cx| ActiveCall::global(cx))?;
7051            let room = active_call
7052                .read_with(cx, |call, _| call.room().cloned())?
7053                .ok_or_else(|| anyhow!("not in a call"))?;
7054            let project = room
7055                .update(cx, |room, cx| {
7056                    room.join_project(
7057                        project_id,
7058                        app_state.languages.clone(),
7059                        app_state.fs.clone(),
7060                        cx,
7061                    )
7062                })?
7063                .await?;
7064
7065            let window_bounds_override = window_bounds_env_override();
7066            cx.update(|cx| {
7067                let mut options = (app_state.build_window_options)(None, cx);
7068                options.window_bounds = window_bounds_override.map(WindowBounds::Windowed);
7069                cx.open_window(options, |window, cx| {
7070                    cx.new(|cx| {
7071                        Workspace::new(Default::default(), project, app_state.clone(), window, cx)
7072                    })
7073                })
7074            })??
7075        };
7076
7077        workspace.update(cx, |workspace, window, cx| {
7078            cx.activate(true);
7079            window.activate_window();
7080
7081            if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
7082                let follow_peer_id = room
7083                    .read(cx)
7084                    .remote_participants()
7085                    .iter()
7086                    .find(|(_, participant)| participant.user.id == follow_user_id)
7087                    .map(|(_, p)| p.peer_id)
7088                    .or_else(|| {
7089                        // If we couldn't follow the given user, follow the host instead.
7090                        let collaborator = workspace
7091                            .project()
7092                            .read(cx)
7093                            .collaborators()
7094                            .values()
7095                            .find(|collaborator| collaborator.is_host)?;
7096                        Some(collaborator.peer_id)
7097                    });
7098
7099                if let Some(follow_peer_id) = follow_peer_id {
7100                    workspace.follow(follow_peer_id, window, cx);
7101                }
7102            }
7103        })?;
7104
7105        anyhow::Ok(())
7106    })
7107}
7108
7109pub fn reload(reload: &Reload, cx: &mut App) {
7110    let should_confirm = WorkspaceSettings::get_global(cx).confirm_quit;
7111    let mut workspace_windows = cx
7112        .windows()
7113        .into_iter()
7114        .filter_map(|window| window.downcast::<Workspace>())
7115        .collect::<Vec<_>>();
7116
7117    // If multiple windows have unsaved changes, and need a save prompt,
7118    // prompt in the active window before switching to a different window.
7119    workspace_windows.sort_by_key(|window| window.is_active(cx) == Some(false));
7120
7121    let mut prompt = None;
7122    if let (true, Some(window)) = (should_confirm, workspace_windows.first()) {
7123        prompt = window
7124            .update(cx, |_, window, cx| {
7125                window.prompt(
7126                    PromptLevel::Info,
7127                    "Are you sure you want to restart?",
7128                    None,
7129                    &["Restart", "Cancel"],
7130                    cx,
7131                )
7132            })
7133            .ok();
7134    }
7135
7136    let binary_path = reload.binary_path.clone();
7137    cx.spawn(async move |cx| {
7138        if let Some(prompt) = prompt {
7139            let answer = prompt.await?;
7140            if answer != 0 {
7141                return Ok(());
7142            }
7143        }
7144
7145        // If the user cancels any save prompt, then keep the app open.
7146        for window in workspace_windows {
7147            if let Ok(should_close) = window.update(cx, |workspace, window, cx| {
7148                workspace.prepare_to_close(CloseIntent::Quit, window, cx)
7149            }) {
7150                if !should_close.await? {
7151                    return Ok(());
7152                }
7153            }
7154        }
7155
7156        cx.update(|cx| cx.restart(binary_path))
7157    })
7158    .detach_and_log_err(cx);
7159}
7160
7161fn parse_pixel_position_env_var(value: &str) -> Option<Point<Pixels>> {
7162    let mut parts = value.split(',');
7163    let x: usize = parts.next()?.parse().ok()?;
7164    let y: usize = parts.next()?.parse().ok()?;
7165    Some(point(px(x as f32), px(y as f32)))
7166}
7167
7168fn parse_pixel_size_env_var(value: &str) -> Option<Size<Pixels>> {
7169    let mut parts = value.split(',');
7170    let width: usize = parts.next()?.parse().ok()?;
7171    let height: usize = parts.next()?.parse().ok()?;
7172    Some(size(px(width as f32), px(height as f32)))
7173}
7174
7175pub fn client_side_decorations(
7176    element: impl IntoElement,
7177    window: &mut Window,
7178    cx: &mut App,
7179) -> Stateful<Div> {
7180    const BORDER_SIZE: Pixels = px(1.0);
7181    let decorations = window.window_decorations();
7182
7183    if matches!(decorations, Decorations::Client { .. }) {
7184        window.set_client_inset(theme::CLIENT_SIDE_DECORATION_SHADOW);
7185    }
7186
7187    struct GlobalResizeEdge(ResizeEdge);
7188    impl Global for GlobalResizeEdge {}
7189
7190    div()
7191        .id("window-backdrop")
7192        .bg(transparent_black())
7193        .map(|div| match decorations {
7194            Decorations::Server => div,
7195            Decorations::Client { tiling, .. } => div
7196                .when(!(tiling.top || tiling.right), |div| {
7197                    div.rounded_tr(theme::CLIENT_SIDE_DECORATION_ROUNDING)
7198                })
7199                .when(!(tiling.top || tiling.left), |div| {
7200                    div.rounded_tl(theme::CLIENT_SIDE_DECORATION_ROUNDING)
7201                })
7202                .when(!(tiling.bottom || tiling.right), |div| {
7203                    div.rounded_br(theme::CLIENT_SIDE_DECORATION_ROUNDING)
7204                })
7205                .when(!(tiling.bottom || tiling.left), |div| {
7206                    div.rounded_bl(theme::CLIENT_SIDE_DECORATION_ROUNDING)
7207                })
7208                .when(!tiling.top, |div| {
7209                    div.pt(theme::CLIENT_SIDE_DECORATION_SHADOW)
7210                })
7211                .when(!tiling.bottom, |div| {
7212                    div.pb(theme::CLIENT_SIDE_DECORATION_SHADOW)
7213                })
7214                .when(!tiling.left, |div| {
7215                    div.pl(theme::CLIENT_SIDE_DECORATION_SHADOW)
7216                })
7217                .when(!tiling.right, |div| {
7218                    div.pr(theme::CLIENT_SIDE_DECORATION_SHADOW)
7219                })
7220                .on_mouse_move(move |e, window, cx| {
7221                    let size = window.window_bounds().get_bounds().size;
7222                    let pos = e.position;
7223
7224                    let new_edge =
7225                        resize_edge(pos, theme::CLIENT_SIDE_DECORATION_SHADOW, size, tiling);
7226
7227                    let edge = cx.try_global::<GlobalResizeEdge>();
7228                    if new_edge != edge.map(|edge| edge.0) {
7229                        window
7230                            .window_handle()
7231                            .update(cx, |workspace, _, cx| {
7232                                cx.notify(workspace.entity_id());
7233                            })
7234                            .ok();
7235                    }
7236                })
7237                .on_mouse_down(MouseButton::Left, move |e, window, _| {
7238                    let size = window.window_bounds().get_bounds().size;
7239                    let pos = e.position;
7240
7241                    let edge = match resize_edge(
7242                        pos,
7243                        theme::CLIENT_SIDE_DECORATION_SHADOW,
7244                        size,
7245                        tiling,
7246                    ) {
7247                        Some(value) => value,
7248                        None => return,
7249                    };
7250
7251                    window.start_window_resize(edge);
7252                }),
7253        })
7254        .size_full()
7255        .child(
7256            div()
7257                .cursor(CursorStyle::Arrow)
7258                .map(|div| match decorations {
7259                    Decorations::Server => div,
7260                    Decorations::Client { tiling } => div
7261                        .border_color(cx.theme().colors().border)
7262                        .when(!(tiling.top || tiling.right), |div| {
7263                            div.rounded_tr(theme::CLIENT_SIDE_DECORATION_ROUNDING)
7264                        })
7265                        .when(!(tiling.top || tiling.left), |div| {
7266                            div.rounded_tl(theme::CLIENT_SIDE_DECORATION_ROUNDING)
7267                        })
7268                        .when(!(tiling.bottom || tiling.right), |div| {
7269                            div.rounded_br(theme::CLIENT_SIDE_DECORATION_ROUNDING)
7270                        })
7271                        .when(!(tiling.bottom || tiling.left), |div| {
7272                            div.rounded_bl(theme::CLIENT_SIDE_DECORATION_ROUNDING)
7273                        })
7274                        .when(!tiling.top, |div| div.border_t(BORDER_SIZE))
7275                        .when(!tiling.bottom, |div| div.border_b(BORDER_SIZE))
7276                        .when(!tiling.left, |div| div.border_l(BORDER_SIZE))
7277                        .when(!tiling.right, |div| div.border_r(BORDER_SIZE))
7278                        .when(!tiling.is_tiled(), |div| {
7279                            div.shadow(smallvec::smallvec![gpui::BoxShadow {
7280                                color: Hsla {
7281                                    h: 0.,
7282                                    s: 0.,
7283                                    l: 0.,
7284                                    a: 0.4,
7285                                },
7286                                blur_radius: theme::CLIENT_SIDE_DECORATION_SHADOW / 2.,
7287                                spread_radius: px(0.),
7288                                offset: point(px(0.0), px(0.0)),
7289                            }])
7290                        }),
7291                })
7292                .on_mouse_move(|_e, _, cx| {
7293                    cx.stop_propagation();
7294                })
7295                .size_full()
7296                .child(element),
7297        )
7298        .map(|div| match decorations {
7299            Decorations::Server => div,
7300            Decorations::Client { tiling, .. } => div.child(
7301                canvas(
7302                    |_bounds, window, _| {
7303                        window.insert_hitbox(
7304                            Bounds::new(
7305                                point(px(0.0), px(0.0)),
7306                                window.window_bounds().get_bounds().size,
7307                            ),
7308                            false,
7309                        )
7310                    },
7311                    move |_bounds, hitbox, window, cx| {
7312                        let mouse = window.mouse_position();
7313                        let size = window.window_bounds().get_bounds().size;
7314                        let Some(edge) =
7315                            resize_edge(mouse, theme::CLIENT_SIDE_DECORATION_SHADOW, size, tiling)
7316                        else {
7317                            return;
7318                        };
7319                        cx.set_global(GlobalResizeEdge(edge));
7320                        window.set_cursor_style(
7321                            match edge {
7322                                ResizeEdge::Top | ResizeEdge::Bottom => CursorStyle::ResizeUpDown,
7323                                ResizeEdge::Left | ResizeEdge::Right => {
7324                                    CursorStyle::ResizeLeftRight
7325                                }
7326                                ResizeEdge::TopLeft | ResizeEdge::BottomRight => {
7327                                    CursorStyle::ResizeUpLeftDownRight
7328                                }
7329                                ResizeEdge::TopRight | ResizeEdge::BottomLeft => {
7330                                    CursorStyle::ResizeUpRightDownLeft
7331                                }
7332                            },
7333                            Some(&hitbox),
7334                        );
7335                    },
7336                )
7337                .size_full()
7338                .absolute(),
7339            ),
7340        })
7341}
7342
7343fn resize_edge(
7344    pos: Point<Pixels>,
7345    shadow_size: Pixels,
7346    window_size: Size<Pixels>,
7347    tiling: Tiling,
7348) -> Option<ResizeEdge> {
7349    let bounds = Bounds::new(Point::default(), window_size).inset(shadow_size * 1.5);
7350    if bounds.contains(&pos) {
7351        return None;
7352    }
7353
7354    let corner_size = size(shadow_size * 1.5, shadow_size * 1.5);
7355    let top_left_bounds = Bounds::new(Point::new(px(0.), px(0.)), corner_size);
7356    if !tiling.top && top_left_bounds.contains(&pos) {
7357        return Some(ResizeEdge::TopLeft);
7358    }
7359
7360    let top_right_bounds = Bounds::new(
7361        Point::new(window_size.width - corner_size.width, px(0.)),
7362        corner_size,
7363    );
7364    if !tiling.top && top_right_bounds.contains(&pos) {
7365        return Some(ResizeEdge::TopRight);
7366    }
7367
7368    let bottom_left_bounds = Bounds::new(
7369        Point::new(px(0.), window_size.height - corner_size.height),
7370        corner_size,
7371    );
7372    if !tiling.bottom && bottom_left_bounds.contains(&pos) {
7373        return Some(ResizeEdge::BottomLeft);
7374    }
7375
7376    let bottom_right_bounds = Bounds::new(
7377        Point::new(
7378            window_size.width - corner_size.width,
7379            window_size.height - corner_size.height,
7380        ),
7381        corner_size,
7382    );
7383    if !tiling.bottom && bottom_right_bounds.contains(&pos) {
7384        return Some(ResizeEdge::BottomRight);
7385    }
7386
7387    if !tiling.top && pos.y < shadow_size {
7388        Some(ResizeEdge::Top)
7389    } else if !tiling.bottom && pos.y > window_size.height - shadow_size {
7390        Some(ResizeEdge::Bottom)
7391    } else if !tiling.left && pos.x < shadow_size {
7392        Some(ResizeEdge::Left)
7393    } else if !tiling.right && pos.x > window_size.width - shadow_size {
7394        Some(ResizeEdge::Right)
7395    } else {
7396        None
7397    }
7398}
7399
7400fn join_pane_into_active(
7401    active_pane: &Entity<Pane>,
7402    pane: &Entity<Pane>,
7403    window: &mut Window,
7404    cx: &mut App,
7405) {
7406    if pane == active_pane {
7407        return;
7408    } else if pane.read(cx).items_len() == 0 {
7409        pane.update(cx, |_, cx| {
7410            cx.emit(pane::Event::Remove {
7411                focus_on_pane: None,
7412            });
7413        })
7414    } else {
7415        move_all_items(pane, active_pane, window, cx);
7416    }
7417}
7418
7419fn move_all_items(
7420    from_pane: &Entity<Pane>,
7421    to_pane: &Entity<Pane>,
7422    window: &mut Window,
7423    cx: &mut App,
7424) {
7425    let destination_is_different = from_pane != to_pane;
7426    let mut moved_items = 0;
7427    for (item_ix, item_handle) in from_pane
7428        .read(cx)
7429        .items()
7430        .enumerate()
7431        .map(|(ix, item)| (ix, item.clone()))
7432        .collect::<Vec<_>>()
7433    {
7434        let ix = item_ix - moved_items;
7435        if destination_is_different {
7436            // Close item from previous pane
7437            from_pane.update(cx, |source, cx| {
7438                source.remove_item_and_focus_on_pane(ix, false, to_pane.clone(), window, cx);
7439            });
7440            moved_items += 1;
7441        }
7442
7443        // This automatically removes duplicate items in the pane
7444        to_pane.update(cx, |destination, cx| {
7445            destination.add_item(item_handle, true, true, None, window, cx);
7446            window.focus(&destination.focus_handle(cx))
7447        });
7448    }
7449}
7450
7451pub fn move_item(
7452    source: &Entity<Pane>,
7453    destination: &Entity<Pane>,
7454    item_id_to_move: EntityId,
7455    destination_index: usize,
7456    window: &mut Window,
7457    cx: &mut App,
7458) {
7459    let Some((item_ix, item_handle)) = source
7460        .read(cx)
7461        .items()
7462        .enumerate()
7463        .find(|(_, item_handle)| item_handle.item_id() == item_id_to_move)
7464        .map(|(ix, item)| (ix, item.clone()))
7465    else {
7466        // Tab was closed during drag
7467        return;
7468    };
7469
7470    if source != destination {
7471        // Close item from previous pane
7472        source.update(cx, |source, cx| {
7473            source.remove_item_and_focus_on_pane(item_ix, false, destination.clone(), window, cx);
7474        });
7475    }
7476
7477    // This automatically removes duplicate items in the pane
7478    destination.update(cx, |destination, cx| {
7479        destination.add_item(item_handle, true, true, Some(destination_index), window, cx);
7480        window.focus(&destination.focus_handle(cx))
7481    });
7482}
7483
7484pub fn move_active_item(
7485    source: &Entity<Pane>,
7486    destination: &Entity<Pane>,
7487    focus_destination: bool,
7488    close_if_empty: bool,
7489    window: &mut Window,
7490    cx: &mut App,
7491) {
7492    if source == destination {
7493        return;
7494    }
7495    let Some(active_item) = source.read(cx).active_item() else {
7496        return;
7497    };
7498    source.update(cx, |source_pane, cx| {
7499        let item_id = active_item.item_id();
7500        source_pane.remove_item(item_id, false, close_if_empty, window, cx);
7501        destination.update(cx, |target_pane, cx| {
7502            target_pane.add_item(
7503                active_item,
7504                focus_destination,
7505                focus_destination,
7506                Some(target_pane.items_len()),
7507                window,
7508                cx,
7509            );
7510        });
7511    });
7512}
7513
7514#[derive(Debug)]
7515pub struct WorkspacePosition {
7516    pub window_bounds: Option<WindowBounds>,
7517    pub display: Option<Uuid>,
7518    pub centered_layout: bool,
7519}
7520
7521pub fn ssh_workspace_position_from_db(
7522    host: String,
7523    port: Option<u16>,
7524    user: Option<String>,
7525    paths_to_open: &[PathBuf],
7526    cx: &App,
7527) -> Task<Result<WorkspacePosition>> {
7528    let paths = paths_to_open
7529        .iter()
7530        .map(|path| path.to_string_lossy().to_string())
7531        .collect::<Vec<_>>();
7532
7533    cx.background_spawn(async move {
7534        let serialized_ssh_project = persistence::DB
7535            .get_or_create_ssh_project(host, port, paths, user)
7536            .await
7537            .context("fetching serialized ssh project")?;
7538        let serialized_workspace =
7539            persistence::DB.workspace_for_ssh_project(&serialized_ssh_project);
7540
7541        let (window_bounds, display) = if let Some(bounds) = window_bounds_env_override() {
7542            (Some(WindowBounds::Windowed(bounds)), None)
7543        } else {
7544            let restorable_bounds = serialized_workspace
7545                .as_ref()
7546                .and_then(|workspace| Some((workspace.display?, workspace.window_bounds?)))
7547                .or_else(|| {
7548                    let (display, window_bounds) = DB.last_window().log_err()?;
7549                    Some((display?, window_bounds?))
7550                });
7551
7552            if let Some((serialized_display, serialized_status)) = restorable_bounds {
7553                (Some(serialized_status.0), Some(serialized_display))
7554            } else {
7555                (None, None)
7556            }
7557        };
7558
7559        let centered_layout = serialized_workspace
7560            .as_ref()
7561            .map(|w| w.centered_layout)
7562            .unwrap_or(false);
7563
7564        Ok(WorkspacePosition {
7565            window_bounds,
7566            display,
7567            centered_layout,
7568        })
7569    })
7570}
7571
7572#[cfg(test)]
7573mod tests {
7574    use std::{cell::RefCell, rc::Rc};
7575
7576    use super::*;
7577    use crate::{
7578        dock::{PanelEvent, test::TestPanel},
7579        item::{
7580            ItemEvent,
7581            test::{TestItem, TestProjectItem},
7582        },
7583    };
7584    use fs::FakeFs;
7585    use gpui::{
7586        DismissEvent, Empty, EventEmitter, FocusHandle, Focusable, Render, TestAppContext,
7587        UpdateGlobal, VisualTestContext, px,
7588    };
7589    use project::{Project, ProjectEntryId};
7590    use serde_json::json;
7591    use settings::SettingsStore;
7592
7593    #[gpui::test]
7594    async fn test_tab_disambiguation(cx: &mut TestAppContext) {
7595        init_test(cx);
7596
7597        let fs = FakeFs::new(cx.executor());
7598        let project = Project::test(fs, [], cx).await;
7599        let (workspace, cx) =
7600            cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
7601
7602        // Adding an item with no ambiguity renders the tab without detail.
7603        let item1 = cx.new(|cx| {
7604            let mut item = TestItem::new(cx);
7605            item.tab_descriptions = Some(vec!["c", "b1/c", "a/b1/c"]);
7606            item
7607        });
7608        workspace.update_in(cx, |workspace, window, cx| {
7609            workspace.add_item_to_active_pane(Box::new(item1.clone()), None, true, window, cx);
7610        });
7611        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(0)));
7612
7613        // Adding an item that creates ambiguity increases the level of detail on
7614        // both tabs.
7615        let item2 = cx.new_window_entity(|_window, cx| {
7616            let mut item = TestItem::new(cx);
7617            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
7618            item
7619        });
7620        workspace.update_in(cx, |workspace, window, cx| {
7621            workspace.add_item_to_active_pane(Box::new(item2.clone()), None, true, window, cx);
7622        });
7623        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
7624        item2.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
7625
7626        // Adding an item that creates ambiguity increases the level of detail only
7627        // on the ambiguous tabs. In this case, the ambiguity can't be resolved so
7628        // we stop at the highest detail available.
7629        let item3 = cx.new(|cx| {
7630            let mut item = TestItem::new(cx);
7631            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
7632            item
7633        });
7634        workspace.update_in(cx, |workspace, window, cx| {
7635            workspace.add_item_to_active_pane(Box::new(item3.clone()), None, true, window, cx);
7636        });
7637        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
7638        item2.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
7639        item3.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
7640    }
7641
7642    #[gpui::test]
7643    async fn test_tracking_active_path(cx: &mut TestAppContext) {
7644        init_test(cx);
7645
7646        let fs = FakeFs::new(cx.executor());
7647        fs.insert_tree(
7648            "/root1",
7649            json!({
7650                "one.txt": "",
7651                "two.txt": "",
7652            }),
7653        )
7654        .await;
7655        fs.insert_tree(
7656            "/root2",
7657            json!({
7658                "three.txt": "",
7659            }),
7660        )
7661        .await;
7662
7663        let project = Project::test(fs, ["root1".as_ref()], cx).await;
7664        let (workspace, cx) =
7665            cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
7666        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
7667        let worktree_id = project.update(cx, |project, cx| {
7668            project.worktrees(cx).next().unwrap().read(cx).id()
7669        });
7670
7671        let item1 = cx.new(|cx| {
7672            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "one.txt", cx)])
7673        });
7674        let item2 = cx.new(|cx| {
7675            TestItem::new(cx).with_project_items(&[TestProjectItem::new(2, "two.txt", cx)])
7676        });
7677
7678        // Add an item to an empty pane
7679        workspace.update_in(cx, |workspace, window, cx| {
7680            workspace.add_item_to_active_pane(Box::new(item1), None, true, window, cx)
7681        });
7682        project.update(cx, |project, cx| {
7683            assert_eq!(
7684                project.active_entry(),
7685                project
7686                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
7687                    .map(|e| e.id)
7688            );
7689        });
7690        assert_eq!(cx.window_title().as_deref(), Some("root1 — one.txt"));
7691
7692        // Add a second item to a non-empty pane
7693        workspace.update_in(cx, |workspace, window, cx| {
7694            workspace.add_item_to_active_pane(Box::new(item2), None, true, window, cx)
7695        });
7696        assert_eq!(cx.window_title().as_deref(), Some("root1 — two.txt"));
7697        project.update(cx, |project, cx| {
7698            assert_eq!(
7699                project.active_entry(),
7700                project
7701                    .entry_for_path(&(worktree_id, "two.txt").into(), cx)
7702                    .map(|e| e.id)
7703            );
7704        });
7705
7706        // Close the active item
7707        pane.update_in(cx, |pane, window, cx| {
7708            pane.close_active_item(&Default::default(), window, cx)
7709                .unwrap()
7710        })
7711        .await
7712        .unwrap();
7713        assert_eq!(cx.window_title().as_deref(), Some("root1 — one.txt"));
7714        project.update(cx, |project, cx| {
7715            assert_eq!(
7716                project.active_entry(),
7717                project
7718                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
7719                    .map(|e| e.id)
7720            );
7721        });
7722
7723        // Add a project folder
7724        project
7725            .update(cx, |project, cx| {
7726                project.find_or_create_worktree("root2", true, cx)
7727            })
7728            .await
7729            .unwrap();
7730        assert_eq!(cx.window_title().as_deref(), Some("root1, root2 — one.txt"));
7731
7732        // Remove a project folder
7733        project.update(cx, |project, cx| project.remove_worktree(worktree_id, cx));
7734        assert_eq!(cx.window_title().as_deref(), Some("root2 — one.txt"));
7735    }
7736
7737    #[gpui::test]
7738    async fn test_close_window(cx: &mut TestAppContext) {
7739        init_test(cx);
7740
7741        let fs = FakeFs::new(cx.executor());
7742        fs.insert_tree("/root", json!({ "one": "" })).await;
7743
7744        let project = Project::test(fs, ["root".as_ref()], cx).await;
7745        let (workspace, cx) =
7746            cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
7747
7748        // When there are no dirty items, there's nothing to do.
7749        let item1 = cx.new(TestItem::new);
7750        workspace.update_in(cx, |w, window, cx| {
7751            w.add_item_to_active_pane(Box::new(item1.clone()), None, true, window, cx)
7752        });
7753        let task = workspace.update_in(cx, |w, window, cx| {
7754            w.prepare_to_close(CloseIntent::CloseWindow, window, cx)
7755        });
7756        assert!(task.await.unwrap());
7757
7758        // When there are dirty untitled items, prompt to save each one. If the user
7759        // cancels any prompt, then abort.
7760        let item2 = cx.new(|cx| TestItem::new(cx).with_dirty(true));
7761        let item3 = cx.new(|cx| {
7762            TestItem::new(cx)
7763                .with_dirty(true)
7764                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
7765        });
7766        workspace.update_in(cx, |w, window, cx| {
7767            w.add_item_to_active_pane(Box::new(item2.clone()), None, true, window, cx);
7768            w.add_item_to_active_pane(Box::new(item3.clone()), None, true, window, cx);
7769        });
7770        let task = workspace.update_in(cx, |w, window, cx| {
7771            w.prepare_to_close(CloseIntent::CloseWindow, window, cx)
7772        });
7773        cx.executor().run_until_parked();
7774        cx.simulate_prompt_answer("Cancel"); // cancel save all
7775        cx.executor().run_until_parked();
7776        assert!(!cx.has_pending_prompt());
7777        assert!(!task.await.unwrap());
7778    }
7779
7780    #[gpui::test]
7781    async fn test_close_window_with_serializable_items(cx: &mut TestAppContext) {
7782        init_test(cx);
7783
7784        // Register TestItem as a serializable item
7785        cx.update(|cx| {
7786            register_serializable_item::<TestItem>(cx);
7787        });
7788
7789        let fs = FakeFs::new(cx.executor());
7790        fs.insert_tree("/root", json!({ "one": "" })).await;
7791
7792        let project = Project::test(fs, ["root".as_ref()], cx).await;
7793        let (workspace, cx) =
7794            cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
7795
7796        // When there are dirty untitled items, but they can serialize, then there is no prompt.
7797        let item1 = cx.new(|cx| {
7798            TestItem::new(cx)
7799                .with_dirty(true)
7800                .with_serialize(|| Some(Task::ready(Ok(()))))
7801        });
7802        let item2 = cx.new(|cx| {
7803            TestItem::new(cx)
7804                .with_dirty(true)
7805                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
7806                .with_serialize(|| Some(Task::ready(Ok(()))))
7807        });
7808        workspace.update_in(cx, |w, window, cx| {
7809            w.add_item_to_active_pane(Box::new(item1.clone()), None, true, window, cx);
7810            w.add_item_to_active_pane(Box::new(item2.clone()), None, true, window, cx);
7811        });
7812        let task = workspace.update_in(cx, |w, window, cx| {
7813            w.prepare_to_close(CloseIntent::CloseWindow, window, cx)
7814        });
7815        assert!(task.await.unwrap());
7816    }
7817
7818    #[gpui::test]
7819    async fn test_close_pane_items(cx: &mut TestAppContext) {
7820        init_test(cx);
7821
7822        let fs = FakeFs::new(cx.executor());
7823
7824        let project = Project::test(fs, None, cx).await;
7825        let (workspace, cx) =
7826            cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
7827
7828        let item1 = cx.new(|cx| {
7829            TestItem::new(cx)
7830                .with_dirty(true)
7831                .with_project_items(&[dirty_project_item(1, "1.txt", cx)])
7832        });
7833        let item2 = cx.new(|cx| {
7834            TestItem::new(cx)
7835                .with_dirty(true)
7836                .with_conflict(true)
7837                .with_project_items(&[dirty_project_item(2, "2.txt", cx)])
7838        });
7839        let item3 = cx.new(|cx| {
7840            TestItem::new(cx)
7841                .with_dirty(true)
7842                .with_conflict(true)
7843                .with_project_items(&[dirty_project_item(3, "3.txt", cx)])
7844        });
7845        let item4 = cx.new(|cx| {
7846            TestItem::new(cx).with_dirty(true).with_project_items(&[{
7847                let project_item = TestProjectItem::new_untitled(cx);
7848                project_item.update(cx, |project_item, _| project_item.is_dirty = true);
7849                project_item
7850            }])
7851        });
7852        let pane = workspace.update_in(cx, |workspace, window, cx| {
7853            workspace.add_item_to_active_pane(Box::new(item1.clone()), None, true, window, cx);
7854            workspace.add_item_to_active_pane(Box::new(item2.clone()), None, true, window, cx);
7855            workspace.add_item_to_active_pane(Box::new(item3.clone()), None, true, window, cx);
7856            workspace.add_item_to_active_pane(Box::new(item4.clone()), None, true, window, cx);
7857            workspace.active_pane().clone()
7858        });
7859
7860        let close_items = pane.update_in(cx, |pane, window, cx| {
7861            pane.activate_item(1, true, true, window, cx);
7862            assert_eq!(pane.active_item().unwrap().item_id(), item2.item_id());
7863            let item1_id = item1.item_id();
7864            let item3_id = item3.item_id();
7865            let item4_id = item4.item_id();
7866            pane.close_items(window, cx, SaveIntent::Close, move |id| {
7867                [item1_id, item3_id, item4_id].contains(&id)
7868            })
7869        });
7870        cx.executor().run_until_parked();
7871
7872        assert!(cx.has_pending_prompt());
7873        cx.simulate_prompt_answer("Save all");
7874
7875        cx.executor().run_until_parked();
7876
7877        // Item 1 is saved. There's a prompt to save item 3.
7878        pane.update(cx, |pane, cx| {
7879            assert_eq!(item1.read(cx).save_count, 1);
7880            assert_eq!(item1.read(cx).save_as_count, 0);
7881            assert_eq!(item1.read(cx).reload_count, 0);
7882            assert_eq!(pane.items_len(), 3);
7883            assert_eq!(pane.active_item().unwrap().item_id(), item3.item_id());
7884        });
7885        assert!(cx.has_pending_prompt());
7886
7887        // Cancel saving item 3.
7888        cx.simulate_prompt_answer("Discard");
7889        cx.executor().run_until_parked();
7890
7891        // Item 3 is reloaded. There's a prompt to save item 4.
7892        pane.update(cx, |pane, cx| {
7893            assert_eq!(item3.read(cx).save_count, 0);
7894            assert_eq!(item3.read(cx).save_as_count, 0);
7895            assert_eq!(item3.read(cx).reload_count, 1);
7896            assert_eq!(pane.items_len(), 2);
7897            assert_eq!(pane.active_item().unwrap().item_id(), item4.item_id());
7898        });
7899
7900        // There's a prompt for a path for item 4.
7901        cx.simulate_new_path_selection(|_| Some(Default::default()));
7902        close_items.await.unwrap();
7903
7904        // The requested items are closed.
7905        pane.update(cx, |pane, cx| {
7906            assert_eq!(item4.read(cx).save_count, 0);
7907            assert_eq!(item4.read(cx).save_as_count, 1);
7908            assert_eq!(item4.read(cx).reload_count, 0);
7909            assert_eq!(pane.items_len(), 1);
7910            assert_eq!(pane.active_item().unwrap().item_id(), item2.item_id());
7911        });
7912    }
7913
7914    #[gpui::test]
7915    async fn test_prompting_to_save_only_on_last_item_for_entry(cx: &mut TestAppContext) {
7916        init_test(cx);
7917
7918        let fs = FakeFs::new(cx.executor());
7919        let project = Project::test(fs, [], cx).await;
7920        let (workspace, cx) =
7921            cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
7922
7923        // Create several workspace items with single project entries, and two
7924        // workspace items with multiple project entries.
7925        let single_entry_items = (0..=4)
7926            .map(|project_entry_id| {
7927                cx.new(|cx| {
7928                    TestItem::new(cx)
7929                        .with_dirty(true)
7930                        .with_project_items(&[dirty_project_item(
7931                            project_entry_id,
7932                            &format!("{project_entry_id}.txt"),
7933                            cx,
7934                        )])
7935                })
7936            })
7937            .collect::<Vec<_>>();
7938        let item_2_3 = cx.new(|cx| {
7939            TestItem::new(cx)
7940                .with_dirty(true)
7941                .with_singleton(false)
7942                .with_project_items(&[
7943                    single_entry_items[2].read(cx).project_items[0].clone(),
7944                    single_entry_items[3].read(cx).project_items[0].clone(),
7945                ])
7946        });
7947        let item_3_4 = cx.new(|cx| {
7948            TestItem::new(cx)
7949                .with_dirty(true)
7950                .with_singleton(false)
7951                .with_project_items(&[
7952                    single_entry_items[3].read(cx).project_items[0].clone(),
7953                    single_entry_items[4].read(cx).project_items[0].clone(),
7954                ])
7955        });
7956
7957        // Create two panes that contain the following project entries:
7958        //   left pane:
7959        //     multi-entry items:   (2, 3)
7960        //     single-entry items:  0, 2, 3, 4
7961        //   right pane:
7962        //     single-entry items:  4, 1
7963        //     multi-entry items:   (3, 4)
7964        let (left_pane, right_pane) = workspace.update_in(cx, |workspace, window, cx| {
7965            let left_pane = workspace.active_pane().clone();
7966            workspace.add_item_to_active_pane(Box::new(item_2_3.clone()), None, true, window, cx);
7967            workspace.add_item_to_active_pane(
7968                single_entry_items[0].boxed_clone(),
7969                None,
7970                true,
7971                window,
7972                cx,
7973            );
7974            workspace.add_item_to_active_pane(
7975                single_entry_items[2].boxed_clone(),
7976                None,
7977                true,
7978                window,
7979                cx,
7980            );
7981            workspace.add_item_to_active_pane(
7982                single_entry_items[3].boxed_clone(),
7983                None,
7984                true,
7985                window,
7986                cx,
7987            );
7988            workspace.add_item_to_active_pane(
7989                single_entry_items[4].boxed_clone(),
7990                None,
7991                true,
7992                window,
7993                cx,
7994            );
7995
7996            let right_pane = workspace
7997                .split_and_clone(left_pane.clone(), SplitDirection::Right, window, cx)
7998                .unwrap();
7999
8000            right_pane.update(cx, |pane, cx| {
8001                pane.add_item(
8002                    single_entry_items[1].boxed_clone(),
8003                    true,
8004                    true,
8005                    None,
8006                    window,
8007                    cx,
8008                );
8009                pane.add_item(Box::new(item_3_4.clone()), true, true, None, window, cx);
8010            });
8011
8012            (left_pane, right_pane)
8013        });
8014
8015        cx.focus(&right_pane);
8016
8017        let mut close = right_pane.update_in(cx, |pane, window, cx| {
8018            pane.close_all_items(&CloseAllItems::default(), window, cx)
8019                .unwrap()
8020        });
8021        cx.executor().run_until_parked();
8022
8023        let msg = cx.pending_prompt().unwrap().0;
8024        assert!(msg.contains("1.txt"));
8025        assert!(!msg.contains("2.txt"));
8026        assert!(!msg.contains("3.txt"));
8027        assert!(!msg.contains("4.txt"));
8028
8029        cx.simulate_prompt_answer("Cancel");
8030        close.await.unwrap();
8031
8032        left_pane
8033            .update_in(cx, |left_pane, window, cx| {
8034                left_pane.close_item_by_id(
8035                    single_entry_items[3].entity_id(),
8036                    SaveIntent::Skip,
8037                    window,
8038                    cx,
8039                )
8040            })
8041            .await
8042            .unwrap();
8043
8044        close = right_pane.update_in(cx, |pane, window, cx| {
8045            pane.close_all_items(&CloseAllItems::default(), window, cx)
8046                .unwrap()
8047        });
8048        cx.executor().run_until_parked();
8049
8050        let details = cx.pending_prompt().unwrap().1;
8051        assert!(details.contains("1.txt"));
8052        assert!(!details.contains("2.txt"));
8053        assert!(details.contains("3.txt"));
8054        // ideally this assertion could be made, but today we can only
8055        // save whole items not project items, so the orphaned item 3 causes
8056        // 4 to be saved too.
8057        // assert!(!details.contains("4.txt"));
8058
8059        cx.simulate_prompt_answer("Save all");
8060
8061        cx.executor().run_until_parked();
8062        close.await.unwrap();
8063        right_pane.update(cx, |pane, _| {
8064            assert_eq!(pane.items_len(), 0);
8065        });
8066    }
8067
8068    #[gpui::test]
8069    async fn test_autosave(cx: &mut gpui::TestAppContext) {
8070        init_test(cx);
8071
8072        let fs = FakeFs::new(cx.executor());
8073        let project = Project::test(fs, [], cx).await;
8074        let (workspace, cx) =
8075            cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
8076        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
8077
8078        let item = cx.new(|cx| {
8079            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
8080        });
8081        let item_id = item.entity_id();
8082        workspace.update_in(cx, |workspace, window, cx| {
8083            workspace.add_item_to_active_pane(Box::new(item.clone()), None, true, window, cx);
8084        });
8085
8086        // Autosave on window change.
8087        item.update(cx, |item, cx| {
8088            SettingsStore::update_global(cx, |settings, cx| {
8089                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
8090                    settings.autosave = Some(AutosaveSetting::OnWindowChange);
8091                })
8092            });
8093            item.is_dirty = true;
8094        });
8095
8096        // Deactivating the window saves the file.
8097        cx.deactivate_window();
8098        item.update(cx, |item, _| assert_eq!(item.save_count, 1));
8099
8100        // Re-activating the window doesn't save the file.
8101        cx.update(|window, _| window.activate_window());
8102        cx.executor().run_until_parked();
8103        item.update(cx, |item, _| assert_eq!(item.save_count, 1));
8104
8105        // Autosave on focus change.
8106        item.update_in(cx, |item, window, cx| {
8107            cx.focus_self(window);
8108            SettingsStore::update_global(cx, |settings, cx| {
8109                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
8110                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
8111                })
8112            });
8113            item.is_dirty = true;
8114        });
8115
8116        // Blurring the item saves the file.
8117        item.update_in(cx, |_, window, _| window.blur());
8118        cx.executor().run_until_parked();
8119        item.update(cx, |item, _| assert_eq!(item.save_count, 2));
8120
8121        // Deactivating the window still saves the file.
8122        item.update_in(cx, |item, window, cx| {
8123            cx.focus_self(window);
8124            item.is_dirty = true;
8125        });
8126        cx.deactivate_window();
8127        item.update(cx, |item, _| assert_eq!(item.save_count, 3));
8128
8129        // Autosave after delay.
8130        item.update(cx, |item, cx| {
8131            SettingsStore::update_global(cx, |settings, cx| {
8132                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
8133                    settings.autosave = Some(AutosaveSetting::AfterDelay { milliseconds: 500 });
8134                })
8135            });
8136            item.is_dirty = true;
8137            cx.emit(ItemEvent::Edit);
8138        });
8139
8140        // Delay hasn't fully expired, so the file is still dirty and unsaved.
8141        cx.executor().advance_clock(Duration::from_millis(250));
8142        item.update(cx, |item, _| assert_eq!(item.save_count, 3));
8143
8144        // After delay expires, the file is saved.
8145        cx.executor().advance_clock(Duration::from_millis(250));
8146        item.update(cx, |item, _| assert_eq!(item.save_count, 4));
8147
8148        // Autosave on focus change, ensuring closing the tab counts as such.
8149        item.update(cx, |item, cx| {
8150            SettingsStore::update_global(cx, |settings, cx| {
8151                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
8152                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
8153                })
8154            });
8155            item.is_dirty = true;
8156            for project_item in &mut item.project_items {
8157                project_item.update(cx, |project_item, _| project_item.is_dirty = true);
8158            }
8159        });
8160
8161        pane.update_in(cx, |pane, window, cx| {
8162            pane.close_items(window, cx, SaveIntent::Close, move |id| id == item_id)
8163        })
8164        .await
8165        .unwrap();
8166        assert!(!cx.has_pending_prompt());
8167        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
8168
8169        // Add the item again, ensuring autosave is prevented if the underlying file has been deleted.
8170        workspace.update_in(cx, |workspace, window, cx| {
8171            workspace.add_item_to_active_pane(Box::new(item.clone()), None, true, window, cx);
8172        });
8173        item.update_in(cx, |item, window, cx| {
8174            item.project_items[0].update(cx, |item, _| {
8175                item.entry_id = None;
8176            });
8177            item.is_dirty = true;
8178            window.blur();
8179        });
8180        cx.run_until_parked();
8181        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
8182
8183        // Ensure autosave is prevented for deleted files also when closing the buffer.
8184        let _close_items = pane.update_in(cx, |pane, window, cx| {
8185            pane.close_items(window, cx, SaveIntent::Close, move |id| id == item_id)
8186        });
8187        cx.run_until_parked();
8188        assert!(cx.has_pending_prompt());
8189        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
8190    }
8191
8192    #[gpui::test]
8193    async fn test_pane_navigation(cx: &mut gpui::TestAppContext) {
8194        init_test(cx);
8195
8196        let fs = FakeFs::new(cx.executor());
8197
8198        let project = Project::test(fs, [], cx).await;
8199        let (workspace, cx) =
8200            cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
8201
8202        let item = cx.new(|cx| {
8203            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
8204        });
8205        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
8206        let toolbar = pane.update(cx, |pane, _| pane.toolbar().clone());
8207        let toolbar_notify_count = Rc::new(RefCell::new(0));
8208
8209        workspace.update_in(cx, |workspace, window, cx| {
8210            workspace.add_item_to_active_pane(Box::new(item.clone()), None, true, window, cx);
8211            let toolbar_notification_count = toolbar_notify_count.clone();
8212            cx.observe_in(&toolbar, window, move |_, _, _, _| {
8213                *toolbar_notification_count.borrow_mut() += 1
8214            })
8215            .detach();
8216        });
8217
8218        pane.update(cx, |pane, _| {
8219            assert!(!pane.can_navigate_backward());
8220            assert!(!pane.can_navigate_forward());
8221        });
8222
8223        item.update_in(cx, |item, _, cx| {
8224            item.set_state("one".to_string(), cx);
8225        });
8226
8227        // Toolbar must be notified to re-render the navigation buttons
8228        assert_eq!(*toolbar_notify_count.borrow(), 1);
8229
8230        pane.update(cx, |pane, _| {
8231            assert!(pane.can_navigate_backward());
8232            assert!(!pane.can_navigate_forward());
8233        });
8234
8235        workspace
8236            .update_in(cx, |workspace, window, cx| {
8237                workspace.go_back(pane.downgrade(), window, cx)
8238            })
8239            .await
8240            .unwrap();
8241
8242        assert_eq!(*toolbar_notify_count.borrow(), 2);
8243        pane.update(cx, |pane, _| {
8244            assert!(!pane.can_navigate_backward());
8245            assert!(pane.can_navigate_forward());
8246        });
8247    }
8248
8249    #[gpui::test]
8250    async fn test_toggle_docks_and_panels(cx: &mut gpui::TestAppContext) {
8251        init_test(cx);
8252        let fs = FakeFs::new(cx.executor());
8253
8254        let project = Project::test(fs, [], cx).await;
8255        let (workspace, cx) =
8256            cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
8257
8258        let panel = workspace.update_in(cx, |workspace, window, cx| {
8259            let panel = cx.new(|cx| TestPanel::new(DockPosition::Right, cx));
8260            workspace.add_panel(panel.clone(), window, cx);
8261
8262            workspace
8263                .right_dock()
8264                .update(cx, |right_dock, cx| right_dock.set_open(true, window, cx));
8265
8266            panel
8267        });
8268
8269        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
8270        pane.update_in(cx, |pane, window, cx| {
8271            let item = cx.new(TestItem::new);
8272            pane.add_item(Box::new(item), true, true, None, window, cx);
8273        });
8274
8275        // Transfer focus from center to panel
8276        workspace.update_in(cx, |workspace, window, cx| {
8277            workspace.toggle_panel_focus::<TestPanel>(window, cx);
8278        });
8279
8280        workspace.update_in(cx, |workspace, window, cx| {
8281            assert!(workspace.right_dock().read(cx).is_open());
8282            assert!(!panel.is_zoomed(window, cx));
8283            assert!(panel.read(cx).focus_handle(cx).contains_focused(window, cx));
8284        });
8285
8286        // Transfer focus from panel to center
8287        workspace.update_in(cx, |workspace, window, cx| {
8288            workspace.toggle_panel_focus::<TestPanel>(window, cx);
8289        });
8290
8291        workspace.update_in(cx, |workspace, window, cx| {
8292            assert!(workspace.right_dock().read(cx).is_open());
8293            assert!(!panel.is_zoomed(window, cx));
8294            assert!(!panel.read(cx).focus_handle(cx).contains_focused(window, cx));
8295        });
8296
8297        // Close the dock
8298        workspace.update_in(cx, |workspace, window, cx| {
8299            workspace.toggle_dock(DockPosition::Right, window, cx);
8300        });
8301
8302        workspace.update_in(cx, |workspace, window, cx| {
8303            assert!(!workspace.right_dock().read(cx).is_open());
8304            assert!(!panel.is_zoomed(window, cx));
8305            assert!(!panel.read(cx).focus_handle(cx).contains_focused(window, cx));
8306        });
8307
8308        // Open the dock
8309        workspace.update_in(cx, |workspace, window, cx| {
8310            workspace.toggle_dock(DockPosition::Right, window, cx);
8311        });
8312
8313        workspace.update_in(cx, |workspace, window, cx| {
8314            assert!(workspace.right_dock().read(cx).is_open());
8315            assert!(!panel.is_zoomed(window, cx));
8316            assert!(panel.read(cx).focus_handle(cx).contains_focused(window, cx));
8317        });
8318
8319        // Focus and zoom panel
8320        panel.update_in(cx, |panel, window, cx| {
8321            cx.focus_self(window);
8322            panel.set_zoomed(true, window, cx)
8323        });
8324
8325        workspace.update_in(cx, |workspace, window, cx| {
8326            assert!(workspace.right_dock().read(cx).is_open());
8327            assert!(panel.is_zoomed(window, cx));
8328            assert!(panel.read(cx).focus_handle(cx).contains_focused(window, cx));
8329        });
8330
8331        // Transfer focus to the center closes the dock
8332        workspace.update_in(cx, |workspace, window, cx| {
8333            workspace.toggle_panel_focus::<TestPanel>(window, cx);
8334        });
8335
8336        workspace.update_in(cx, |workspace, window, cx| {
8337            assert!(!workspace.right_dock().read(cx).is_open());
8338            assert!(panel.is_zoomed(window, cx));
8339            assert!(!panel.read(cx).focus_handle(cx).contains_focused(window, cx));
8340        });
8341
8342        // Transferring focus back to the panel keeps it zoomed
8343        workspace.update_in(cx, |workspace, window, cx| {
8344            workspace.toggle_panel_focus::<TestPanel>(window, cx);
8345        });
8346
8347        workspace.update_in(cx, |workspace, window, cx| {
8348            assert!(workspace.right_dock().read(cx).is_open());
8349            assert!(panel.is_zoomed(window, cx));
8350            assert!(panel.read(cx).focus_handle(cx).contains_focused(window, cx));
8351        });
8352
8353        // Close the dock while it is zoomed
8354        workspace.update_in(cx, |workspace, window, cx| {
8355            workspace.toggle_dock(DockPosition::Right, window, cx)
8356        });
8357
8358        workspace.update_in(cx, |workspace, window, cx| {
8359            assert!(!workspace.right_dock().read(cx).is_open());
8360            assert!(panel.is_zoomed(window, cx));
8361            assert!(workspace.zoomed.is_none());
8362            assert!(!panel.read(cx).focus_handle(cx).contains_focused(window, cx));
8363        });
8364
8365        // Opening the dock, when it's zoomed, retains focus
8366        workspace.update_in(cx, |workspace, window, cx| {
8367            workspace.toggle_dock(DockPosition::Right, window, cx)
8368        });
8369
8370        workspace.update_in(cx, |workspace, window, cx| {
8371            assert!(workspace.right_dock().read(cx).is_open());
8372            assert!(panel.is_zoomed(window, cx));
8373            assert!(workspace.zoomed.is_some());
8374            assert!(panel.read(cx).focus_handle(cx).contains_focused(window, cx));
8375        });
8376
8377        // Unzoom and close the panel, zoom the active pane.
8378        panel.update_in(cx, |panel, window, cx| panel.set_zoomed(false, window, cx));
8379        workspace.update_in(cx, |workspace, window, cx| {
8380            workspace.toggle_dock(DockPosition::Right, window, cx)
8381        });
8382        pane.update_in(cx, |pane, window, cx| {
8383            pane.toggle_zoom(&Default::default(), window, cx)
8384        });
8385
8386        // Opening a dock unzooms the pane.
8387        workspace.update_in(cx, |workspace, window, cx| {
8388            workspace.toggle_dock(DockPosition::Right, window, cx)
8389        });
8390        workspace.update_in(cx, |workspace, window, cx| {
8391            let pane = pane.read(cx);
8392            assert!(!pane.is_zoomed());
8393            assert!(!pane.focus_handle(cx).is_focused(window));
8394            assert!(workspace.right_dock().read(cx).is_open());
8395            assert!(workspace.zoomed.is_none());
8396        });
8397    }
8398
8399    #[gpui::test]
8400    async fn test_join_pane_into_next(cx: &mut gpui::TestAppContext) {
8401        init_test(cx);
8402
8403        let fs = FakeFs::new(cx.executor());
8404
8405        let project = Project::test(fs, None, cx).await;
8406        let (workspace, cx) =
8407            cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
8408
8409        // Let's arrange the panes like this:
8410        //
8411        // +-----------------------+
8412        // |         top           |
8413        // +------+--------+-------+
8414        // | left | center | right |
8415        // +------+--------+-------+
8416        // |        bottom         |
8417        // +-----------------------+
8418
8419        let top_item = cx.new(|cx| {
8420            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "top.txt", cx)])
8421        });
8422        let bottom_item = cx.new(|cx| {
8423            TestItem::new(cx).with_project_items(&[TestProjectItem::new(2, "bottom.txt", cx)])
8424        });
8425        let left_item = cx.new(|cx| {
8426            TestItem::new(cx).with_project_items(&[TestProjectItem::new(3, "left.txt", cx)])
8427        });
8428        let right_item = cx.new(|cx| {
8429            TestItem::new(cx).with_project_items(&[TestProjectItem::new(4, "right.txt", cx)])
8430        });
8431        let center_item = cx.new(|cx| {
8432            TestItem::new(cx).with_project_items(&[TestProjectItem::new(5, "center.txt", cx)])
8433        });
8434
8435        let top_pane_id = workspace.update_in(cx, |workspace, window, cx| {
8436            let top_pane_id = workspace.active_pane().entity_id();
8437            workspace.add_item_to_active_pane(Box::new(top_item.clone()), None, false, window, cx);
8438            workspace.split_pane(
8439                workspace.active_pane().clone(),
8440                SplitDirection::Down,
8441                window,
8442                cx,
8443            );
8444            top_pane_id
8445        });
8446        let bottom_pane_id = workspace.update_in(cx, |workspace, window, cx| {
8447            let bottom_pane_id = workspace.active_pane().entity_id();
8448            workspace.add_item_to_active_pane(
8449                Box::new(bottom_item.clone()),
8450                None,
8451                false,
8452                window,
8453                cx,
8454            );
8455            workspace.split_pane(
8456                workspace.active_pane().clone(),
8457                SplitDirection::Up,
8458                window,
8459                cx,
8460            );
8461            bottom_pane_id
8462        });
8463        let left_pane_id = workspace.update_in(cx, |workspace, window, cx| {
8464            let left_pane_id = workspace.active_pane().entity_id();
8465            workspace.add_item_to_active_pane(Box::new(left_item.clone()), None, false, window, cx);
8466            workspace.split_pane(
8467                workspace.active_pane().clone(),
8468                SplitDirection::Right,
8469                window,
8470                cx,
8471            );
8472            left_pane_id
8473        });
8474        let right_pane_id = workspace.update_in(cx, |workspace, window, cx| {
8475            let right_pane_id = workspace.active_pane().entity_id();
8476            workspace.add_item_to_active_pane(
8477                Box::new(right_item.clone()),
8478                None,
8479                false,
8480                window,
8481                cx,
8482            );
8483            workspace.split_pane(
8484                workspace.active_pane().clone(),
8485                SplitDirection::Left,
8486                window,
8487                cx,
8488            );
8489            right_pane_id
8490        });
8491        let center_pane_id = workspace.update_in(cx, |workspace, window, cx| {
8492            let center_pane_id = workspace.active_pane().entity_id();
8493            workspace.add_item_to_active_pane(
8494                Box::new(center_item.clone()),
8495                None,
8496                false,
8497                window,
8498                cx,
8499            );
8500            center_pane_id
8501        });
8502        cx.executor().run_until_parked();
8503
8504        workspace.update_in(cx, |workspace, window, cx| {
8505            assert_eq!(center_pane_id, workspace.active_pane().entity_id());
8506
8507            // Join into next from center pane into right
8508            workspace.join_pane_into_next(workspace.active_pane().clone(), window, cx);
8509        });
8510
8511        workspace.update_in(cx, |workspace, window, cx| {
8512            let active_pane = workspace.active_pane();
8513            assert_eq!(right_pane_id, active_pane.entity_id());
8514            assert_eq!(2, active_pane.read(cx).items_len());
8515            let item_ids_in_pane =
8516                HashSet::from_iter(active_pane.read(cx).items().map(|item| item.item_id()));
8517            assert!(item_ids_in_pane.contains(&center_item.item_id()));
8518            assert!(item_ids_in_pane.contains(&right_item.item_id()));
8519
8520            // Join into next from right pane into bottom
8521            workspace.join_pane_into_next(workspace.active_pane().clone(), window, cx);
8522        });
8523
8524        workspace.update_in(cx, |workspace, window, cx| {
8525            let active_pane = workspace.active_pane();
8526            assert_eq!(bottom_pane_id, active_pane.entity_id());
8527            assert_eq!(3, active_pane.read(cx).items_len());
8528            let item_ids_in_pane =
8529                HashSet::from_iter(active_pane.read(cx).items().map(|item| item.item_id()));
8530            assert!(item_ids_in_pane.contains(&center_item.item_id()));
8531            assert!(item_ids_in_pane.contains(&right_item.item_id()));
8532            assert!(item_ids_in_pane.contains(&bottom_item.item_id()));
8533
8534            // Join into next from bottom pane into left
8535            workspace.join_pane_into_next(workspace.active_pane().clone(), window, cx);
8536        });
8537
8538        workspace.update_in(cx, |workspace, window, cx| {
8539            let active_pane = workspace.active_pane();
8540            assert_eq!(left_pane_id, active_pane.entity_id());
8541            assert_eq!(4, active_pane.read(cx).items_len());
8542            let item_ids_in_pane =
8543                HashSet::from_iter(active_pane.read(cx).items().map(|item| item.item_id()));
8544            assert!(item_ids_in_pane.contains(&center_item.item_id()));
8545            assert!(item_ids_in_pane.contains(&right_item.item_id()));
8546            assert!(item_ids_in_pane.contains(&bottom_item.item_id()));
8547            assert!(item_ids_in_pane.contains(&left_item.item_id()));
8548
8549            // Join into next from left pane into top
8550            workspace.join_pane_into_next(workspace.active_pane().clone(), window, cx);
8551        });
8552
8553        workspace.update_in(cx, |workspace, window, cx| {
8554            let active_pane = workspace.active_pane();
8555            assert_eq!(top_pane_id, active_pane.entity_id());
8556            assert_eq!(5, active_pane.read(cx).items_len());
8557            let item_ids_in_pane =
8558                HashSet::from_iter(active_pane.read(cx).items().map(|item| item.item_id()));
8559            assert!(item_ids_in_pane.contains(&center_item.item_id()));
8560            assert!(item_ids_in_pane.contains(&right_item.item_id()));
8561            assert!(item_ids_in_pane.contains(&bottom_item.item_id()));
8562            assert!(item_ids_in_pane.contains(&left_item.item_id()));
8563            assert!(item_ids_in_pane.contains(&top_item.item_id()));
8564
8565            // Single pane left: no-op
8566            workspace.join_pane_into_next(workspace.active_pane().clone(), window, cx)
8567        });
8568
8569        workspace.update(cx, |workspace, _cx| {
8570            let active_pane = workspace.active_pane();
8571            assert_eq!(top_pane_id, active_pane.entity_id());
8572        });
8573    }
8574
8575    fn add_an_item_to_active_pane(
8576        cx: &mut VisualTestContext,
8577        workspace: &Entity<Workspace>,
8578        item_id: u64,
8579    ) -> Entity<TestItem> {
8580        let item = cx.new(|cx| {
8581            TestItem::new(cx).with_project_items(&[TestProjectItem::new(
8582                item_id,
8583                "item{item_id}.txt",
8584                cx,
8585            )])
8586        });
8587        workspace.update_in(cx, |workspace, window, cx| {
8588            workspace.add_item_to_active_pane(Box::new(item.clone()), None, false, window, cx);
8589        });
8590        return item;
8591    }
8592
8593    fn split_pane(cx: &mut VisualTestContext, workspace: &Entity<Workspace>) -> Entity<Pane> {
8594        return workspace.update_in(cx, |workspace, window, cx| {
8595            let new_pane = workspace.split_pane(
8596                workspace.active_pane().clone(),
8597                SplitDirection::Right,
8598                window,
8599                cx,
8600            );
8601            new_pane
8602        });
8603    }
8604
8605    #[gpui::test]
8606    async fn test_join_all_panes(cx: &mut gpui::TestAppContext) {
8607        init_test(cx);
8608        let fs = FakeFs::new(cx.executor());
8609        let project = Project::test(fs, None, cx).await;
8610        let (workspace, cx) =
8611            cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
8612
8613        add_an_item_to_active_pane(cx, &workspace, 1);
8614        split_pane(cx, &workspace);
8615        add_an_item_to_active_pane(cx, &workspace, 2);
8616        split_pane(cx, &workspace); // empty pane
8617        split_pane(cx, &workspace);
8618        let last_item = add_an_item_to_active_pane(cx, &workspace, 3);
8619
8620        cx.executor().run_until_parked();
8621
8622        workspace.update(cx, |workspace, cx| {
8623            let num_panes = workspace.panes().len();
8624            let num_items_in_current_pane = workspace.active_pane().read(cx).items().count();
8625            let active_item = workspace
8626                .active_pane()
8627                .read(cx)
8628                .active_item()
8629                .expect("item is in focus");
8630
8631            assert_eq!(num_panes, 4);
8632            assert_eq!(num_items_in_current_pane, 1);
8633            assert_eq!(active_item.item_id(), last_item.item_id());
8634        });
8635
8636        workspace.update_in(cx, |workspace, window, cx| {
8637            workspace.join_all_panes(window, cx);
8638        });
8639
8640        workspace.update(cx, |workspace, cx| {
8641            let num_panes = workspace.panes().len();
8642            let num_items_in_current_pane = workspace.active_pane().read(cx).items().count();
8643            let active_item = workspace
8644                .active_pane()
8645                .read(cx)
8646                .active_item()
8647                .expect("item is in focus");
8648
8649            assert_eq!(num_panes, 1);
8650            assert_eq!(num_items_in_current_pane, 3);
8651            assert_eq!(active_item.item_id(), last_item.item_id());
8652        });
8653    }
8654    struct TestModal(FocusHandle);
8655
8656    impl TestModal {
8657        fn new(_: &mut Window, cx: &mut Context<Self>) -> Self {
8658            Self(cx.focus_handle())
8659        }
8660    }
8661
8662    impl EventEmitter<DismissEvent> for TestModal {}
8663
8664    impl Focusable for TestModal {
8665        fn focus_handle(&self, _cx: &App) -> FocusHandle {
8666            self.0.clone()
8667        }
8668    }
8669
8670    impl ModalView for TestModal {}
8671
8672    impl Render for TestModal {
8673        fn render(
8674            &mut self,
8675            _window: &mut Window,
8676            _cx: &mut Context<TestModal>,
8677        ) -> impl IntoElement {
8678            div().track_focus(&self.0)
8679        }
8680    }
8681
8682    #[gpui::test]
8683    async fn test_panels(cx: &mut gpui::TestAppContext) {
8684        init_test(cx);
8685        let fs = FakeFs::new(cx.executor());
8686
8687        let project = Project::test(fs, [], cx).await;
8688        let (workspace, cx) =
8689            cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
8690
8691        let (panel_1, panel_2) = workspace.update_in(cx, |workspace, window, cx| {
8692            let panel_1 = cx.new(|cx| TestPanel::new(DockPosition::Left, cx));
8693            workspace.add_panel(panel_1.clone(), window, cx);
8694            workspace.toggle_dock(DockPosition::Left, window, cx);
8695            let panel_2 = cx.new(|cx| TestPanel::new(DockPosition::Right, cx));
8696            workspace.add_panel(panel_2.clone(), window, cx);
8697            workspace.toggle_dock(DockPosition::Right, window, cx);
8698
8699            let left_dock = workspace.left_dock();
8700            assert_eq!(
8701                left_dock.read(cx).visible_panel().unwrap().panel_id(),
8702                panel_1.panel_id()
8703            );
8704            assert_eq!(
8705                left_dock.read(cx).active_panel_size(window, cx).unwrap(),
8706                panel_1.size(window, cx)
8707            );
8708
8709            left_dock.update(cx, |left_dock, cx| {
8710                left_dock.resize_active_panel(Some(px(1337.)), window, cx)
8711            });
8712            assert_eq!(
8713                workspace
8714                    .right_dock()
8715                    .read(cx)
8716                    .visible_panel()
8717                    .unwrap()
8718                    .panel_id(),
8719                panel_2.panel_id(),
8720            );
8721
8722            (panel_1, panel_2)
8723        });
8724
8725        // Move panel_1 to the right
8726        panel_1.update_in(cx, |panel_1, window, cx| {
8727            panel_1.set_position(DockPosition::Right, window, cx)
8728        });
8729
8730        workspace.update_in(cx, |workspace, window, cx| {
8731            // Since panel_1 was visible on the left, it should now be visible now that it's been moved to the right.
8732            // Since it was the only panel on the left, the left dock should now be closed.
8733            assert!(!workspace.left_dock().read(cx).is_open());
8734            assert!(workspace.left_dock().read(cx).visible_panel().is_none());
8735            let right_dock = workspace.right_dock();
8736            assert_eq!(
8737                right_dock.read(cx).visible_panel().unwrap().panel_id(),
8738                panel_1.panel_id()
8739            );
8740            assert_eq!(
8741                right_dock.read(cx).active_panel_size(window, cx).unwrap(),
8742                px(1337.)
8743            );
8744
8745            // Now we move panel_2 to the left
8746            panel_2.set_position(DockPosition::Left, window, cx);
8747        });
8748
8749        workspace.update(cx, |workspace, cx| {
8750            // Since panel_2 was not visible on the right, we don't open the left dock.
8751            assert!(!workspace.left_dock().read(cx).is_open());
8752            // And the right dock is unaffected in its displaying of panel_1
8753            assert!(workspace.right_dock().read(cx).is_open());
8754            assert_eq!(
8755                workspace
8756                    .right_dock()
8757                    .read(cx)
8758                    .visible_panel()
8759                    .unwrap()
8760                    .panel_id(),
8761                panel_1.panel_id(),
8762            );
8763        });
8764
8765        // Move panel_1 back to the left
8766        panel_1.update_in(cx, |panel_1, window, cx| {
8767            panel_1.set_position(DockPosition::Left, window, cx)
8768        });
8769
8770        workspace.update_in(cx, |workspace, window, cx| {
8771            // Since panel_1 was visible on the right, we open the left dock and make panel_1 active.
8772            let left_dock = workspace.left_dock();
8773            assert!(left_dock.read(cx).is_open());
8774            assert_eq!(
8775                left_dock.read(cx).visible_panel().unwrap().panel_id(),
8776                panel_1.panel_id()
8777            );
8778            assert_eq!(
8779                left_dock.read(cx).active_panel_size(window, cx).unwrap(),
8780                px(1337.)
8781            );
8782            // And the right dock should be closed as it no longer has any panels.
8783            assert!(!workspace.right_dock().read(cx).is_open());
8784
8785            // Now we move panel_1 to the bottom
8786            panel_1.set_position(DockPosition::Bottom, window, cx);
8787        });
8788
8789        workspace.update_in(cx, |workspace, window, cx| {
8790            // Since panel_1 was visible on the left, we close the left dock.
8791            assert!(!workspace.left_dock().read(cx).is_open());
8792            // The bottom dock is sized based on the panel's default size,
8793            // since the panel orientation changed from vertical to horizontal.
8794            let bottom_dock = workspace.bottom_dock();
8795            assert_eq!(
8796                bottom_dock.read(cx).active_panel_size(window, cx).unwrap(),
8797                panel_1.size(window, cx),
8798            );
8799            // Close bottom dock and move panel_1 back to the left.
8800            bottom_dock.update(cx, |bottom_dock, cx| {
8801                bottom_dock.set_open(false, window, cx)
8802            });
8803            panel_1.set_position(DockPosition::Left, window, cx);
8804        });
8805
8806        // Emit activated event on panel 1
8807        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::Activate));
8808
8809        // Now the left dock is open and panel_1 is active and focused.
8810        workspace.update_in(cx, |workspace, window, cx| {
8811            let left_dock = workspace.left_dock();
8812            assert!(left_dock.read(cx).is_open());
8813            assert_eq!(
8814                left_dock.read(cx).visible_panel().unwrap().panel_id(),
8815                panel_1.panel_id(),
8816            );
8817            assert!(panel_1.focus_handle(cx).is_focused(window));
8818        });
8819
8820        // Emit closed event on panel 2, which is not active
8821        panel_2.update(cx, |_, cx| cx.emit(PanelEvent::Close));
8822
8823        // Wo don't close the left dock, because panel_2 wasn't the active panel
8824        workspace.update(cx, |workspace, cx| {
8825            let left_dock = workspace.left_dock();
8826            assert!(left_dock.read(cx).is_open());
8827            assert_eq!(
8828                left_dock.read(cx).visible_panel().unwrap().panel_id(),
8829                panel_1.panel_id(),
8830            );
8831        });
8832
8833        // Emitting a ZoomIn event shows the panel as zoomed.
8834        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::ZoomIn));
8835        workspace.update(cx, |workspace, _| {
8836            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
8837            assert_eq!(workspace.zoomed_position, Some(DockPosition::Left));
8838        });
8839
8840        // Move panel to another dock while it is zoomed
8841        panel_1.update_in(cx, |panel, window, cx| {
8842            panel.set_position(DockPosition::Right, window, cx)
8843        });
8844        workspace.update(cx, |workspace, _| {
8845            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
8846
8847            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
8848        });
8849
8850        // This is a helper for getting a:
8851        // - valid focus on an element,
8852        // - that isn't a part of the panes and panels system of the Workspace,
8853        // - and doesn't trigger the 'on_focus_lost' API.
8854        let focus_other_view = {
8855            let workspace = workspace.clone();
8856            move |cx: &mut VisualTestContext| {
8857                workspace.update_in(cx, |workspace, window, cx| {
8858                    if let Some(_) = workspace.active_modal::<TestModal>(cx) {
8859                        workspace.toggle_modal(window, cx, TestModal::new);
8860                        workspace.toggle_modal(window, cx, TestModal::new);
8861                    } else {
8862                        workspace.toggle_modal(window, cx, TestModal::new);
8863                    }
8864                })
8865            }
8866        };
8867
8868        // If focus is transferred to another view that's not a panel or another pane, we still show
8869        // the panel as zoomed.
8870        focus_other_view(cx);
8871        workspace.update(cx, |workspace, _| {
8872            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
8873            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
8874        });
8875
8876        // If focus is transferred elsewhere in the workspace, the panel is no longer zoomed.
8877        workspace.update_in(cx, |_workspace, window, cx| {
8878            cx.focus_self(window);
8879        });
8880        workspace.update(cx, |workspace, _| {
8881            assert_eq!(workspace.zoomed, None);
8882            assert_eq!(workspace.zoomed_position, None);
8883        });
8884
8885        // If focus is transferred again to another view that's not a panel or a pane, we won't
8886        // show the panel as zoomed because it wasn't zoomed before.
8887        focus_other_view(cx);
8888        workspace.update(cx, |workspace, _| {
8889            assert_eq!(workspace.zoomed, None);
8890            assert_eq!(workspace.zoomed_position, None);
8891        });
8892
8893        // When the panel is activated, it is zoomed again.
8894        cx.dispatch_action(ToggleRightDock);
8895        workspace.update(cx, |workspace, _| {
8896            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
8897            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
8898        });
8899
8900        // Emitting a ZoomOut event unzooms the panel.
8901        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::ZoomOut));
8902        workspace.update(cx, |workspace, _| {
8903            assert_eq!(workspace.zoomed, None);
8904            assert_eq!(workspace.zoomed_position, None);
8905        });
8906
8907        // Emit closed event on panel 1, which is active
8908        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::Close));
8909
8910        // Now the left dock is closed, because panel_1 was the active panel
8911        workspace.update(cx, |workspace, cx| {
8912            let right_dock = workspace.right_dock();
8913            assert!(!right_dock.read(cx).is_open());
8914        });
8915    }
8916
8917    #[gpui::test]
8918    async fn test_no_save_prompt_when_multi_buffer_dirty_items_closed(cx: &mut TestAppContext) {
8919        init_test(cx);
8920
8921        let fs = FakeFs::new(cx.background_executor.clone());
8922        let project = Project::test(fs, [], cx).await;
8923        let (workspace, cx) =
8924            cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
8925        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
8926
8927        let dirty_regular_buffer = cx.new(|cx| {
8928            TestItem::new(cx)
8929                .with_dirty(true)
8930                .with_label("1.txt")
8931                .with_project_items(&[dirty_project_item(1, "1.txt", cx)])
8932        });
8933        let dirty_regular_buffer_2 = cx.new(|cx| {
8934            TestItem::new(cx)
8935                .with_dirty(true)
8936                .with_label("2.txt")
8937                .with_project_items(&[dirty_project_item(2, "2.txt", cx)])
8938        });
8939        let dirty_multi_buffer_with_both = cx.new(|cx| {
8940            TestItem::new(cx)
8941                .with_dirty(true)
8942                .with_singleton(false)
8943                .with_label("Fake Project Search")
8944                .with_project_items(&[
8945                    dirty_regular_buffer.read(cx).project_items[0].clone(),
8946                    dirty_regular_buffer_2.read(cx).project_items[0].clone(),
8947                ])
8948        });
8949        let multi_buffer_with_both_files_id = dirty_multi_buffer_with_both.item_id();
8950        workspace.update_in(cx, |workspace, window, cx| {
8951            workspace.add_item(
8952                pane.clone(),
8953                Box::new(dirty_regular_buffer.clone()),
8954                None,
8955                false,
8956                false,
8957                window,
8958                cx,
8959            );
8960            workspace.add_item(
8961                pane.clone(),
8962                Box::new(dirty_regular_buffer_2.clone()),
8963                None,
8964                false,
8965                false,
8966                window,
8967                cx,
8968            );
8969            workspace.add_item(
8970                pane.clone(),
8971                Box::new(dirty_multi_buffer_with_both.clone()),
8972                None,
8973                false,
8974                false,
8975                window,
8976                cx,
8977            );
8978        });
8979
8980        pane.update_in(cx, |pane, window, cx| {
8981            pane.activate_item(2, true, true, window, cx);
8982            assert_eq!(
8983                pane.active_item().unwrap().item_id(),
8984                multi_buffer_with_both_files_id,
8985                "Should select the multi buffer in the pane"
8986            );
8987        });
8988        let close_all_but_multi_buffer_task = pane
8989            .update_in(cx, |pane, window, cx| {
8990                pane.close_inactive_items(
8991                    &CloseInactiveItems {
8992                        save_intent: Some(SaveIntent::Save),
8993                        close_pinned: true,
8994                    },
8995                    window,
8996                    cx,
8997                )
8998            })
8999            .expect("should have inactive files to close");
9000        cx.background_executor.run_until_parked();
9001        assert!(!cx.has_pending_prompt());
9002        close_all_but_multi_buffer_task
9003            .await
9004            .expect("Closing all buffers but the multi buffer failed");
9005        pane.update(cx, |pane, cx| {
9006            assert_eq!(dirty_regular_buffer.read(cx).save_count, 1);
9007            assert_eq!(dirty_multi_buffer_with_both.read(cx).save_count, 0);
9008            assert_eq!(dirty_regular_buffer_2.read(cx).save_count, 1);
9009            assert_eq!(pane.items_len(), 1);
9010            assert_eq!(
9011                pane.active_item().unwrap().item_id(),
9012                multi_buffer_with_both_files_id,
9013                "Should have only the multi buffer left in the pane"
9014            );
9015            assert!(
9016                dirty_multi_buffer_with_both.read(cx).is_dirty,
9017                "The multi buffer containing the unsaved buffer should still be dirty"
9018            );
9019        });
9020
9021        dirty_regular_buffer.update(cx, |buffer, cx| {
9022            buffer.project_items[0].update(cx, |pi, _| pi.is_dirty = true)
9023        });
9024
9025        let close_multi_buffer_task = pane
9026            .update_in(cx, |pane, window, cx| {
9027                pane.close_active_item(
9028                    &CloseActiveItem {
9029                        save_intent: Some(SaveIntent::Close),
9030                        close_pinned: false,
9031                    },
9032                    window,
9033                    cx,
9034                )
9035            })
9036            .expect("should have the multi buffer to close");
9037        cx.background_executor.run_until_parked();
9038        assert!(
9039            cx.has_pending_prompt(),
9040            "Dirty multi buffer should prompt a save dialog"
9041        );
9042        cx.simulate_prompt_answer("Save");
9043        cx.background_executor.run_until_parked();
9044        close_multi_buffer_task
9045            .await
9046            .expect("Closing the multi buffer failed");
9047        pane.update(cx, |pane, cx| {
9048            assert_eq!(
9049                dirty_multi_buffer_with_both.read(cx).save_count,
9050                1,
9051                "Multi buffer item should get be saved"
9052            );
9053            // Test impl does not save inner items, so we do not assert them
9054            assert_eq!(
9055                pane.items_len(),
9056                0,
9057                "No more items should be left in the pane"
9058            );
9059            assert!(pane.active_item().is_none());
9060        });
9061    }
9062
9063    #[gpui::test]
9064    async fn test_save_prompt_when_dirty_multi_buffer_closed_with_some_of_its_dirty_items_not_present_in_the_pane(
9065        cx: &mut TestAppContext,
9066    ) {
9067        init_test(cx);
9068
9069        let fs = FakeFs::new(cx.background_executor.clone());
9070        let project = Project::test(fs, [], cx).await;
9071        let (workspace, cx) =
9072            cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
9073        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
9074
9075        let dirty_regular_buffer = cx.new(|cx| {
9076            TestItem::new(cx)
9077                .with_dirty(true)
9078                .with_label("1.txt")
9079                .with_project_items(&[dirty_project_item(1, "1.txt", cx)])
9080        });
9081        let dirty_regular_buffer_2 = cx.new(|cx| {
9082            TestItem::new(cx)
9083                .with_dirty(true)
9084                .with_label("2.txt")
9085                .with_project_items(&[dirty_project_item(2, "2.txt", cx)])
9086        });
9087        let clear_regular_buffer = cx.new(|cx| {
9088            TestItem::new(cx)
9089                .with_label("3.txt")
9090                .with_project_items(&[TestProjectItem::new(3, "3.txt", cx)])
9091        });
9092
9093        let dirty_multi_buffer_with_both = cx.new(|cx| {
9094            TestItem::new(cx)
9095                .with_dirty(true)
9096                .with_singleton(false)
9097                .with_label("Fake Project Search")
9098                .with_project_items(&[
9099                    dirty_regular_buffer.read(cx).project_items[0].clone(),
9100                    dirty_regular_buffer_2.read(cx).project_items[0].clone(),
9101                    clear_regular_buffer.read(cx).project_items[0].clone(),
9102                ])
9103        });
9104        let multi_buffer_with_both_files_id = dirty_multi_buffer_with_both.item_id();
9105        workspace.update_in(cx, |workspace, window, cx| {
9106            workspace.add_item(
9107                pane.clone(),
9108                Box::new(dirty_regular_buffer.clone()),
9109                None,
9110                false,
9111                false,
9112                window,
9113                cx,
9114            );
9115            workspace.add_item(
9116                pane.clone(),
9117                Box::new(dirty_multi_buffer_with_both.clone()),
9118                None,
9119                false,
9120                false,
9121                window,
9122                cx,
9123            );
9124        });
9125
9126        pane.update_in(cx, |pane, window, cx| {
9127            pane.activate_item(1, true, true, window, cx);
9128            assert_eq!(
9129                pane.active_item().unwrap().item_id(),
9130                multi_buffer_with_both_files_id,
9131                "Should select the multi buffer in the pane"
9132            );
9133        });
9134        let _close_multi_buffer_task = pane
9135            .update_in(cx, |pane, window, cx| {
9136                pane.close_active_item(
9137                    &CloseActiveItem {
9138                        save_intent: None,
9139                        close_pinned: false,
9140                    },
9141                    window,
9142                    cx,
9143                )
9144            })
9145            .expect("should have active multi buffer to close");
9146        cx.background_executor.run_until_parked();
9147        assert!(
9148            cx.has_pending_prompt(),
9149            "With one dirty item from the multi buffer not being in the pane, a save prompt should be shown"
9150        );
9151    }
9152
9153    #[gpui::test]
9154    async fn test_no_save_prompt_when_dirty_multi_buffer_closed_with_all_of_its_dirty_items_present_in_the_pane(
9155        cx: &mut TestAppContext,
9156    ) {
9157        init_test(cx);
9158
9159        let fs = FakeFs::new(cx.background_executor.clone());
9160        let project = Project::test(fs, [], cx).await;
9161        let (workspace, cx) =
9162            cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
9163        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
9164
9165        let dirty_regular_buffer = cx.new(|cx| {
9166            TestItem::new(cx)
9167                .with_dirty(true)
9168                .with_label("1.txt")
9169                .with_project_items(&[dirty_project_item(1, "1.txt", cx)])
9170        });
9171        let dirty_regular_buffer_2 = cx.new(|cx| {
9172            TestItem::new(cx)
9173                .with_dirty(true)
9174                .with_label("2.txt")
9175                .with_project_items(&[dirty_project_item(2, "2.txt", cx)])
9176        });
9177        let clear_regular_buffer = cx.new(|cx| {
9178            TestItem::new(cx)
9179                .with_label("3.txt")
9180                .with_project_items(&[TestProjectItem::new(3, "3.txt", cx)])
9181        });
9182
9183        let dirty_multi_buffer = cx.new(|cx| {
9184            TestItem::new(cx)
9185                .with_dirty(true)
9186                .with_singleton(false)
9187                .with_label("Fake Project Search")
9188                .with_project_items(&[
9189                    dirty_regular_buffer.read(cx).project_items[0].clone(),
9190                    dirty_regular_buffer_2.read(cx).project_items[0].clone(),
9191                    clear_regular_buffer.read(cx).project_items[0].clone(),
9192                ])
9193        });
9194        workspace.update_in(cx, |workspace, window, cx| {
9195            workspace.add_item(
9196                pane.clone(),
9197                Box::new(dirty_regular_buffer.clone()),
9198                None,
9199                false,
9200                false,
9201                window,
9202                cx,
9203            );
9204            workspace.add_item(
9205                pane.clone(),
9206                Box::new(dirty_regular_buffer_2.clone()),
9207                None,
9208                false,
9209                false,
9210                window,
9211                cx,
9212            );
9213            workspace.add_item(
9214                pane.clone(),
9215                Box::new(dirty_multi_buffer.clone()),
9216                None,
9217                false,
9218                false,
9219                window,
9220                cx,
9221            );
9222        });
9223
9224        pane.update_in(cx, |pane, window, cx| {
9225            pane.activate_item(2, true, true, window, cx);
9226            assert_eq!(
9227                pane.active_item().unwrap().item_id(),
9228                dirty_multi_buffer.item_id(),
9229                "Should select the multi buffer in the pane"
9230            );
9231        });
9232        let close_multi_buffer_task = pane
9233            .update_in(cx, |pane, window, cx| {
9234                pane.close_active_item(
9235                    &CloseActiveItem {
9236                        save_intent: None,
9237                        close_pinned: false,
9238                    },
9239                    window,
9240                    cx,
9241                )
9242            })
9243            .expect("should have active multi buffer to close");
9244        cx.background_executor.run_until_parked();
9245        assert!(
9246            !cx.has_pending_prompt(),
9247            "All dirty items from the multi buffer are in the pane still, no save prompts should be shown"
9248        );
9249        close_multi_buffer_task
9250            .await
9251            .expect("Closing multi buffer failed");
9252        pane.update(cx, |pane, cx| {
9253            assert_eq!(dirty_regular_buffer.read(cx).save_count, 0);
9254            assert_eq!(dirty_multi_buffer.read(cx).save_count, 0);
9255            assert_eq!(dirty_regular_buffer_2.read(cx).save_count, 0);
9256            assert_eq!(
9257                pane.items()
9258                    .map(|item| item.item_id())
9259                    .sorted()
9260                    .collect::<Vec<_>>(),
9261                vec![
9262                    dirty_regular_buffer.item_id(),
9263                    dirty_regular_buffer_2.item_id(),
9264                ],
9265                "Should have no multi buffer left in the pane"
9266            );
9267            assert!(dirty_regular_buffer.read(cx).is_dirty);
9268            assert!(dirty_regular_buffer_2.read(cx).is_dirty);
9269        });
9270    }
9271
9272    #[gpui::test]
9273    async fn test_move_focused_panel_to_next_position(cx: &mut gpui::TestAppContext) {
9274        init_test(cx);
9275        let fs = FakeFs::new(cx.executor());
9276        let project = Project::test(fs, [], cx).await;
9277        let (workspace, cx) =
9278            cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
9279
9280        // Add a new panel to the right dock, opening the dock and setting the
9281        // focus to the new panel.
9282        let panel = workspace.update_in(cx, |workspace, window, cx| {
9283            let panel = cx.new(|cx| TestPanel::new(DockPosition::Right, cx));
9284            workspace.add_panel(panel.clone(), window, cx);
9285
9286            workspace
9287                .right_dock()
9288                .update(cx, |right_dock, cx| right_dock.set_open(true, window, cx));
9289
9290            workspace.toggle_panel_focus::<TestPanel>(window, cx);
9291
9292            panel
9293        });
9294
9295        // Dispatch the `MoveFocusedPanelToNextPosition` action, moving the
9296        // panel to the next valid position which, in this case, is the left
9297        // dock.
9298        cx.dispatch_action(MoveFocusedPanelToNextPosition);
9299        workspace.update(cx, |workspace, cx| {
9300            assert!(workspace.left_dock().read(cx).is_open());
9301            assert_eq!(panel.read(cx).position, DockPosition::Left);
9302        });
9303
9304        // Dispatch the `MoveFocusedPanelToNextPosition` action, moving the
9305        // panel to the next valid position which, in this case, is the bottom
9306        // dock.
9307        cx.dispatch_action(MoveFocusedPanelToNextPosition);
9308        workspace.update(cx, |workspace, cx| {
9309            assert!(workspace.bottom_dock().read(cx).is_open());
9310            assert_eq!(panel.read(cx).position, DockPosition::Bottom);
9311        });
9312
9313        // Dispatch the `MoveFocusedPanelToNextPosition` action again, this time
9314        // around moving the panel to its initial position, the right dock.
9315        cx.dispatch_action(MoveFocusedPanelToNextPosition);
9316        workspace.update(cx, |workspace, cx| {
9317            assert!(workspace.right_dock().read(cx).is_open());
9318            assert_eq!(panel.read(cx).position, DockPosition::Right);
9319        });
9320
9321        // Remove focus from the panel, ensuring that, if the panel is not
9322        // focused, the `MoveFocusedPanelToNextPosition` action does not update
9323        // the panel's position, so the panel is still in the right dock.
9324        workspace.update_in(cx, |workspace, window, cx| {
9325            workspace.toggle_panel_focus::<TestPanel>(window, cx);
9326        });
9327
9328        cx.dispatch_action(MoveFocusedPanelToNextPosition);
9329        workspace.update(cx, |workspace, cx| {
9330            assert!(workspace.right_dock().read(cx).is_open());
9331            assert_eq!(panel.read(cx).position, DockPosition::Right);
9332        });
9333    }
9334
9335    mod register_project_item_tests {
9336
9337        use super::*;
9338
9339        // View
9340        struct TestPngItemView {
9341            focus_handle: FocusHandle,
9342        }
9343        // Model
9344        struct TestPngItem {}
9345
9346        impl project::ProjectItem for TestPngItem {
9347            fn try_open(
9348                _project: &Entity<Project>,
9349                path: &ProjectPath,
9350                cx: &mut App,
9351            ) -> Option<Task<gpui::Result<Entity<Self>>>> {
9352                if path.path.extension().unwrap() == "png" {
9353                    Some(cx.spawn(async move |cx| cx.new(|_| TestPngItem {})))
9354                } else {
9355                    None
9356                }
9357            }
9358
9359            fn entry_id(&self, _: &App) -> Option<ProjectEntryId> {
9360                None
9361            }
9362
9363            fn project_path(&self, _: &App) -> Option<ProjectPath> {
9364                None
9365            }
9366
9367            fn is_dirty(&self) -> bool {
9368                false
9369            }
9370        }
9371
9372        impl Item for TestPngItemView {
9373            type Event = ();
9374            fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString {
9375                "".into()
9376            }
9377        }
9378        impl EventEmitter<()> for TestPngItemView {}
9379        impl Focusable for TestPngItemView {
9380            fn focus_handle(&self, _cx: &App) -> FocusHandle {
9381                self.focus_handle.clone()
9382            }
9383        }
9384
9385        impl Render for TestPngItemView {
9386            fn render(
9387                &mut self,
9388                _window: &mut Window,
9389                _cx: &mut Context<Self>,
9390            ) -> impl IntoElement {
9391                Empty
9392            }
9393        }
9394
9395        impl ProjectItem for TestPngItemView {
9396            type Item = TestPngItem;
9397
9398            fn for_project_item(
9399                _project: Entity<Project>,
9400                _pane: Option<&Pane>,
9401                _item: Entity<Self::Item>,
9402                _: &mut Window,
9403                cx: &mut Context<Self>,
9404            ) -> Self
9405            where
9406                Self: Sized,
9407            {
9408                Self {
9409                    focus_handle: cx.focus_handle(),
9410                }
9411            }
9412        }
9413
9414        // View
9415        struct TestIpynbItemView {
9416            focus_handle: FocusHandle,
9417        }
9418        // Model
9419        struct TestIpynbItem {}
9420
9421        impl project::ProjectItem for TestIpynbItem {
9422            fn try_open(
9423                _project: &Entity<Project>,
9424                path: &ProjectPath,
9425                cx: &mut App,
9426            ) -> Option<Task<gpui::Result<Entity<Self>>>> {
9427                if path.path.extension().unwrap() == "ipynb" {
9428                    Some(cx.spawn(async move |cx| cx.new(|_| TestIpynbItem {})))
9429                } else {
9430                    None
9431                }
9432            }
9433
9434            fn entry_id(&self, _: &App) -> Option<ProjectEntryId> {
9435                None
9436            }
9437
9438            fn project_path(&self, _: &App) -> Option<ProjectPath> {
9439                None
9440            }
9441
9442            fn is_dirty(&self) -> bool {
9443                false
9444            }
9445        }
9446
9447        impl Item for TestIpynbItemView {
9448            type Event = ();
9449            fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString {
9450                "".into()
9451            }
9452        }
9453        impl EventEmitter<()> for TestIpynbItemView {}
9454        impl Focusable for TestIpynbItemView {
9455            fn focus_handle(&self, _cx: &App) -> FocusHandle {
9456                self.focus_handle.clone()
9457            }
9458        }
9459
9460        impl Render for TestIpynbItemView {
9461            fn render(
9462                &mut self,
9463                _window: &mut Window,
9464                _cx: &mut Context<Self>,
9465            ) -> impl IntoElement {
9466                Empty
9467            }
9468        }
9469
9470        impl ProjectItem for TestIpynbItemView {
9471            type Item = TestIpynbItem;
9472
9473            fn for_project_item(
9474                _project: Entity<Project>,
9475                _pane: Option<&Pane>,
9476                _item: Entity<Self::Item>,
9477                _: &mut Window,
9478                cx: &mut Context<Self>,
9479            ) -> Self
9480            where
9481                Self: Sized,
9482            {
9483                Self {
9484                    focus_handle: cx.focus_handle(),
9485                }
9486            }
9487        }
9488
9489        struct TestAlternatePngItemView {
9490            focus_handle: FocusHandle,
9491        }
9492
9493        impl Item for TestAlternatePngItemView {
9494            type Event = ();
9495            fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString {
9496                "".into()
9497            }
9498        }
9499
9500        impl EventEmitter<()> for TestAlternatePngItemView {}
9501        impl Focusable for TestAlternatePngItemView {
9502            fn focus_handle(&self, _cx: &App) -> FocusHandle {
9503                self.focus_handle.clone()
9504            }
9505        }
9506
9507        impl Render for TestAlternatePngItemView {
9508            fn render(
9509                &mut self,
9510                _window: &mut Window,
9511                _cx: &mut Context<Self>,
9512            ) -> impl IntoElement {
9513                Empty
9514            }
9515        }
9516
9517        impl ProjectItem for TestAlternatePngItemView {
9518            type Item = TestPngItem;
9519
9520            fn for_project_item(
9521                _project: Entity<Project>,
9522                _pane: Option<&Pane>,
9523                _item: Entity<Self::Item>,
9524                _: &mut Window,
9525                cx: &mut Context<Self>,
9526            ) -> Self
9527            where
9528                Self: Sized,
9529            {
9530                Self {
9531                    focus_handle: cx.focus_handle(),
9532                }
9533            }
9534        }
9535
9536        #[gpui::test]
9537        async fn test_register_project_item(cx: &mut TestAppContext) {
9538            init_test(cx);
9539
9540            cx.update(|cx| {
9541                register_project_item::<TestPngItemView>(cx);
9542                register_project_item::<TestIpynbItemView>(cx);
9543            });
9544
9545            let fs = FakeFs::new(cx.executor());
9546            fs.insert_tree(
9547                "/root1",
9548                json!({
9549                    "one.png": "BINARYDATAHERE",
9550                    "two.ipynb": "{ totally a notebook }",
9551                    "three.txt": "editing text, sure why not?"
9552                }),
9553            )
9554            .await;
9555
9556            let project = Project::test(fs, ["root1".as_ref()], cx).await;
9557            let (workspace, cx) =
9558                cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
9559
9560            let worktree_id = project.update(cx, |project, cx| {
9561                project.worktrees(cx).next().unwrap().read(cx).id()
9562            });
9563
9564            let handle = workspace
9565                .update_in(cx, |workspace, window, cx| {
9566                    let project_path = (worktree_id, "one.png");
9567                    workspace.open_path(project_path, None, true, window, cx)
9568                })
9569                .await
9570                .unwrap();
9571
9572            // Now we can check if the handle we got back errored or not
9573            assert_eq!(
9574                handle.to_any().entity_type(),
9575                TypeId::of::<TestPngItemView>()
9576            );
9577
9578            let handle = workspace
9579                .update_in(cx, |workspace, window, cx| {
9580                    let project_path = (worktree_id, "two.ipynb");
9581                    workspace.open_path(project_path, None, true, window, cx)
9582                })
9583                .await
9584                .unwrap();
9585
9586            assert_eq!(
9587                handle.to_any().entity_type(),
9588                TypeId::of::<TestIpynbItemView>()
9589            );
9590
9591            let handle = workspace
9592                .update_in(cx, |workspace, window, cx| {
9593                    let project_path = (worktree_id, "three.txt");
9594                    workspace.open_path(project_path, None, true, window, cx)
9595                })
9596                .await;
9597            assert!(handle.is_err());
9598        }
9599
9600        #[gpui::test]
9601        async fn test_register_project_item_two_enter_one_leaves(cx: &mut TestAppContext) {
9602            init_test(cx);
9603
9604            cx.update(|cx| {
9605                register_project_item::<TestPngItemView>(cx);
9606                register_project_item::<TestAlternatePngItemView>(cx);
9607            });
9608
9609            let fs = FakeFs::new(cx.executor());
9610            fs.insert_tree(
9611                "/root1",
9612                json!({
9613                    "one.png": "BINARYDATAHERE",
9614                    "two.ipynb": "{ totally a notebook }",
9615                    "three.txt": "editing text, sure why not?"
9616                }),
9617            )
9618            .await;
9619            let project = Project::test(fs, ["root1".as_ref()], cx).await;
9620            let (workspace, cx) =
9621                cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
9622            let worktree_id = project.update(cx, |project, cx| {
9623                project.worktrees(cx).next().unwrap().read(cx).id()
9624            });
9625
9626            let handle = workspace
9627                .update_in(cx, |workspace, window, cx| {
9628                    let project_path = (worktree_id, "one.png");
9629                    workspace.open_path(project_path, None, true, window, cx)
9630                })
9631                .await
9632                .unwrap();
9633
9634            // This _must_ be the second item registered
9635            assert_eq!(
9636                handle.to_any().entity_type(),
9637                TypeId::of::<TestAlternatePngItemView>()
9638            );
9639
9640            let handle = workspace
9641                .update_in(cx, |workspace, window, cx| {
9642                    let project_path = (worktree_id, "three.txt");
9643                    workspace.open_path(project_path, None, true, window, cx)
9644                })
9645                .await;
9646            assert!(handle.is_err());
9647        }
9648    }
9649
9650    pub fn init_test(cx: &mut TestAppContext) {
9651        cx.update(|cx| {
9652            let settings_store = SettingsStore::test(cx);
9653            cx.set_global(settings_store);
9654            theme::init(theme::LoadThemes::JustBase, cx);
9655            language::init(cx);
9656            crate::init_settings(cx);
9657            Project::init_settings(cx);
9658        });
9659    }
9660
9661    fn dirty_project_item(id: u64, path: &str, cx: &mut App) -> Entity<TestProjectItem> {
9662        let item = TestProjectItem::new(id, path, cx);
9663        item.update(cx, |item, _| {
9664            item.is_dirty = true;
9665        });
9666        item
9667    }
9668}