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>(&mut self, window: &mut Window, cx: &mut Context<Self>) {
2780        self.focus_or_unfocus_panel::<T>(window, cx, |panel, window, cx| {
2781            !panel.panel_focus_handle(cx).contains_focused(window, cx)
2782        });
2783    }
2784
2785    pub fn activate_panel_for_proto_id(
2786        &mut self,
2787        panel_id: PanelId,
2788        window: &mut Window,
2789        cx: &mut Context<Self>,
2790    ) -> Option<Arc<dyn PanelHandle>> {
2791        let mut panel = None;
2792        for dock in self.all_docks() {
2793            if let Some(panel_index) = dock.read(cx).panel_index_for_proto_id(panel_id) {
2794                panel = dock.update(cx, |dock, cx| {
2795                    dock.activate_panel(panel_index, window, cx);
2796                    dock.set_open(true, window, cx);
2797                    dock.active_panel().cloned()
2798                });
2799                break;
2800            }
2801        }
2802
2803        if panel.is_some() {
2804            cx.notify();
2805            self.serialize_workspace(window, cx);
2806        }
2807
2808        panel
2809    }
2810
2811    /// Focus or unfocus the given panel type, depending on the given callback.
2812    fn focus_or_unfocus_panel<T: Panel>(
2813        &mut self,
2814        window: &mut Window,
2815        cx: &mut Context<Self>,
2816        should_focus: impl Fn(&dyn PanelHandle, &mut Window, &mut Context<Dock>) -> bool,
2817    ) -> Option<Arc<dyn PanelHandle>> {
2818        let mut result_panel = None;
2819        let mut serialize = false;
2820        for dock in self.all_docks() {
2821            if let Some(panel_index) = dock.read(cx).panel_index_for_type::<T>() {
2822                let mut focus_center = false;
2823                let panel = dock.update(cx, |dock, cx| {
2824                    dock.activate_panel(panel_index, window, cx);
2825
2826                    let panel = dock.active_panel().cloned();
2827                    if let Some(panel) = panel.as_ref() {
2828                        if should_focus(&**panel, window, cx) {
2829                            dock.set_open(true, window, cx);
2830                            panel.panel_focus_handle(cx).focus(window);
2831                        } else {
2832                            focus_center = true;
2833                        }
2834                    }
2835                    panel
2836                });
2837
2838                if focus_center {
2839                    self.active_pane
2840                        .update(cx, |pane, cx| window.focus(&pane.focus_handle(cx)))
2841                }
2842
2843                result_panel = panel;
2844                serialize = true;
2845                break;
2846            }
2847        }
2848
2849        if serialize {
2850            self.serialize_workspace(window, cx);
2851        }
2852
2853        cx.notify();
2854        result_panel
2855    }
2856
2857    /// Open the panel of the given type
2858    pub fn open_panel<T: Panel>(&mut self, window: &mut Window, cx: &mut Context<Self>) {
2859        for dock in self.all_docks() {
2860            if let Some(panel_index) = dock.read(cx).panel_index_for_type::<T>() {
2861                dock.update(cx, |dock, cx| {
2862                    dock.activate_panel(panel_index, window, cx);
2863                    dock.set_open(true, window, cx);
2864                });
2865            }
2866        }
2867    }
2868
2869    pub fn panel<T: Panel>(&self, cx: &App) -> Option<Entity<T>> {
2870        self.all_docks()
2871            .iter()
2872            .find_map(|dock| dock.read(cx).panel::<T>())
2873    }
2874
2875    fn dismiss_zoomed_items_to_reveal(
2876        &mut self,
2877        dock_to_reveal: Option<DockPosition>,
2878        window: &mut Window,
2879        cx: &mut Context<Self>,
2880    ) {
2881        // If a center pane is zoomed, unzoom it.
2882        for pane in &self.panes {
2883            if pane != &self.active_pane || dock_to_reveal.is_some() {
2884                pane.update(cx, |pane, cx| pane.set_zoomed(false, cx));
2885            }
2886        }
2887
2888        // If another dock is zoomed, hide it.
2889        let mut focus_center = false;
2890        for dock in self.all_docks() {
2891            dock.update(cx, |dock, cx| {
2892                if Some(dock.position()) != dock_to_reveal {
2893                    if let Some(panel) = dock.active_panel() {
2894                        if panel.is_zoomed(window, cx) {
2895                            focus_center |=
2896                                panel.panel_focus_handle(cx).contains_focused(window, cx);
2897                            dock.set_open(false, window, cx);
2898                        }
2899                    }
2900                }
2901            });
2902        }
2903
2904        if focus_center {
2905            self.active_pane
2906                .update(cx, |pane, cx| window.focus(&pane.focus_handle(cx)))
2907        }
2908
2909        if self.zoomed_position != dock_to_reveal {
2910            self.zoomed = None;
2911            self.zoomed_position = None;
2912            cx.emit(Event::ZoomChanged);
2913        }
2914
2915        cx.notify();
2916    }
2917
2918    fn add_pane(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Entity<Pane> {
2919        let pane = cx.new(|cx| {
2920            let mut pane = Pane::new(
2921                self.weak_handle(),
2922                self.project.clone(),
2923                self.pane_history_timestamp.clone(),
2924                None,
2925                NewFile.boxed_clone(),
2926                window,
2927                cx,
2928            );
2929            pane.set_can_split(Some(Arc::new(|_, _, _, _| true)));
2930            pane
2931        });
2932        cx.subscribe_in(&pane, window, Self::handle_pane_event)
2933            .detach();
2934        self.panes.push(pane.clone());
2935
2936        window.focus(&pane.focus_handle(cx));
2937
2938        cx.emit(Event::PaneAdded(pane.clone()));
2939        pane
2940    }
2941
2942    pub fn add_item_to_center(
2943        &mut self,
2944        item: Box<dyn ItemHandle>,
2945        window: &mut Window,
2946        cx: &mut Context<Self>,
2947    ) -> bool {
2948        if let Some(center_pane) = self.last_active_center_pane.clone() {
2949            if let Some(center_pane) = center_pane.upgrade() {
2950                center_pane.update(cx, |pane, cx| {
2951                    pane.add_item(item, true, true, None, window, cx)
2952                });
2953                true
2954            } else {
2955                false
2956            }
2957        } else {
2958            false
2959        }
2960    }
2961
2962    pub fn add_item_to_active_pane(
2963        &mut self,
2964        item: Box<dyn ItemHandle>,
2965        destination_index: Option<usize>,
2966        focus_item: bool,
2967        window: &mut Window,
2968        cx: &mut App,
2969    ) {
2970        self.add_item(
2971            self.active_pane.clone(),
2972            item,
2973            destination_index,
2974            false,
2975            focus_item,
2976            window,
2977            cx,
2978        )
2979    }
2980
2981    pub fn add_item(
2982        &mut self,
2983        pane: Entity<Pane>,
2984        item: Box<dyn ItemHandle>,
2985        destination_index: Option<usize>,
2986        activate_pane: bool,
2987        focus_item: bool,
2988        window: &mut Window,
2989        cx: &mut App,
2990    ) {
2991        if let Some(text) = item.telemetry_event_text(cx) {
2992            telemetry::event!(text);
2993        }
2994
2995        pane.update(cx, |pane, cx| {
2996            pane.add_item(
2997                item,
2998                activate_pane,
2999                focus_item,
3000                destination_index,
3001                window,
3002                cx,
3003            )
3004        });
3005    }
3006
3007    pub fn split_item(
3008        &mut self,
3009        split_direction: SplitDirection,
3010        item: Box<dyn ItemHandle>,
3011        window: &mut Window,
3012        cx: &mut Context<Self>,
3013    ) {
3014        let new_pane = self.split_pane(self.active_pane.clone(), split_direction, window, cx);
3015        self.add_item(new_pane, item, None, true, true, window, cx);
3016    }
3017
3018    pub fn open_abs_path(
3019        &mut self,
3020        abs_path: PathBuf,
3021        options: OpenOptions,
3022        window: &mut Window,
3023        cx: &mut Context<Self>,
3024    ) -> Task<anyhow::Result<Box<dyn ItemHandle>>> {
3025        cx.spawn_in(window, async move |workspace, cx| {
3026            let open_paths_task_result = workspace
3027                .update_in(cx, |workspace, window, cx| {
3028                    workspace.open_paths(vec![abs_path.clone()], options, None, window, cx)
3029                })
3030                .with_context(|| format!("open abs path {abs_path:?} task spawn"))?
3031                .await;
3032            anyhow::ensure!(
3033                open_paths_task_result.len() == 1,
3034                "open abs path {abs_path:?} task returned incorrect number of results"
3035            );
3036            match open_paths_task_result
3037                .into_iter()
3038                .next()
3039                .expect("ensured single task result")
3040            {
3041                Some(open_result) => {
3042                    open_result.with_context(|| format!("open abs path {abs_path:?} task join"))
3043                }
3044                None => anyhow::bail!("open abs path {abs_path:?} task returned None"),
3045            }
3046        })
3047    }
3048
3049    pub fn split_abs_path(
3050        &mut self,
3051        abs_path: PathBuf,
3052        visible: bool,
3053        window: &mut Window,
3054        cx: &mut Context<Self>,
3055    ) -> Task<anyhow::Result<Box<dyn ItemHandle>>> {
3056        let project_path_task =
3057            Workspace::project_path_for_path(self.project.clone(), &abs_path, visible, cx);
3058        cx.spawn_in(window, async move |this, cx| {
3059            let (_, path) = project_path_task.await?;
3060            this.update_in(cx, |this, window, cx| this.split_path(path, window, cx))?
3061                .await
3062        })
3063    }
3064
3065    pub fn open_path(
3066        &mut self,
3067        path: impl Into<ProjectPath>,
3068        pane: Option<WeakEntity<Pane>>,
3069        focus_item: bool,
3070        window: &mut Window,
3071        cx: &mut App,
3072    ) -> Task<Result<Box<dyn ItemHandle>, anyhow::Error>> {
3073        self.open_path_preview(path, pane, focus_item, false, true, window, cx)
3074    }
3075
3076    pub fn open_path_preview(
3077        &mut self,
3078        path: impl Into<ProjectPath>,
3079        pane: Option<WeakEntity<Pane>>,
3080        focus_item: bool,
3081        allow_preview: bool,
3082        activate: bool,
3083        window: &mut Window,
3084        cx: &mut App,
3085    ) -> Task<Result<Box<dyn ItemHandle>, anyhow::Error>> {
3086        let pane = pane.unwrap_or_else(|| {
3087            self.last_active_center_pane.clone().unwrap_or_else(|| {
3088                self.panes
3089                    .first()
3090                    .expect("There must be an active pane")
3091                    .downgrade()
3092            })
3093        });
3094
3095        let project_path = path.into();
3096        let task = self.load_path(project_path.clone(), window, cx);
3097        window.spawn(cx, async move |cx| {
3098            let (project_entry_id, build_item) = task.await?;
3099            let result = pane.update_in(cx, |pane, window, cx| {
3100                let result = pane.open_item(
3101                    project_entry_id,
3102                    project_path,
3103                    focus_item,
3104                    allow_preview,
3105                    activate,
3106                    None,
3107                    window,
3108                    cx,
3109                    build_item,
3110                );
3111
3112                result
3113            });
3114            result
3115        })
3116    }
3117
3118    pub fn split_path(
3119        &mut self,
3120        path: impl Into<ProjectPath>,
3121        window: &mut Window,
3122        cx: &mut Context<Self>,
3123    ) -> Task<Result<Box<dyn ItemHandle>, anyhow::Error>> {
3124        self.split_path_preview(path, false, None, window, cx)
3125    }
3126
3127    pub fn split_path_preview(
3128        &mut self,
3129        path: impl Into<ProjectPath>,
3130        allow_preview: bool,
3131        split_direction: Option<SplitDirection>,
3132        window: &mut Window,
3133        cx: &mut Context<Self>,
3134    ) -> Task<Result<Box<dyn ItemHandle>, anyhow::Error>> {
3135        let pane = self.last_active_center_pane.clone().unwrap_or_else(|| {
3136            self.panes
3137                .first()
3138                .expect("There must be an active pane")
3139                .downgrade()
3140        });
3141
3142        if let Member::Pane(center_pane) = &self.center.root {
3143            if center_pane.read(cx).items_len() == 0 {
3144                return self.open_path(path, Some(pane), true, window, cx);
3145            }
3146        }
3147
3148        let project_path = path.into();
3149        let task = self.load_path(project_path.clone(), window, cx);
3150        cx.spawn_in(window, async move |this, cx| {
3151            let (project_entry_id, build_item) = task.await?;
3152            this.update_in(cx, move |this, window, cx| -> Option<_> {
3153                let pane = pane.upgrade()?;
3154                let new_pane = this.split_pane(
3155                    pane,
3156                    split_direction.unwrap_or(SplitDirection::Right),
3157                    window,
3158                    cx,
3159                );
3160                new_pane.update(cx, |new_pane, cx| {
3161                    Some(new_pane.open_item(
3162                        project_entry_id,
3163                        project_path,
3164                        true,
3165                        allow_preview,
3166                        true,
3167                        None,
3168                        window,
3169                        cx,
3170                        build_item,
3171                    ))
3172                })
3173            })
3174            .map(|option| option.ok_or_else(|| anyhow!("pane was dropped")))?
3175        })
3176    }
3177
3178    fn load_path(
3179        &mut self,
3180        path: ProjectPath,
3181        window: &mut Window,
3182        cx: &mut App,
3183    ) -> Task<Result<(Option<ProjectEntryId>, WorkspaceItemBuilder)>> {
3184        let project = self.project().clone();
3185        let registry = cx.default_global::<ProjectItemRegistry>().clone();
3186        registry.open_path(&project, &path, window, cx)
3187    }
3188
3189    pub fn find_project_item<T>(
3190        &self,
3191        pane: &Entity<Pane>,
3192        project_item: &Entity<T::Item>,
3193        cx: &App,
3194    ) -> Option<Entity<T>>
3195    where
3196        T: ProjectItem,
3197    {
3198        use project::ProjectItem as _;
3199        let project_item = project_item.read(cx);
3200        let entry_id = project_item.entry_id(cx);
3201        let project_path = project_item.project_path(cx);
3202
3203        let mut item = None;
3204        if let Some(entry_id) = entry_id {
3205            item = pane.read(cx).item_for_entry(entry_id, cx);
3206        }
3207        if item.is_none() {
3208            if let Some(project_path) = project_path {
3209                item = pane.read(cx).item_for_path(project_path, cx);
3210            }
3211        }
3212
3213        item.and_then(|item| item.downcast::<T>())
3214    }
3215
3216    pub fn is_project_item_open<T>(
3217        &self,
3218        pane: &Entity<Pane>,
3219        project_item: &Entity<T::Item>,
3220        cx: &App,
3221    ) -> bool
3222    where
3223        T: ProjectItem,
3224    {
3225        self.find_project_item::<T>(pane, project_item, cx)
3226            .is_some()
3227    }
3228
3229    pub fn open_project_item<T>(
3230        &mut self,
3231        pane: Entity<Pane>,
3232        project_item: Entity<T::Item>,
3233        activate_pane: bool,
3234        focus_item: bool,
3235        window: &mut Window,
3236        cx: &mut Context<Self>,
3237    ) -> Entity<T>
3238    where
3239        T: ProjectItem,
3240    {
3241        if let Some(item) = self.find_project_item(&pane, &project_item, cx) {
3242            self.activate_item(&item, activate_pane, focus_item, window, cx);
3243            return item;
3244        }
3245
3246        let item = pane.update(cx, |pane, cx| {
3247            cx.new(|cx| {
3248                T::for_project_item(self.project().clone(), Some(pane), project_item, window, cx)
3249            })
3250        });
3251        let item_id = item.item_id();
3252        let mut destination_index = None;
3253        pane.update(cx, |pane, cx| {
3254            if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
3255                if let Some(preview_item_id) = pane.preview_item_id() {
3256                    if preview_item_id != item_id {
3257                        destination_index = pane.close_current_preview_item(window, cx);
3258                    }
3259                }
3260            }
3261            pane.set_preview_item_id(Some(item.item_id()), cx)
3262        });
3263
3264        self.add_item(
3265            pane,
3266            Box::new(item.clone()),
3267            destination_index,
3268            activate_pane,
3269            focus_item,
3270            window,
3271            cx,
3272        );
3273        item
3274    }
3275
3276    pub fn open_shared_screen(
3277        &mut self,
3278        peer_id: PeerId,
3279        window: &mut Window,
3280        cx: &mut Context<Self>,
3281    ) {
3282        if let Some(shared_screen) =
3283            self.shared_screen_for_peer(peer_id, &self.active_pane, window, cx)
3284        {
3285            self.active_pane.update(cx, |pane, cx| {
3286                pane.add_item(Box::new(shared_screen), false, true, None, window, cx)
3287            });
3288        }
3289    }
3290
3291    pub fn activate_item(
3292        &mut self,
3293        item: &dyn ItemHandle,
3294        activate_pane: bool,
3295        focus_item: bool,
3296        window: &mut Window,
3297        cx: &mut App,
3298    ) -> bool {
3299        let result = self.panes.iter().find_map(|pane| {
3300            pane.read(cx)
3301                .index_for_item(item)
3302                .map(|ix| (pane.clone(), ix))
3303        });
3304        if let Some((pane, ix)) = result {
3305            pane.update(cx, |pane, cx| {
3306                pane.activate_item(ix, activate_pane, focus_item, window, cx)
3307            });
3308            true
3309        } else {
3310            false
3311        }
3312    }
3313
3314    fn activate_pane_at_index(
3315        &mut self,
3316        action: &ActivatePane,
3317        window: &mut Window,
3318        cx: &mut Context<Self>,
3319    ) {
3320        let panes = self.center.panes();
3321        if let Some(pane) = panes.get(action.0).map(|p| (*p).clone()) {
3322            window.focus(&pane.focus_handle(cx));
3323        } else {
3324            self.split_and_clone(self.active_pane.clone(), SplitDirection::Right, window, cx);
3325        }
3326    }
3327
3328    fn move_item_to_pane_at_index(
3329        &mut self,
3330        action: &MoveItemToPane,
3331        window: &mut Window,
3332        cx: &mut Context<Self>,
3333    ) {
3334        let Some(&target_pane) = self.center.panes().get(action.destination) else {
3335            return;
3336        };
3337        move_active_item(
3338            &self.active_pane,
3339            target_pane,
3340            action.focus,
3341            true,
3342            window,
3343            cx,
3344        );
3345    }
3346
3347    pub fn activate_next_pane(&mut self, window: &mut Window, cx: &mut App) {
3348        let panes = self.center.panes();
3349        if let Some(ix) = panes.iter().position(|pane| **pane == self.active_pane) {
3350            let next_ix = (ix + 1) % panes.len();
3351            let next_pane = panes[next_ix].clone();
3352            window.focus(&next_pane.focus_handle(cx));
3353        }
3354    }
3355
3356    pub fn activate_previous_pane(&mut self, window: &mut Window, cx: &mut App) {
3357        let panes = self.center.panes();
3358        if let Some(ix) = panes.iter().position(|pane| **pane == self.active_pane) {
3359            let prev_ix = cmp::min(ix.wrapping_sub(1), panes.len() - 1);
3360            let prev_pane = panes[prev_ix].clone();
3361            window.focus(&prev_pane.focus_handle(cx));
3362        }
3363    }
3364
3365    pub fn activate_pane_in_direction(
3366        &mut self,
3367        direction: SplitDirection,
3368        window: &mut Window,
3369        cx: &mut App,
3370    ) {
3371        use ActivateInDirectionTarget as Target;
3372        enum Origin {
3373            LeftDock,
3374            RightDock,
3375            BottomDock,
3376            Center,
3377        }
3378
3379        let origin: Origin = [
3380            (&self.left_dock, Origin::LeftDock),
3381            (&self.right_dock, Origin::RightDock),
3382            (&self.bottom_dock, Origin::BottomDock),
3383        ]
3384        .into_iter()
3385        .find_map(|(dock, origin)| {
3386            if dock.focus_handle(cx).contains_focused(window, cx) && dock.read(cx).is_open() {
3387                Some(origin)
3388            } else {
3389                None
3390            }
3391        })
3392        .unwrap_or(Origin::Center);
3393
3394        let get_last_active_pane = || {
3395            let pane = self
3396                .last_active_center_pane
3397                .clone()
3398                .unwrap_or_else(|| {
3399                    self.panes
3400                        .first()
3401                        .expect("There must be an active pane")
3402                        .downgrade()
3403                })
3404                .upgrade()?;
3405            (pane.read(cx).items_len() != 0).then_some(pane)
3406        };
3407
3408        let try_dock =
3409            |dock: &Entity<Dock>| dock.read(cx).is_open().then(|| Target::Dock(dock.clone()));
3410
3411        let target = match (origin, direction) {
3412            // We're in the center, so we first try to go to a different pane,
3413            // otherwise try to go to a dock.
3414            (Origin::Center, direction) => {
3415                if let Some(pane) = self.find_pane_in_direction(direction, cx) {
3416                    Some(Target::Pane(pane))
3417                } else {
3418                    match direction {
3419                        SplitDirection::Up => None,
3420                        SplitDirection::Down => try_dock(&self.bottom_dock),
3421                        SplitDirection::Left => try_dock(&self.left_dock),
3422                        SplitDirection::Right => try_dock(&self.right_dock),
3423                    }
3424                }
3425            }
3426
3427            (Origin::LeftDock, SplitDirection::Right) => {
3428                if let Some(last_active_pane) = get_last_active_pane() {
3429                    Some(Target::Pane(last_active_pane))
3430                } else {
3431                    try_dock(&self.bottom_dock).or_else(|| try_dock(&self.right_dock))
3432                }
3433            }
3434
3435            (Origin::LeftDock, SplitDirection::Down)
3436            | (Origin::RightDock, SplitDirection::Down) => try_dock(&self.bottom_dock),
3437
3438            (Origin::BottomDock, SplitDirection::Up) => get_last_active_pane().map(Target::Pane),
3439            (Origin::BottomDock, SplitDirection::Left) => try_dock(&self.left_dock),
3440            (Origin::BottomDock, SplitDirection::Right) => try_dock(&self.right_dock),
3441
3442            (Origin::RightDock, SplitDirection::Left) => {
3443                if let Some(last_active_pane) = get_last_active_pane() {
3444                    Some(Target::Pane(last_active_pane))
3445                } else {
3446                    try_dock(&self.bottom_dock).or_else(|| try_dock(&self.left_dock))
3447                }
3448            }
3449
3450            _ => None,
3451        };
3452
3453        match target {
3454            Some(ActivateInDirectionTarget::Pane(pane)) => {
3455                window.focus(&pane.focus_handle(cx));
3456            }
3457            Some(ActivateInDirectionTarget::Dock(dock)) => {
3458                // Defer this to avoid a panic when the dock's active panel is already on the stack.
3459                window.defer(cx, move |window, cx| {
3460                    let dock = dock.read(cx);
3461                    if let Some(panel) = dock.active_panel() {
3462                        panel.panel_focus_handle(cx).focus(window);
3463                    } else {
3464                        log::error!("Could not find a focus target when in switching focus in {direction} direction for a {:?} dock", dock.position());
3465                    }
3466                })
3467            }
3468            None => {}
3469        }
3470    }
3471
3472    pub fn move_item_to_pane_in_direction(
3473        &mut self,
3474        action: &MoveItemToPaneInDirection,
3475        window: &mut Window,
3476        cx: &mut App,
3477    ) {
3478        if let Some(destination) = self.find_pane_in_direction(action.direction, cx) {
3479            move_active_item(
3480                &self.active_pane,
3481                &destination,
3482                action.focus,
3483                true,
3484                window,
3485                cx,
3486            );
3487        }
3488    }
3489
3490    pub fn bounding_box_for_pane(&self, pane: &Entity<Pane>) -> Option<Bounds<Pixels>> {
3491        self.center.bounding_box_for_pane(pane)
3492    }
3493
3494    pub fn find_pane_in_direction(
3495        &mut self,
3496        direction: SplitDirection,
3497        cx: &App,
3498    ) -> Option<Entity<Pane>> {
3499        self.center
3500            .find_pane_in_direction(&self.active_pane, direction, cx)
3501            .cloned()
3502    }
3503
3504    pub fn swap_pane_in_direction(&mut self, direction: SplitDirection, cx: &mut Context<Self>) {
3505        if let Some(to) = self.find_pane_in_direction(direction, cx) {
3506            self.center.swap(&self.active_pane, &to);
3507            cx.notify();
3508        }
3509    }
3510
3511    pub fn resize_pane(
3512        &mut self,
3513        axis: gpui::Axis,
3514        amount: Pixels,
3515        window: &mut Window,
3516        cx: &mut Context<Self>,
3517    ) {
3518        let docks = self.all_docks();
3519        let active_dock = docks
3520            .into_iter()
3521            .find(|dock| dock.focus_handle(cx).contains_focused(window, cx));
3522
3523        if let Some(dock) = active_dock {
3524            let Some(panel_size) = dock.read(cx).active_panel_size(window, cx) else {
3525                return;
3526            };
3527            match dock.read(cx).position() {
3528                DockPosition::Left => resize_left_dock(panel_size + amount, self, window, cx),
3529                DockPosition::Bottom => resize_bottom_dock(panel_size + amount, self, window, cx),
3530                DockPosition::Right => resize_right_dock(panel_size + amount, self, window, cx),
3531            }
3532        } else {
3533            self.center
3534                .resize(&self.active_pane, axis, amount, &self.bounds);
3535        }
3536        cx.notify();
3537    }
3538
3539    pub fn reset_pane_sizes(&mut self, cx: &mut Context<Self>) {
3540        self.center.reset_pane_sizes();
3541        cx.notify();
3542    }
3543
3544    fn handle_pane_focused(
3545        &mut self,
3546        pane: Entity<Pane>,
3547        window: &mut Window,
3548        cx: &mut Context<Self>,
3549    ) {
3550        // This is explicitly hoisted out of the following check for pane identity as
3551        // terminal panel panes are not registered as a center panes.
3552        self.status_bar.update(cx, |status_bar, cx| {
3553            status_bar.set_active_pane(&pane, window, cx);
3554        });
3555        if self.active_pane != pane {
3556            self.set_active_pane(&pane, window, cx);
3557        }
3558
3559        if self.last_active_center_pane.is_none() {
3560            self.last_active_center_pane = Some(pane.downgrade());
3561        }
3562
3563        self.dismiss_zoomed_items_to_reveal(None, window, cx);
3564        if pane.read(cx).is_zoomed() {
3565            self.zoomed = Some(pane.downgrade().into());
3566        } else {
3567            self.zoomed = None;
3568        }
3569        self.zoomed_position = None;
3570        cx.emit(Event::ZoomChanged);
3571        self.update_active_view_for_followers(window, cx);
3572        pane.update(cx, |pane, _| {
3573            pane.track_alternate_file_items();
3574        });
3575
3576        cx.notify();
3577    }
3578
3579    fn set_active_pane(
3580        &mut self,
3581        pane: &Entity<Pane>,
3582        window: &mut Window,
3583        cx: &mut Context<Self>,
3584    ) {
3585        self.active_pane = pane.clone();
3586        self.active_item_path_changed(window, cx);
3587        self.last_active_center_pane = Some(pane.downgrade());
3588    }
3589
3590    fn handle_panel_focused(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3591        self.update_active_view_for_followers(window, cx);
3592    }
3593
3594    fn handle_pane_event(
3595        &mut self,
3596        pane: &Entity<Pane>,
3597        event: &pane::Event,
3598        window: &mut Window,
3599        cx: &mut Context<Self>,
3600    ) {
3601        let mut serialize_workspace = true;
3602        match event {
3603            pane::Event::AddItem { item } => {
3604                item.added_to_pane(self, pane.clone(), window, cx);
3605                cx.emit(Event::ItemAdded {
3606                    item: item.boxed_clone(),
3607                });
3608            }
3609            pane::Event::Split(direction) => {
3610                self.split_and_clone(pane.clone(), *direction, window, cx);
3611            }
3612            pane::Event::JoinIntoNext => {
3613                self.join_pane_into_next(pane.clone(), window, cx);
3614            }
3615            pane::Event::JoinAll => {
3616                self.join_all_panes(window, cx);
3617            }
3618            pane::Event::Remove { focus_on_pane } => {
3619                self.remove_pane(pane.clone(), focus_on_pane.clone(), window, cx);
3620            }
3621            pane::Event::ActivateItem {
3622                local,
3623                focus_changed,
3624            } => {
3625                cx.on_next_frame(window, |_, window, _| {
3626                    window.invalidate_character_coordinates();
3627                });
3628
3629                pane.update(cx, |pane, _| {
3630                    pane.track_alternate_file_items();
3631                });
3632                if *local {
3633                    self.unfollow_in_pane(&pane, window, cx);
3634                }
3635                if pane == self.active_pane() {
3636                    self.active_item_path_changed(window, cx);
3637                    self.update_active_view_for_followers(window, cx);
3638                }
3639                serialize_workspace = *focus_changed || pane != self.active_pane();
3640            }
3641            pane::Event::UserSavedItem { item, save_intent } => {
3642                cx.emit(Event::UserSavedItem {
3643                    pane: pane.downgrade(),
3644                    item: item.boxed_clone(),
3645                    save_intent: *save_intent,
3646                });
3647                serialize_workspace = false;
3648            }
3649            pane::Event::ChangeItemTitle => {
3650                if *pane == self.active_pane {
3651                    self.active_item_path_changed(window, cx);
3652                }
3653                serialize_workspace = false;
3654            }
3655            pane::Event::RemoveItem { .. } => {}
3656            pane::Event::RemovedItem { item } => {
3657                cx.emit(Event::ActiveItemChanged);
3658                self.update_window_edited(window, cx);
3659                if let hash_map::Entry::Occupied(entry) = self.panes_by_item.entry(item.item_id()) {
3660                    if entry.get().entity_id() == pane.entity_id() {
3661                        entry.remove();
3662                    }
3663                }
3664            }
3665            pane::Event::Focus => {
3666                cx.on_next_frame(window, |_, window, _| {
3667                    window.invalidate_character_coordinates();
3668                });
3669                self.handle_pane_focused(pane.clone(), window, cx);
3670            }
3671            pane::Event::ZoomIn => {
3672                if *pane == self.active_pane {
3673                    pane.update(cx, |pane, cx| pane.set_zoomed(true, cx));
3674                    if pane.read(cx).has_focus(window, cx) {
3675                        self.zoomed = Some(pane.downgrade().into());
3676                        self.zoomed_position = None;
3677                        cx.emit(Event::ZoomChanged);
3678                    }
3679                    cx.notify();
3680                }
3681            }
3682            pane::Event::ZoomOut => {
3683                pane.update(cx, |pane, cx| pane.set_zoomed(false, cx));
3684                if self.zoomed_position.is_none() {
3685                    self.zoomed = None;
3686                    cx.emit(Event::ZoomChanged);
3687                }
3688                cx.notify();
3689            }
3690        }
3691
3692        if serialize_workspace {
3693            self.serialize_workspace(window, cx);
3694        }
3695    }
3696
3697    pub fn unfollow_in_pane(
3698        &mut self,
3699        pane: &Entity<Pane>,
3700        window: &mut Window,
3701        cx: &mut Context<Workspace>,
3702    ) -> Option<CollaboratorId> {
3703        let leader_id = self.leader_for_pane(pane)?;
3704        self.unfollow(leader_id, window, cx);
3705        Some(leader_id)
3706    }
3707
3708    pub fn split_pane(
3709        &mut self,
3710        pane_to_split: Entity<Pane>,
3711        split_direction: SplitDirection,
3712        window: &mut Window,
3713        cx: &mut Context<Self>,
3714    ) -> Entity<Pane> {
3715        let new_pane = self.add_pane(window, cx);
3716        self.center
3717            .split(&pane_to_split, &new_pane, split_direction)
3718            .unwrap();
3719        cx.notify();
3720        new_pane
3721    }
3722
3723    pub fn split_and_clone(
3724        &mut self,
3725        pane: Entity<Pane>,
3726        direction: SplitDirection,
3727        window: &mut Window,
3728        cx: &mut Context<Self>,
3729    ) -> Option<Entity<Pane>> {
3730        let item = pane.read(cx).active_item()?;
3731        let maybe_pane_handle =
3732            if let Some(clone) = item.clone_on_split(self.database_id(), window, cx) {
3733                let new_pane = self.add_pane(window, cx);
3734                new_pane.update(cx, |pane, cx| {
3735                    pane.add_item(clone, true, true, None, window, cx)
3736                });
3737                self.center.split(&pane, &new_pane, direction).unwrap();
3738                Some(new_pane)
3739            } else {
3740                None
3741            };
3742        cx.notify();
3743        maybe_pane_handle
3744    }
3745
3746    pub fn split_pane_with_item(
3747        &mut self,
3748        pane_to_split: WeakEntity<Pane>,
3749        split_direction: SplitDirection,
3750        from: WeakEntity<Pane>,
3751        item_id_to_move: EntityId,
3752        window: &mut Window,
3753        cx: &mut Context<Self>,
3754    ) {
3755        let Some(pane_to_split) = pane_to_split.upgrade() else {
3756            return;
3757        };
3758        let Some(from) = from.upgrade() else {
3759            return;
3760        };
3761
3762        let new_pane = self.add_pane(window, cx);
3763        move_item(&from, &new_pane, item_id_to_move, 0, window, cx);
3764        self.center
3765            .split(&pane_to_split, &new_pane, split_direction)
3766            .unwrap();
3767        cx.notify();
3768    }
3769
3770    pub fn split_pane_with_project_entry(
3771        &mut self,
3772        pane_to_split: WeakEntity<Pane>,
3773        split_direction: SplitDirection,
3774        project_entry: ProjectEntryId,
3775        window: &mut Window,
3776        cx: &mut Context<Self>,
3777    ) -> Option<Task<Result<()>>> {
3778        let pane_to_split = pane_to_split.upgrade()?;
3779        let new_pane = self.add_pane(window, cx);
3780        self.center
3781            .split(&pane_to_split, &new_pane, split_direction)
3782            .unwrap();
3783
3784        let path = self.project.read(cx).path_for_entry(project_entry, cx)?;
3785        let task = self.open_path(path, Some(new_pane.downgrade()), true, window, cx);
3786        Some(cx.foreground_executor().spawn(async move {
3787            task.await?;
3788            Ok(())
3789        }))
3790    }
3791
3792    pub fn join_all_panes(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3793        let active_item = self.active_pane.read(cx).active_item();
3794        for pane in &self.panes {
3795            join_pane_into_active(&self.active_pane, pane, window, cx);
3796        }
3797        if let Some(active_item) = active_item {
3798            self.activate_item(active_item.as_ref(), true, true, window, cx);
3799        }
3800        cx.notify();
3801    }
3802
3803    pub fn join_pane_into_next(
3804        &mut self,
3805        pane: Entity<Pane>,
3806        window: &mut Window,
3807        cx: &mut Context<Self>,
3808    ) {
3809        let next_pane = self
3810            .find_pane_in_direction(SplitDirection::Right, cx)
3811            .or_else(|| self.find_pane_in_direction(SplitDirection::Down, cx))
3812            .or_else(|| self.find_pane_in_direction(SplitDirection::Left, cx))
3813            .or_else(|| self.find_pane_in_direction(SplitDirection::Up, cx));
3814        let Some(next_pane) = next_pane else {
3815            return;
3816        };
3817        move_all_items(&pane, &next_pane, window, cx);
3818        cx.notify();
3819    }
3820
3821    fn remove_pane(
3822        &mut self,
3823        pane: Entity<Pane>,
3824        focus_on: Option<Entity<Pane>>,
3825        window: &mut Window,
3826        cx: &mut Context<Self>,
3827    ) {
3828        if self.center.remove(&pane).unwrap() {
3829            self.force_remove_pane(&pane, &focus_on, window, cx);
3830            self.unfollow_in_pane(&pane, window, cx);
3831            self.last_leaders_by_pane.remove(&pane.downgrade());
3832            for removed_item in pane.read(cx).items() {
3833                self.panes_by_item.remove(&removed_item.item_id());
3834            }
3835
3836            cx.notify();
3837        } else {
3838            self.active_item_path_changed(window, cx);
3839        }
3840        cx.emit(Event::PaneRemoved);
3841    }
3842
3843    pub fn panes(&self) -> &[Entity<Pane>] {
3844        &self.panes
3845    }
3846
3847    pub fn active_pane(&self) -> &Entity<Pane> {
3848        &self.active_pane
3849    }
3850
3851    pub fn focused_pane(&self, window: &Window, cx: &App) -> Entity<Pane> {
3852        for dock in self.all_docks() {
3853            if dock.focus_handle(cx).contains_focused(window, cx) {
3854                if let Some(pane) = dock
3855                    .read(cx)
3856                    .active_panel()
3857                    .and_then(|panel| panel.pane(cx))
3858                {
3859                    return pane;
3860                }
3861            }
3862        }
3863        self.active_pane().clone()
3864    }
3865
3866    pub fn adjacent_pane(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Entity<Pane> {
3867        self.find_pane_in_direction(SplitDirection::Right, cx)
3868            .or_else(|| self.find_pane_in_direction(SplitDirection::Left, cx))
3869            .unwrap_or_else(|| {
3870                self.split_pane(self.active_pane.clone(), SplitDirection::Right, window, cx)
3871            })
3872            .clone()
3873    }
3874
3875    pub fn pane_for(&self, handle: &dyn ItemHandle) -> Option<Entity<Pane>> {
3876        let weak_pane = self.panes_by_item.get(&handle.item_id())?;
3877        weak_pane.upgrade()
3878    }
3879
3880    fn collaborator_left(&mut self, peer_id: PeerId, window: &mut Window, cx: &mut Context<Self>) {
3881        self.follower_states.retain(|leader_id, state| {
3882            if *leader_id == CollaboratorId::PeerId(peer_id) {
3883                for item in state.items_by_leader_view_id.values() {
3884                    item.view.set_leader_id(None, window, cx);
3885                }
3886                false
3887            } else {
3888                true
3889            }
3890        });
3891        cx.notify();
3892    }
3893
3894    pub fn start_following(
3895        &mut self,
3896        leader_id: impl Into<CollaboratorId>,
3897        window: &mut Window,
3898        cx: &mut Context<Self>,
3899    ) -> Option<Task<Result<()>>> {
3900        let leader_id = leader_id.into();
3901        let pane = self.active_pane().clone();
3902
3903        self.last_leaders_by_pane
3904            .insert(pane.downgrade(), leader_id);
3905        self.unfollow(leader_id, window, cx);
3906        self.unfollow_in_pane(&pane, window, cx);
3907        self.follower_states.insert(
3908            leader_id,
3909            FollowerState {
3910                center_pane: pane.clone(),
3911                dock_pane: None,
3912                active_view_id: None,
3913                items_by_leader_view_id: Default::default(),
3914            },
3915        );
3916        cx.notify();
3917
3918        match leader_id {
3919            CollaboratorId::PeerId(leader_peer_id) => {
3920                let room_id = self.active_call()?.read(cx).room()?.read(cx).id();
3921                let project_id = self.project.read(cx).remote_id();
3922                let request = self.app_state.client.request(proto::Follow {
3923                    room_id,
3924                    project_id,
3925                    leader_id: Some(leader_peer_id),
3926                });
3927
3928                Some(cx.spawn_in(window, async move |this, cx| {
3929                    let response = request.await?;
3930                    this.update(cx, |this, _| {
3931                        let state = this
3932                            .follower_states
3933                            .get_mut(&leader_id)
3934                            .ok_or_else(|| anyhow!("following interrupted"))?;
3935                        state.active_view_id = response
3936                            .active_view
3937                            .as_ref()
3938                            .and_then(|view| ViewId::from_proto(view.id.clone()?).ok());
3939                        Ok::<_, anyhow::Error>(())
3940                    })??;
3941                    if let Some(view) = response.active_view {
3942                        Self::add_view_from_leader(this.clone(), leader_peer_id, &view, cx).await?;
3943                    }
3944                    this.update_in(cx, |this, window, cx| {
3945                        this.leader_updated(leader_id, window, cx)
3946                    })?;
3947                    Ok(())
3948                }))
3949            }
3950            CollaboratorId::Agent => {
3951                self.leader_updated(leader_id, window, cx)?;
3952                Some(Task::ready(Ok(())))
3953            }
3954        }
3955    }
3956
3957    pub fn follow_next_collaborator(
3958        &mut self,
3959        _: &FollowNextCollaborator,
3960        window: &mut Window,
3961        cx: &mut Context<Self>,
3962    ) {
3963        let collaborators = self.project.read(cx).collaborators();
3964        let next_leader_id = if let Some(leader_id) = self.leader_for_pane(&self.active_pane) {
3965            let mut collaborators = collaborators.keys().copied();
3966            for peer_id in collaborators.by_ref() {
3967                if CollaboratorId::PeerId(peer_id) == leader_id {
3968                    break;
3969                }
3970            }
3971            collaborators.next().map(CollaboratorId::PeerId)
3972        } else if let Some(last_leader_id) =
3973            self.last_leaders_by_pane.get(&self.active_pane.downgrade())
3974        {
3975            match last_leader_id {
3976                CollaboratorId::PeerId(peer_id) => {
3977                    if collaborators.contains_key(peer_id) {
3978                        Some(*last_leader_id)
3979                    } else {
3980                        None
3981                    }
3982                }
3983                CollaboratorId::Agent => Some(CollaboratorId::Agent),
3984            }
3985        } else {
3986            None
3987        };
3988
3989        let pane = self.active_pane.clone();
3990        let Some(leader_id) = next_leader_id.or_else(|| {
3991            Some(CollaboratorId::PeerId(
3992                collaborators.keys().copied().next()?,
3993            ))
3994        }) else {
3995            return;
3996        };
3997        if self.unfollow_in_pane(&pane, window, cx) == Some(leader_id) {
3998            return;
3999        }
4000        if let Some(task) = self.start_following(leader_id, window, cx) {
4001            task.detach_and_log_err(cx)
4002        }
4003    }
4004
4005    pub fn follow(
4006        &mut self,
4007        leader_id: impl Into<CollaboratorId>,
4008        window: &mut Window,
4009        cx: &mut Context<Self>,
4010    ) {
4011        let leader_id = leader_id.into();
4012
4013        if let CollaboratorId::PeerId(peer_id) = leader_id {
4014            let Some(room) = ActiveCall::global(cx).read(cx).room() else {
4015                return;
4016            };
4017            let room = room.read(cx);
4018            let Some(remote_participant) = room.remote_participant_for_peer_id(peer_id) else {
4019                return;
4020            };
4021
4022            let project = self.project.read(cx);
4023
4024            let other_project_id = match remote_participant.location {
4025                call::ParticipantLocation::External => None,
4026                call::ParticipantLocation::UnsharedProject => None,
4027                call::ParticipantLocation::SharedProject { project_id } => {
4028                    if Some(project_id) == project.remote_id() {
4029                        None
4030                    } else {
4031                        Some(project_id)
4032                    }
4033                }
4034            };
4035
4036            // if they are active in another project, follow there.
4037            if let Some(project_id) = other_project_id {
4038                let app_state = self.app_state.clone();
4039                crate::join_in_room_project(project_id, remote_participant.user.id, app_state, cx)
4040                    .detach_and_log_err(cx);
4041            }
4042        }
4043
4044        // if you're already following, find the right pane and focus it.
4045        if let Some(follower_state) = self.follower_states.get(&leader_id) {
4046            window.focus(&follower_state.pane().focus_handle(cx));
4047
4048            return;
4049        }
4050
4051        // Otherwise, follow.
4052        if let Some(task) = self.start_following(leader_id, window, cx) {
4053            task.detach_and_log_err(cx)
4054        }
4055    }
4056
4057    pub fn unfollow(
4058        &mut self,
4059        leader_id: impl Into<CollaboratorId>,
4060        window: &mut Window,
4061        cx: &mut Context<Self>,
4062    ) -> Option<()> {
4063        cx.notify();
4064
4065        let leader_id = leader_id.into();
4066        let state = self.follower_states.remove(&leader_id)?;
4067        for (_, item) in state.items_by_leader_view_id {
4068            item.view.set_leader_id(None, window, cx);
4069        }
4070
4071        if let CollaboratorId::PeerId(leader_peer_id) = leader_id {
4072            let project_id = self.project.read(cx).remote_id();
4073            let room_id = self.active_call()?.read(cx).room()?.read(cx).id();
4074            self.app_state
4075                .client
4076                .send(proto::Unfollow {
4077                    room_id,
4078                    project_id,
4079                    leader_id: Some(leader_peer_id),
4080                })
4081                .log_err();
4082        }
4083
4084        Some(())
4085    }
4086
4087    pub fn is_being_followed(&self, id: impl Into<CollaboratorId>) -> bool {
4088        self.follower_states.contains_key(&id.into())
4089    }
4090
4091    fn active_item_path_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4092        cx.emit(Event::ActiveItemChanged);
4093        let active_entry = self.active_project_path(cx);
4094        self.project
4095            .update(cx, |project, cx| project.set_active_path(active_entry, cx));
4096
4097        self.update_window_title(window, cx);
4098    }
4099
4100    fn update_window_title(&mut self, window: &mut Window, cx: &mut App) {
4101        let project = self.project().read(cx);
4102        let mut title = String::new();
4103
4104        for (i, name) in project.worktree_root_names(cx).enumerate() {
4105            if i > 0 {
4106                title.push_str(", ");
4107            }
4108            title.push_str(name);
4109        }
4110
4111        if title.is_empty() {
4112            title = "empty project".to_string();
4113        }
4114
4115        if let Some(path) = self.active_item(cx).and_then(|item| item.project_path(cx)) {
4116            let filename = path
4117                .path
4118                .file_name()
4119                .map(|s| s.to_string_lossy())
4120                .or_else(|| {
4121                    Some(Cow::Borrowed(
4122                        project
4123                            .worktree_for_id(path.worktree_id, cx)?
4124                            .read(cx)
4125                            .root_name(),
4126                    ))
4127                });
4128
4129            if let Some(filename) = filename {
4130                title.push_str("");
4131                title.push_str(filename.as_ref());
4132            }
4133        }
4134
4135        if project.is_via_collab() {
4136            title.push_str("");
4137        } else if project.is_shared() {
4138            title.push_str("");
4139        }
4140
4141        window.set_window_title(&title);
4142    }
4143
4144    fn update_window_edited(&mut self, window: &mut Window, cx: &mut App) {
4145        let is_edited = !self.project.read(cx).is_disconnected(cx) && !self.dirty_items.is_empty();
4146        if is_edited != self.window_edited {
4147            self.window_edited = is_edited;
4148            window.set_window_edited(self.window_edited)
4149        }
4150    }
4151
4152    fn update_item_dirty_state(
4153        &mut self,
4154        item: &dyn ItemHandle,
4155        window: &mut Window,
4156        cx: &mut App,
4157    ) {
4158        let is_dirty = item.is_dirty(cx);
4159        let item_id = item.item_id();
4160        let was_dirty = self.dirty_items.contains_key(&item_id);
4161        if is_dirty == was_dirty {
4162            return;
4163        }
4164        if was_dirty {
4165            self.dirty_items.remove(&item_id);
4166            self.update_window_edited(window, cx);
4167            return;
4168        }
4169        if let Some(window_handle) = window.window_handle().downcast::<Self>() {
4170            let s = item.on_release(
4171                cx,
4172                Box::new(move |cx| {
4173                    window_handle
4174                        .update(cx, |this, window, cx| {
4175                            this.dirty_items.remove(&item_id);
4176                            this.update_window_edited(window, cx)
4177                        })
4178                        .ok();
4179                }),
4180            );
4181            self.dirty_items.insert(item_id, s);
4182            self.update_window_edited(window, cx);
4183        }
4184    }
4185
4186    fn render_notifications(&self, _window: &mut Window, _cx: &mut Context<Self>) -> Option<Div> {
4187        if self.notifications.is_empty() {
4188            None
4189        } else {
4190            Some(
4191                div()
4192                    .absolute()
4193                    .right_3()
4194                    .bottom_3()
4195                    .w_112()
4196                    .h_full()
4197                    .flex()
4198                    .flex_col()
4199                    .justify_end()
4200                    .gap_2()
4201                    .children(
4202                        self.notifications
4203                            .iter()
4204                            .map(|(_, notification)| notification.clone().into_any()),
4205                    ),
4206            )
4207        }
4208    }
4209
4210    // RPC handlers
4211
4212    fn active_view_for_follower(
4213        &self,
4214        follower_project_id: Option<u64>,
4215        window: &mut Window,
4216        cx: &mut Context<Self>,
4217    ) -> Option<proto::View> {
4218        let (item, panel_id) = self.active_item_for_followers(window, cx);
4219        let item = item?;
4220        let leader_id = self
4221            .pane_for(&*item)
4222            .and_then(|pane| self.leader_for_pane(&pane));
4223        let leader_peer_id = match leader_id {
4224            Some(CollaboratorId::PeerId(peer_id)) => Some(peer_id),
4225            Some(CollaboratorId::Agent) | None => None,
4226        };
4227
4228        let item_handle = item.to_followable_item_handle(cx)?;
4229        let id = item_handle.remote_id(&self.app_state.client, window, cx)?;
4230        let variant = item_handle.to_state_proto(window, cx)?;
4231
4232        if item_handle.is_project_item(window, cx)
4233            && (follower_project_id.is_none()
4234                || follower_project_id != self.project.read(cx).remote_id())
4235        {
4236            return None;
4237        }
4238
4239        Some(proto::View {
4240            id: id.to_proto(),
4241            leader_id: leader_peer_id,
4242            variant: Some(variant),
4243            panel_id: panel_id.map(|id| id as i32),
4244        })
4245    }
4246
4247    fn handle_follow(
4248        &mut self,
4249        follower_project_id: Option<u64>,
4250        window: &mut Window,
4251        cx: &mut Context<Self>,
4252    ) -> proto::FollowResponse {
4253        let active_view = self.active_view_for_follower(follower_project_id, window, cx);
4254
4255        cx.notify();
4256        proto::FollowResponse {
4257            // TODO: Remove after version 0.145.x stabilizes.
4258            active_view_id: active_view.as_ref().and_then(|view| view.id.clone()),
4259            views: active_view.iter().cloned().collect(),
4260            active_view,
4261        }
4262    }
4263
4264    fn handle_update_followers(
4265        &mut self,
4266        leader_id: PeerId,
4267        message: proto::UpdateFollowers,
4268        _window: &mut Window,
4269        _cx: &mut Context<Self>,
4270    ) {
4271        self.leader_updates_tx
4272            .unbounded_send((leader_id, message))
4273            .ok();
4274    }
4275
4276    async fn process_leader_update(
4277        this: &WeakEntity<Self>,
4278        leader_id: PeerId,
4279        update: proto::UpdateFollowers,
4280        cx: &mut AsyncWindowContext,
4281    ) -> Result<()> {
4282        match update.variant.ok_or_else(|| anyhow!("invalid update"))? {
4283            proto::update_followers::Variant::CreateView(view) => {
4284                let view_id = ViewId::from_proto(view.id.clone().context("invalid view id")?)?;
4285                let should_add_view = this.update(cx, |this, _| {
4286                    if let Some(state) = this.follower_states.get_mut(&leader_id.into()) {
4287                        anyhow::Ok(!state.items_by_leader_view_id.contains_key(&view_id))
4288                    } else {
4289                        anyhow::Ok(false)
4290                    }
4291                })??;
4292
4293                if should_add_view {
4294                    Self::add_view_from_leader(this.clone(), leader_id, &view, cx).await?
4295                }
4296            }
4297            proto::update_followers::Variant::UpdateActiveView(update_active_view) => {
4298                let should_add_view = this.update(cx, |this, _| {
4299                    if let Some(state) = this.follower_states.get_mut(&leader_id.into()) {
4300                        state.active_view_id = update_active_view
4301                            .view
4302                            .as_ref()
4303                            .and_then(|view| ViewId::from_proto(view.id.clone()?).ok());
4304
4305                        if state.active_view_id.is_some_and(|view_id| {
4306                            !state.items_by_leader_view_id.contains_key(&view_id)
4307                        }) {
4308                            anyhow::Ok(true)
4309                        } else {
4310                            anyhow::Ok(false)
4311                        }
4312                    } else {
4313                        anyhow::Ok(false)
4314                    }
4315                })??;
4316
4317                if should_add_view {
4318                    if let Some(view) = update_active_view.view {
4319                        Self::add_view_from_leader(this.clone(), leader_id, &view, cx).await?
4320                    }
4321                }
4322            }
4323            proto::update_followers::Variant::UpdateView(update_view) => {
4324                let variant = update_view
4325                    .variant
4326                    .ok_or_else(|| anyhow!("missing update view variant"))?;
4327                let id = update_view
4328                    .id
4329                    .ok_or_else(|| anyhow!("missing update view id"))?;
4330                let mut tasks = Vec::new();
4331                this.update_in(cx, |this, window, cx| {
4332                    let project = this.project.clone();
4333                    if let Some(state) = this.follower_states.get(&leader_id.into()) {
4334                        let view_id = ViewId::from_proto(id.clone())?;
4335                        if let Some(item) = state.items_by_leader_view_id.get(&view_id) {
4336                            tasks.push(item.view.apply_update_proto(
4337                                &project,
4338                                variant.clone(),
4339                                window,
4340                                cx,
4341                            ));
4342                        }
4343                    }
4344                    anyhow::Ok(())
4345                })??;
4346                try_join_all(tasks).await.log_err();
4347            }
4348        }
4349        this.update_in(cx, |this, window, cx| {
4350            this.leader_updated(leader_id, window, cx)
4351        })?;
4352        Ok(())
4353    }
4354
4355    async fn add_view_from_leader(
4356        this: WeakEntity<Self>,
4357        leader_id: PeerId,
4358        view: &proto::View,
4359        cx: &mut AsyncWindowContext,
4360    ) -> Result<()> {
4361        let this = this.upgrade().context("workspace dropped")?;
4362
4363        let Some(id) = view.id.clone() else {
4364            return Err(anyhow!("no id for view"));
4365        };
4366        let id = ViewId::from_proto(id)?;
4367        let panel_id = view.panel_id.and_then(proto::PanelId::from_i32);
4368
4369        let pane = this.update(cx, |this, _cx| {
4370            let state = this
4371                .follower_states
4372                .get(&leader_id.into())
4373                .context("stopped following")?;
4374            anyhow::Ok(state.pane().clone())
4375        })??;
4376        let existing_item = pane.update_in(cx, |pane, window, cx| {
4377            let client = this.read(cx).client().clone();
4378            pane.items().find_map(|item| {
4379                let item = item.to_followable_item_handle(cx)?;
4380                if item.remote_id(&client, window, cx) == Some(id) {
4381                    Some(item)
4382                } else {
4383                    None
4384                }
4385            })
4386        })?;
4387        let item = if let Some(existing_item) = existing_item {
4388            existing_item
4389        } else {
4390            let variant = view.variant.clone();
4391            if variant.is_none() {
4392                Err(anyhow!("missing view variant"))?;
4393            }
4394
4395            let task = cx.update(|window, cx| {
4396                FollowableViewRegistry::from_state_proto(this.clone(), id, variant, window, cx)
4397            })?;
4398
4399            let Some(task) = task else {
4400                return Err(anyhow!(
4401                    "failed to construct view from leader (maybe from a different version of zed?)"
4402                ));
4403            };
4404
4405            let mut new_item = task.await?;
4406            pane.update_in(cx, |pane, window, cx| {
4407                let mut item_to_remove = None;
4408                for (ix, item) in pane.items().enumerate() {
4409                    if let Some(item) = item.to_followable_item_handle(cx) {
4410                        match new_item.dedup(item.as_ref(), window, cx) {
4411                            Some(item::Dedup::KeepExisting) => {
4412                                new_item =
4413                                    item.boxed_clone().to_followable_item_handle(cx).unwrap();
4414                                break;
4415                            }
4416                            Some(item::Dedup::ReplaceExisting) => {
4417                                item_to_remove = Some((ix, item.item_id()));
4418                                break;
4419                            }
4420                            None => {}
4421                        }
4422                    }
4423                }
4424
4425                if let Some((ix, id)) = item_to_remove {
4426                    pane.remove_item(id, false, false, window, cx);
4427                    pane.add_item(new_item.boxed_clone(), false, false, Some(ix), window, cx);
4428                }
4429            })?;
4430
4431            new_item
4432        };
4433
4434        this.update_in(cx, |this, window, cx| {
4435            let state = this.follower_states.get_mut(&leader_id.into())?;
4436            item.set_leader_id(Some(leader_id.into()), window, cx);
4437            state.items_by_leader_view_id.insert(
4438                id,
4439                FollowerView {
4440                    view: item,
4441                    location: panel_id,
4442                },
4443            );
4444
4445            Some(())
4446        })?;
4447
4448        Ok(())
4449    }
4450
4451    fn handle_agent_location_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4452        let Some(follower_state) = self.follower_states.get_mut(&CollaboratorId::Agent) else {
4453            return;
4454        };
4455
4456        if let Some(agent_location) = self.project.read(cx).agent_location() {
4457            let buffer_entity_id = agent_location.buffer.entity_id();
4458            let view_id = ViewId {
4459                creator: CollaboratorId::Agent,
4460                id: buffer_entity_id.as_u64(),
4461            };
4462            follower_state.active_view_id = Some(view_id);
4463
4464            let item = match follower_state.items_by_leader_view_id.entry(view_id) {
4465                hash_map::Entry::Occupied(entry) => Some(entry.into_mut()),
4466                hash_map::Entry::Vacant(entry) => {
4467                    let existing_view =
4468                        follower_state
4469                            .center_pane
4470                            .read(cx)
4471                            .items()
4472                            .find_map(|item| {
4473                                let item = item.to_followable_item_handle(cx)?;
4474                                if item.is_singleton(cx)
4475                                    && item.project_item_model_ids(cx).as_slice()
4476                                        == [buffer_entity_id]
4477                                {
4478                                    Some(item)
4479                                } else {
4480                                    None
4481                                }
4482                            });
4483                    let view = existing_view.or_else(|| {
4484                        agent_location.buffer.upgrade().and_then(|buffer| {
4485                            cx.update_default_global(|registry: &mut ProjectItemRegistry, cx| {
4486                                registry.build_item(buffer, self.project.clone(), None, window, cx)
4487                            })?
4488                            .to_followable_item_handle(cx)
4489                        })
4490                    });
4491
4492                    if let Some(view) = view {
4493                        Some(entry.insert(FollowerView {
4494                            view,
4495                            location: None,
4496                        }))
4497                    } else {
4498                        None
4499                    }
4500                }
4501            };
4502
4503            if let Some(item) = item {
4504                item.view
4505                    .set_leader_id(Some(CollaboratorId::Agent), window, cx);
4506                item.view
4507                    .update_agent_location(agent_location.position, window, cx);
4508            }
4509        } else {
4510            follower_state.active_view_id = None;
4511        }
4512
4513        self.leader_updated(CollaboratorId::Agent, window, cx);
4514    }
4515
4516    pub fn update_active_view_for_followers(&mut self, window: &mut Window, cx: &mut App) {
4517        let mut is_project_item = true;
4518        let mut update = proto::UpdateActiveView::default();
4519        if window.is_window_active() {
4520            let (active_item, panel_id) = self.active_item_for_followers(window, cx);
4521
4522            if let Some(item) = active_item {
4523                if item.item_focus_handle(cx).contains_focused(window, cx) {
4524                    let leader_id = self
4525                        .pane_for(&*item)
4526                        .and_then(|pane| self.leader_for_pane(&pane));
4527                    let leader_peer_id = match leader_id {
4528                        Some(CollaboratorId::PeerId(peer_id)) => Some(peer_id),
4529                        Some(CollaboratorId::Agent) | None => None,
4530                    };
4531
4532                    if let Some(item) = item.to_followable_item_handle(cx) {
4533                        let id = item
4534                            .remote_id(&self.app_state.client, window, cx)
4535                            .map(|id| id.to_proto());
4536
4537                        if let Some(id) = id.clone() {
4538                            if let Some(variant) = item.to_state_proto(window, cx) {
4539                                let view = Some(proto::View {
4540                                    id: id.clone(),
4541                                    leader_id: leader_peer_id,
4542                                    variant: Some(variant),
4543                                    panel_id: panel_id.map(|id| id as i32),
4544                                });
4545
4546                                is_project_item = item.is_project_item(window, cx);
4547                                update = proto::UpdateActiveView {
4548                                    view,
4549                                    // TODO: Remove after version 0.145.x stabilizes.
4550                                    id: id.clone(),
4551                                    leader_id: leader_peer_id,
4552                                };
4553                            }
4554                        };
4555                    }
4556                }
4557            }
4558        }
4559
4560        let active_view_id = update.view.as_ref().and_then(|view| view.id.as_ref());
4561        if active_view_id != self.last_active_view_id.as_ref() {
4562            self.last_active_view_id = active_view_id.cloned();
4563            self.update_followers(
4564                is_project_item,
4565                proto::update_followers::Variant::UpdateActiveView(update),
4566                window,
4567                cx,
4568            );
4569        }
4570    }
4571
4572    fn active_item_for_followers(
4573        &self,
4574        window: &mut Window,
4575        cx: &mut App,
4576    ) -> (Option<Box<dyn ItemHandle>>, Option<proto::PanelId>) {
4577        let mut active_item = None;
4578        let mut panel_id = None;
4579        for dock in self.all_docks() {
4580            if dock.focus_handle(cx).contains_focused(window, cx) {
4581                if let Some(panel) = dock.read(cx).active_panel() {
4582                    if let Some(pane) = panel.pane(cx) {
4583                        if let Some(item) = pane.read(cx).active_item() {
4584                            active_item = Some(item);
4585                            panel_id = panel.remote_id();
4586                            break;
4587                        }
4588                    }
4589                }
4590            }
4591        }
4592
4593        if active_item.is_none() {
4594            active_item = self.active_pane().read(cx).active_item();
4595        }
4596        (active_item, panel_id)
4597    }
4598
4599    fn update_followers(
4600        &self,
4601        project_only: bool,
4602        update: proto::update_followers::Variant,
4603        _: &mut Window,
4604        cx: &mut App,
4605    ) -> Option<()> {
4606        // If this update only applies to for followers in the current project,
4607        // then skip it unless this project is shared. If it applies to all
4608        // followers, regardless of project, then set `project_id` to none,
4609        // indicating that it goes to all followers.
4610        let project_id = if project_only {
4611            Some(self.project.read(cx).remote_id()?)
4612        } else {
4613            None
4614        };
4615        self.app_state().workspace_store.update(cx, |store, cx| {
4616            store.update_followers(project_id, update, cx)
4617        })
4618    }
4619
4620    pub fn leader_for_pane(&self, pane: &Entity<Pane>) -> Option<CollaboratorId> {
4621        self.follower_states.iter().find_map(|(leader_id, state)| {
4622            if state.center_pane == *pane || state.dock_pane.as_ref() == Some(pane) {
4623                Some(*leader_id)
4624            } else {
4625                None
4626            }
4627        })
4628    }
4629
4630    fn leader_updated(
4631        &mut self,
4632        leader_id: impl Into<CollaboratorId>,
4633        window: &mut Window,
4634        cx: &mut Context<Self>,
4635    ) -> Option<Box<dyn ItemHandle>> {
4636        cx.notify();
4637
4638        let leader_id = leader_id.into();
4639        let (panel_id, item) = match leader_id {
4640            CollaboratorId::PeerId(peer_id) => self.active_item_for_peer(peer_id, window, cx)?,
4641            CollaboratorId::Agent => (None, self.active_item_for_agent()?),
4642        };
4643
4644        let state = self.follower_states.get(&leader_id)?;
4645        let mut transfer_focus = state.center_pane.read(cx).has_focus(window, cx);
4646        let pane;
4647        if let Some(panel_id) = panel_id {
4648            pane = self
4649                .activate_panel_for_proto_id(panel_id, window, cx)?
4650                .pane(cx)?;
4651            let state = self.follower_states.get_mut(&leader_id)?;
4652            state.dock_pane = Some(pane.clone());
4653        } else {
4654            pane = state.center_pane.clone();
4655            let state = self.follower_states.get_mut(&leader_id)?;
4656            if let Some(dock_pane) = state.dock_pane.take() {
4657                transfer_focus |= dock_pane.focus_handle(cx).contains_focused(window, cx);
4658            }
4659        }
4660
4661        pane.update(cx, |pane, cx| {
4662            let focus_active_item = pane.has_focus(window, cx) || transfer_focus;
4663            if let Some(index) = pane.index_for_item(item.as_ref()) {
4664                pane.activate_item(index, false, false, window, cx);
4665            } else {
4666                pane.add_item(item.boxed_clone(), false, false, None, window, cx)
4667            }
4668
4669            if focus_active_item {
4670                pane.focus_active_item(window, cx)
4671            }
4672        });
4673
4674        Some(item)
4675    }
4676
4677    fn active_item_for_agent(&self) -> Option<Box<dyn ItemHandle>> {
4678        let state = self.follower_states.get(&CollaboratorId::Agent)?;
4679        let active_view_id = state.active_view_id?;
4680        Some(
4681            state
4682                .items_by_leader_view_id
4683                .get(&active_view_id)?
4684                .view
4685                .boxed_clone(),
4686        )
4687    }
4688
4689    fn active_item_for_peer(
4690        &self,
4691        peer_id: PeerId,
4692        window: &mut Window,
4693        cx: &mut Context<Self>,
4694    ) -> Option<(Option<PanelId>, Box<dyn ItemHandle>)> {
4695        let call = self.active_call()?;
4696        let room = call.read(cx).room()?.read(cx);
4697        let participant = room.remote_participant_for_peer_id(peer_id)?;
4698        let leader_in_this_app;
4699        let leader_in_this_project;
4700        match participant.location {
4701            call::ParticipantLocation::SharedProject { project_id } => {
4702                leader_in_this_app = true;
4703                leader_in_this_project = Some(project_id) == self.project.read(cx).remote_id();
4704            }
4705            call::ParticipantLocation::UnsharedProject => {
4706                leader_in_this_app = true;
4707                leader_in_this_project = false;
4708            }
4709            call::ParticipantLocation::External => {
4710                leader_in_this_app = false;
4711                leader_in_this_project = false;
4712            }
4713        };
4714        let state = self.follower_states.get(&peer_id.into())?;
4715        let mut item_to_activate = None;
4716        if let (Some(active_view_id), true) = (state.active_view_id, leader_in_this_app) {
4717            if let Some(item) = state.items_by_leader_view_id.get(&active_view_id) {
4718                if leader_in_this_project || !item.view.is_project_item(window, cx) {
4719                    item_to_activate = Some((item.location, item.view.boxed_clone()));
4720                }
4721            }
4722        } else if let Some(shared_screen) =
4723            self.shared_screen_for_peer(peer_id, &state.center_pane, window, cx)
4724        {
4725            item_to_activate = Some((None, Box::new(shared_screen)));
4726        }
4727        item_to_activate
4728    }
4729
4730    fn shared_screen_for_peer(
4731        &self,
4732        peer_id: PeerId,
4733        pane: &Entity<Pane>,
4734        window: &mut Window,
4735        cx: &mut App,
4736    ) -> Option<Entity<SharedScreen>> {
4737        let call = self.active_call()?;
4738        let room = call.read(cx).room()?.clone();
4739        let participant = room.read(cx).remote_participant_for_peer_id(peer_id)?;
4740        let track = participant.video_tracks.values().next()?.clone();
4741        let user = participant.user.clone();
4742
4743        for item in pane.read(cx).items_of_type::<SharedScreen>() {
4744            if item.read(cx).peer_id == peer_id {
4745                return Some(item);
4746            }
4747        }
4748
4749        Some(cx.new(|cx| SharedScreen::new(track, peer_id, user.clone(), room.clone(), window, cx)))
4750    }
4751
4752    pub fn on_window_activation_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4753        if window.is_window_active() {
4754            self.update_active_view_for_followers(window, cx);
4755
4756            if let Some(database_id) = self.database_id {
4757                cx.background_spawn(persistence::DB.update_timestamp(database_id))
4758                    .detach();
4759            }
4760        } else {
4761            for pane in &self.panes {
4762                pane.update(cx, |pane, cx| {
4763                    if let Some(item) = pane.active_item() {
4764                        item.workspace_deactivated(window, cx);
4765                    }
4766                    for item in pane.items() {
4767                        if matches!(
4768                            item.workspace_settings(cx).autosave,
4769                            AutosaveSetting::OnWindowChange | AutosaveSetting::OnFocusChange
4770                        ) {
4771                            Pane::autosave_item(item.as_ref(), self.project.clone(), window, cx)
4772                                .detach_and_log_err(cx);
4773                        }
4774                    }
4775                });
4776            }
4777        }
4778    }
4779
4780    pub fn active_call(&self) -> Option<&Entity<ActiveCall>> {
4781        self.active_call.as_ref().map(|(call, _)| call)
4782    }
4783
4784    fn on_active_call_event(
4785        &mut self,
4786        _: &Entity<ActiveCall>,
4787        event: &call::room::Event,
4788        window: &mut Window,
4789        cx: &mut Context<Self>,
4790    ) {
4791        match event {
4792            call::room::Event::ParticipantLocationChanged { participant_id }
4793            | call::room::Event::RemoteVideoTracksChanged { participant_id } => {
4794                self.leader_updated(participant_id, window, cx);
4795            }
4796            _ => {}
4797        }
4798    }
4799
4800    pub fn database_id(&self) -> Option<WorkspaceId> {
4801        self.database_id
4802    }
4803
4804    pub fn session_id(&self) -> Option<String> {
4805        self.session_id.clone()
4806    }
4807
4808    fn local_paths(&self, cx: &App) -> Option<Vec<Arc<Path>>> {
4809        let project = self.project().read(cx);
4810
4811        if project.is_local() {
4812            Some(
4813                project
4814                    .visible_worktrees(cx)
4815                    .map(|worktree| worktree.read(cx).abs_path())
4816                    .collect::<Vec<_>>(),
4817            )
4818        } else {
4819            None
4820        }
4821    }
4822
4823    fn remove_panes(&mut self, member: Member, window: &mut Window, cx: &mut Context<Workspace>) {
4824        match member {
4825            Member::Axis(PaneAxis { members, .. }) => {
4826                for child in members.iter() {
4827                    self.remove_panes(child.clone(), window, cx)
4828                }
4829            }
4830            Member::Pane(pane) => {
4831                self.force_remove_pane(&pane, &None, window, cx);
4832            }
4833        }
4834    }
4835
4836    fn remove_from_session(&mut self, window: &mut Window, cx: &mut App) -> Task<()> {
4837        self.session_id.take();
4838        self.serialize_workspace_internal(window, cx)
4839    }
4840
4841    fn force_remove_pane(
4842        &mut self,
4843        pane: &Entity<Pane>,
4844        focus_on: &Option<Entity<Pane>>,
4845        window: &mut Window,
4846        cx: &mut Context<Workspace>,
4847    ) {
4848        self.panes.retain(|p| p != pane);
4849        if let Some(focus_on) = focus_on {
4850            focus_on.update(cx, |pane, cx| window.focus(&pane.focus_handle(cx)));
4851        } else {
4852            if self.active_pane() == pane {
4853                self.panes
4854                    .last()
4855                    .unwrap()
4856                    .update(cx, |pane, cx| window.focus(&pane.focus_handle(cx)));
4857            }
4858        }
4859        if self.last_active_center_pane == Some(pane.downgrade()) {
4860            self.last_active_center_pane = None;
4861        }
4862        cx.notify();
4863    }
4864
4865    fn serialize_workspace(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4866        if self._schedule_serialize.is_none() {
4867            self._schedule_serialize = Some(cx.spawn_in(window, async move |this, cx| {
4868                cx.background_executor()
4869                    .timer(Duration::from_millis(100))
4870                    .await;
4871                this.update_in(cx, |this, window, cx| {
4872                    this.serialize_workspace_internal(window, cx).detach();
4873                    this._schedule_serialize.take();
4874                })
4875                .log_err();
4876            }));
4877        }
4878    }
4879
4880    fn serialize_workspace_internal(&self, window: &mut Window, cx: &mut App) -> Task<()> {
4881        let Some(database_id) = self.database_id() else {
4882            return Task::ready(());
4883        };
4884
4885        fn serialize_pane_handle(
4886            pane_handle: &Entity<Pane>,
4887            window: &mut Window,
4888            cx: &mut App,
4889        ) -> SerializedPane {
4890            let (items, active, pinned_count) = {
4891                let pane = pane_handle.read(cx);
4892                let active_item_id = pane.active_item().map(|item| item.item_id());
4893                (
4894                    pane.items()
4895                        .filter_map(|handle| {
4896                            let handle = handle.to_serializable_item_handle(cx)?;
4897
4898                            Some(SerializedItem {
4899                                kind: Arc::from(handle.serialized_item_kind()),
4900                                item_id: handle.item_id().as_u64(),
4901                                active: Some(handle.item_id()) == active_item_id,
4902                                preview: pane.is_active_preview_item(handle.item_id()),
4903                            })
4904                        })
4905                        .collect::<Vec<_>>(),
4906                    pane.has_focus(window, cx),
4907                    pane.pinned_count(),
4908                )
4909            };
4910
4911            SerializedPane::new(items, active, pinned_count)
4912        }
4913
4914        fn build_serialized_pane_group(
4915            pane_group: &Member,
4916            window: &mut Window,
4917            cx: &mut App,
4918        ) -> SerializedPaneGroup {
4919            match pane_group {
4920                Member::Axis(PaneAxis {
4921                    axis,
4922                    members,
4923                    flexes,
4924                    bounding_boxes: _,
4925                }) => SerializedPaneGroup::Group {
4926                    axis: SerializedAxis(*axis),
4927                    children: members
4928                        .iter()
4929                        .map(|member| build_serialized_pane_group(member, window, cx))
4930                        .collect::<Vec<_>>(),
4931                    flexes: Some(flexes.lock().clone()),
4932                },
4933                Member::Pane(pane_handle) => {
4934                    SerializedPaneGroup::Pane(serialize_pane_handle(pane_handle, window, cx))
4935                }
4936            }
4937        }
4938
4939        fn build_serialized_docks(
4940            this: &Workspace,
4941            window: &mut Window,
4942            cx: &mut App,
4943        ) -> DockStructure {
4944            let left_dock = this.left_dock.read(cx);
4945            let left_visible = left_dock.is_open();
4946            let left_active_panel = left_dock
4947                .active_panel()
4948                .map(|panel| panel.persistent_name().to_string());
4949            let left_dock_zoom = left_dock
4950                .active_panel()
4951                .map(|panel| panel.is_zoomed(window, cx))
4952                .unwrap_or(false);
4953
4954            let right_dock = this.right_dock.read(cx);
4955            let right_visible = right_dock.is_open();
4956            let right_active_panel = right_dock
4957                .active_panel()
4958                .map(|panel| panel.persistent_name().to_string());
4959            let right_dock_zoom = right_dock
4960                .active_panel()
4961                .map(|panel| panel.is_zoomed(window, cx))
4962                .unwrap_or(false);
4963
4964            let bottom_dock = this.bottom_dock.read(cx);
4965            let bottom_visible = bottom_dock.is_open();
4966            let bottom_active_panel = bottom_dock
4967                .active_panel()
4968                .map(|panel| panel.persistent_name().to_string());
4969            let bottom_dock_zoom = bottom_dock
4970                .active_panel()
4971                .map(|panel| panel.is_zoomed(window, cx))
4972                .unwrap_or(false);
4973
4974            DockStructure {
4975                left: DockData {
4976                    visible: left_visible,
4977                    active_panel: left_active_panel,
4978                    zoom: left_dock_zoom,
4979                },
4980                right: DockData {
4981                    visible: right_visible,
4982                    active_panel: right_active_panel,
4983                    zoom: right_dock_zoom,
4984                },
4985                bottom: DockData {
4986                    visible: bottom_visible,
4987                    active_panel: bottom_active_panel,
4988                    zoom: bottom_dock_zoom,
4989                },
4990            }
4991        }
4992
4993        if let Some(location) = self.serialize_workspace_location(cx) {
4994            let breakpoints = self.project.update(cx, |project, cx| {
4995                project.breakpoint_store().read(cx).all_breakpoints(cx)
4996            });
4997
4998            let center_group = build_serialized_pane_group(&self.center.root, window, cx);
4999            let docks = build_serialized_docks(self, window, cx);
5000            let window_bounds = Some(SerializedWindowBounds(window.window_bounds()));
5001            let serialized_workspace = SerializedWorkspace {
5002                id: database_id,
5003                location,
5004                center_group,
5005                window_bounds,
5006                display: Default::default(),
5007                docks,
5008                centered_layout: self.centered_layout,
5009                session_id: self.session_id.clone(),
5010                breakpoints,
5011                window_id: Some(window.window_handle().window_id().as_u64()),
5012            };
5013
5014            return window.spawn(cx, async move |_| {
5015                persistence::DB.save_workspace(serialized_workspace).await;
5016            });
5017        }
5018        Task::ready(())
5019    }
5020
5021    fn serialize_workspace_location(&self, cx: &App) -> Option<SerializedWorkspaceLocation> {
5022        if let Some(ssh_project) = &self.serialized_ssh_project {
5023            Some(SerializedWorkspaceLocation::Ssh(ssh_project.clone()))
5024        } else if let Some(local_paths) = self.local_paths(cx) {
5025            if !local_paths.is_empty() {
5026                Some(SerializedWorkspaceLocation::from_local_paths(local_paths))
5027            } else {
5028                None
5029            }
5030        } else {
5031            None
5032        }
5033    }
5034
5035    fn update_history(&self, cx: &mut App) {
5036        let Some(id) = self.database_id() else {
5037            return;
5038        };
5039        let Some(location) = self.serialize_workspace_location(cx) else {
5040            return;
5041        };
5042        if let Some(manager) = HistoryManager::global(cx) {
5043            manager.update(cx, |this, cx| {
5044                this.update_history(id, HistoryManagerEntry::new(id, &location), cx);
5045            });
5046        }
5047    }
5048
5049    async fn serialize_items(
5050        this: &WeakEntity<Self>,
5051        items_rx: UnboundedReceiver<Box<dyn SerializableItemHandle>>,
5052        cx: &mut AsyncWindowContext,
5053    ) -> Result<()> {
5054        const CHUNK_SIZE: usize = 200;
5055
5056        let mut serializable_items = items_rx.ready_chunks(CHUNK_SIZE);
5057
5058        while let Some(items_received) = serializable_items.next().await {
5059            let unique_items =
5060                items_received
5061                    .into_iter()
5062                    .fold(HashMap::default(), |mut acc, item| {
5063                        acc.entry(item.item_id()).or_insert(item);
5064                        acc
5065                    });
5066
5067            // We use into_iter() here so that the references to the items are moved into
5068            // the tasks and not kept alive while we're sleeping.
5069            for (_, item) in unique_items.into_iter() {
5070                if let Ok(Some(task)) = this.update_in(cx, |workspace, window, cx| {
5071                    item.serialize(workspace, false, window, cx)
5072                }) {
5073                    cx.background_spawn(async move { task.await.log_err() })
5074                        .detach();
5075                }
5076            }
5077
5078            cx.background_executor()
5079                .timer(SERIALIZATION_THROTTLE_TIME)
5080                .await;
5081        }
5082
5083        Ok(())
5084    }
5085
5086    pub(crate) fn enqueue_item_serialization(
5087        &mut self,
5088        item: Box<dyn SerializableItemHandle>,
5089    ) -> Result<()> {
5090        self.serializable_items_tx
5091            .unbounded_send(item)
5092            .map_err(|err| anyhow!("failed to send serializable item over channel: {}", err))
5093    }
5094
5095    pub(crate) fn load_workspace(
5096        serialized_workspace: SerializedWorkspace,
5097        paths_to_open: Vec<Option<ProjectPath>>,
5098        window: &mut Window,
5099        cx: &mut Context<Workspace>,
5100    ) -> Task<Result<Vec<Option<Box<dyn ItemHandle>>>>> {
5101        cx.spawn_in(window, async move |workspace, cx| {
5102            let project = workspace.update(cx, |workspace, _| workspace.project().clone())?;
5103
5104            let mut center_group = None;
5105            let mut center_items = None;
5106
5107            // Traverse the splits tree and add to things
5108            if let Some((group, active_pane, items)) = serialized_workspace
5109                .center_group
5110                .deserialize(&project, serialized_workspace.id, workspace.clone(), cx)
5111                .await
5112            {
5113                center_items = Some(items);
5114                center_group = Some((group, active_pane))
5115            }
5116
5117            let mut items_by_project_path = HashMap::default();
5118            let mut item_ids_by_kind = HashMap::default();
5119            let mut all_deserialized_items = Vec::default();
5120            cx.update(|_, cx| {
5121                for item in center_items.unwrap_or_default().into_iter().flatten() {
5122                    if let Some(serializable_item_handle) = item.to_serializable_item_handle(cx) {
5123                        item_ids_by_kind
5124                            .entry(serializable_item_handle.serialized_item_kind())
5125                            .or_insert(Vec::new())
5126                            .push(item.item_id().as_u64() as ItemId);
5127                    }
5128
5129                    if let Some(project_path) = item.project_path(cx) {
5130                        items_by_project_path.insert(project_path, item.clone());
5131                    }
5132                    all_deserialized_items.push(item);
5133                }
5134            })?;
5135
5136            let opened_items = paths_to_open
5137                .into_iter()
5138                .map(|path_to_open| {
5139                    path_to_open
5140                        .and_then(|path_to_open| items_by_project_path.remove(&path_to_open))
5141                })
5142                .collect::<Vec<_>>();
5143
5144            // Remove old panes from workspace panes list
5145            workspace.update_in(cx, |workspace, window, cx| {
5146                if let Some((center_group, active_pane)) = center_group {
5147                    workspace.remove_panes(workspace.center.root.clone(), window, cx);
5148
5149                    // Swap workspace center group
5150                    workspace.center = PaneGroup::with_root(center_group);
5151                    if let Some(active_pane) = active_pane {
5152                        workspace.set_active_pane(&active_pane, window, cx);
5153                        cx.focus_self(window);
5154                    } else {
5155                        workspace.set_active_pane(&workspace.center.first_pane(), window, cx);
5156                    }
5157                }
5158
5159                let docks = serialized_workspace.docks;
5160
5161                for (dock, serialized_dock) in [
5162                    (&mut workspace.right_dock, docks.right),
5163                    (&mut workspace.left_dock, docks.left),
5164                    (&mut workspace.bottom_dock, docks.bottom),
5165                ]
5166                .iter_mut()
5167                {
5168                    dock.update(cx, |dock, cx| {
5169                        dock.serialized_dock = Some(serialized_dock.clone());
5170                        dock.restore_state(window, cx);
5171                    });
5172                }
5173
5174                cx.notify();
5175            })?;
5176
5177            let _ = project
5178                .update(cx, |project, cx| {
5179                    project
5180                        .breakpoint_store()
5181                        .update(cx, |breakpoint_store, cx| {
5182                            breakpoint_store
5183                                .with_serialized_breakpoints(serialized_workspace.breakpoints, cx)
5184                        })
5185                })?
5186                .await;
5187
5188            // Clean up all the items that have _not_ been loaded. Our ItemIds aren't stable. That means
5189            // after loading the items, we might have different items and in order to avoid
5190            // the database filling up, we delete items that haven't been loaded now.
5191            //
5192            // The items that have been loaded, have been saved after they've been added to the workspace.
5193            let clean_up_tasks = workspace.update_in(cx, |_, window, cx| {
5194                item_ids_by_kind
5195                    .into_iter()
5196                    .map(|(item_kind, loaded_items)| {
5197                        SerializableItemRegistry::cleanup(
5198                            item_kind,
5199                            serialized_workspace.id,
5200                            loaded_items,
5201                            window,
5202                            cx,
5203                        )
5204                        .log_err()
5205                    })
5206                    .collect::<Vec<_>>()
5207            })?;
5208
5209            futures::future::join_all(clean_up_tasks).await;
5210
5211            workspace
5212                .update_in(cx, |workspace, window, cx| {
5213                    // Serialize ourself to make sure our timestamps and any pane / item changes are replicated
5214                    workspace.serialize_workspace_internal(window, cx).detach();
5215
5216                    // Ensure that we mark the window as edited if we did load dirty items
5217                    workspace.update_window_edited(window, cx);
5218                })
5219                .ok();
5220
5221            Ok(opened_items)
5222        })
5223    }
5224
5225    fn actions(&self, div: Div, window: &mut Window, cx: &mut Context<Self>) -> Div {
5226        self.add_workspace_actions_listeners(div, window, cx)
5227            .on_action(cx.listener(Self::close_inactive_items_and_panes))
5228            .on_action(cx.listener(Self::close_all_items_and_panes))
5229            .on_action(cx.listener(Self::save_all))
5230            .on_action(cx.listener(Self::send_keystrokes))
5231            .on_action(cx.listener(Self::add_folder_to_project))
5232            .on_action(cx.listener(Self::follow_next_collaborator))
5233            .on_action(cx.listener(Self::close_window))
5234            .on_action(cx.listener(Self::activate_pane_at_index))
5235            .on_action(cx.listener(Self::move_item_to_pane_at_index))
5236            .on_action(cx.listener(Self::move_focused_panel_to_next_position))
5237            .on_action(cx.listener(|workspace, _: &Unfollow, window, cx| {
5238                let pane = workspace.active_pane().clone();
5239                workspace.unfollow_in_pane(&pane, window, cx);
5240            }))
5241            .on_action(cx.listener(|workspace, action: &Save, window, cx| {
5242                workspace
5243                    .save_active_item(action.save_intent.unwrap_or(SaveIntent::Save), window, cx)
5244                    .detach_and_prompt_err("Failed to save", window, cx, |_, _, _| None);
5245            }))
5246            .on_action(cx.listener(|workspace, _: &SaveWithoutFormat, window, cx| {
5247                workspace
5248                    .save_active_item(SaveIntent::SaveWithoutFormat, window, cx)
5249                    .detach_and_prompt_err("Failed to save", window, cx, |_, _, _| None);
5250            }))
5251            .on_action(cx.listener(|workspace, _: &SaveAs, window, cx| {
5252                workspace
5253                    .save_active_item(SaveIntent::SaveAs, window, cx)
5254                    .detach_and_prompt_err("Failed to save", window, cx, |_, _, _| None);
5255            }))
5256            .on_action(
5257                cx.listener(|workspace, _: &ActivatePreviousPane, window, cx| {
5258                    workspace.activate_previous_pane(window, cx)
5259                }),
5260            )
5261            .on_action(cx.listener(|workspace, _: &ActivateNextPane, window, cx| {
5262                workspace.activate_next_pane(window, cx)
5263            }))
5264            .on_action(
5265                cx.listener(|workspace, _: &ActivateNextWindow, _window, cx| {
5266                    workspace.activate_next_window(cx)
5267                }),
5268            )
5269            .on_action(
5270                cx.listener(|workspace, _: &ActivatePreviousWindow, _window, cx| {
5271                    workspace.activate_previous_window(cx)
5272                }),
5273            )
5274            .on_action(cx.listener(|workspace, _: &ActivatePaneLeft, window, cx| {
5275                workspace.activate_pane_in_direction(SplitDirection::Left, window, cx)
5276            }))
5277            .on_action(cx.listener(|workspace, _: &ActivatePaneRight, window, cx| {
5278                workspace.activate_pane_in_direction(SplitDirection::Right, window, cx)
5279            }))
5280            .on_action(cx.listener(|workspace, _: &ActivatePaneUp, window, cx| {
5281                workspace.activate_pane_in_direction(SplitDirection::Up, window, cx)
5282            }))
5283            .on_action(cx.listener(|workspace, _: &ActivatePaneDown, window, cx| {
5284                workspace.activate_pane_in_direction(SplitDirection::Down, window, cx)
5285            }))
5286            .on_action(cx.listener(|workspace, _: &ActivateNextPane, window, cx| {
5287                workspace.activate_next_pane(window, cx)
5288            }))
5289            .on_action(cx.listener(
5290                |workspace, action: &MoveItemToPaneInDirection, window, cx| {
5291                    workspace.move_item_to_pane_in_direction(action, window, cx)
5292                },
5293            ))
5294            .on_action(cx.listener(|workspace, _: &SwapPaneLeft, _, cx| {
5295                workspace.swap_pane_in_direction(SplitDirection::Left, cx)
5296            }))
5297            .on_action(cx.listener(|workspace, _: &SwapPaneRight, _, cx| {
5298                workspace.swap_pane_in_direction(SplitDirection::Right, cx)
5299            }))
5300            .on_action(cx.listener(|workspace, _: &SwapPaneUp, _, cx| {
5301                workspace.swap_pane_in_direction(SplitDirection::Up, cx)
5302            }))
5303            .on_action(cx.listener(|workspace, _: &SwapPaneDown, _, cx| {
5304                workspace.swap_pane_in_direction(SplitDirection::Down, cx)
5305            }))
5306            .on_action(cx.listener(|this, _: &ToggleLeftDock, window, cx| {
5307                this.toggle_dock(DockPosition::Left, window, cx);
5308            }))
5309            .on_action(cx.listener(
5310                |workspace: &mut Workspace, _: &ToggleRightDock, window, cx| {
5311                    workspace.toggle_dock(DockPosition::Right, window, cx);
5312                },
5313            ))
5314            .on_action(cx.listener(
5315                |workspace: &mut Workspace, _: &ToggleBottomDock, window, cx| {
5316                    workspace.toggle_dock(DockPosition::Bottom, window, cx);
5317                },
5318            ))
5319            .on_action(cx.listener(
5320                |workspace: &mut Workspace, _: &CloseActiveDock, window, cx| {
5321                    workspace.close_active_dock(window, cx);
5322                },
5323            ))
5324            .on_action(
5325                cx.listener(|workspace: &mut Workspace, _: &CloseAllDocks, window, cx| {
5326                    workspace.close_all_docks(window, cx);
5327                }),
5328            )
5329            .on_action(cx.listener(
5330                |workspace: &mut Workspace, _: &ClearAllNotifications, _, cx| {
5331                    workspace.clear_all_notifications(cx);
5332                },
5333            ))
5334            .on_action(cx.listener(
5335                |workspace: &mut Workspace, _: &SuppressNotification, _, cx| {
5336                    if let Some((notification_id, _)) = workspace.notifications.pop() {
5337                        workspace.suppress_notification(&notification_id, cx);
5338                    }
5339                },
5340            ))
5341            .on_action(cx.listener(
5342                |workspace: &mut Workspace, _: &ReopenClosedItem, window, cx| {
5343                    workspace.reopen_closed_item(window, cx).detach();
5344                },
5345            ))
5346            .on_action(cx.listener(Workspace::toggle_centered_layout))
5347            .on_action(cx.listener(Workspace::cancel))
5348    }
5349
5350    #[cfg(any(test, feature = "test-support"))]
5351    pub fn test_new(project: Entity<Project>, window: &mut Window, cx: &mut Context<Self>) -> Self {
5352        use node_runtime::NodeRuntime;
5353        use session::Session;
5354
5355        let client = project.read(cx).client();
5356        let user_store = project.read(cx).user_store();
5357
5358        let workspace_store = cx.new(|cx| WorkspaceStore::new(client.clone(), cx));
5359        let session = cx.new(|cx| AppSession::new(Session::test(), cx));
5360        window.activate_window();
5361        let app_state = Arc::new(AppState {
5362            languages: project.read(cx).languages().clone(),
5363            workspace_store,
5364            client,
5365            user_store,
5366            fs: project.read(cx).fs().clone(),
5367            build_window_options: |_, _| Default::default(),
5368            node_runtime: NodeRuntime::unavailable(),
5369            session,
5370        });
5371        let workspace = Self::new(Default::default(), project, app_state, window, cx);
5372        workspace
5373            .active_pane
5374            .update(cx, |pane, cx| window.focus(&pane.focus_handle(cx)));
5375        workspace
5376    }
5377
5378    pub fn register_action<A: Action>(
5379        &mut self,
5380        callback: impl Fn(&mut Self, &A, &mut Window, &mut Context<Self>) + 'static,
5381    ) -> &mut Self {
5382        let callback = Arc::new(callback);
5383
5384        self.workspace_actions.push(Box::new(move |div, _, cx| {
5385            let callback = callback.clone();
5386            div.on_action(cx.listener(move |workspace, event, window, cx| {
5387                (callback)(workspace, event, window, cx)
5388            }))
5389        }));
5390        self
5391    }
5392
5393    fn add_workspace_actions_listeners(
5394        &self,
5395        mut div: Div,
5396        window: &mut Window,
5397        cx: &mut Context<Self>,
5398    ) -> Div {
5399        for action in self.workspace_actions.iter() {
5400            div = (action)(div, window, cx)
5401        }
5402        div
5403    }
5404
5405    pub fn has_active_modal(&self, _: &mut Window, cx: &mut App) -> bool {
5406        self.modal_layer.read(cx).has_active_modal()
5407    }
5408
5409    pub fn active_modal<V: ManagedView + 'static>(&self, cx: &App) -> Option<Entity<V>> {
5410        self.modal_layer.read(cx).active_modal()
5411    }
5412
5413    pub fn toggle_modal<V: ModalView, B>(&mut self, window: &mut Window, cx: &mut App, build: B)
5414    where
5415        B: FnOnce(&mut Window, &mut Context<V>) -> V,
5416    {
5417        self.modal_layer.update(cx, |modal_layer, cx| {
5418            modal_layer.toggle_modal(window, cx, build)
5419        })
5420    }
5421
5422    pub fn toggle_status_toast<V: ToastView>(&mut self, entity: Entity<V>, cx: &mut App) {
5423        self.toast_layer
5424            .update(cx, |toast_layer, cx| toast_layer.toggle_toast(cx, entity))
5425    }
5426
5427    pub fn toggle_centered_layout(
5428        &mut self,
5429        _: &ToggleCenteredLayout,
5430        _: &mut Window,
5431        cx: &mut Context<Self>,
5432    ) {
5433        self.centered_layout = !self.centered_layout;
5434        if let Some(database_id) = self.database_id() {
5435            cx.background_spawn(DB.set_centered_layout(database_id, self.centered_layout))
5436                .detach_and_log_err(cx);
5437        }
5438        cx.notify();
5439    }
5440
5441    fn adjust_padding(padding: Option<f32>) -> f32 {
5442        padding
5443            .unwrap_or(Self::DEFAULT_PADDING)
5444            .clamp(0.0, Self::MAX_PADDING)
5445    }
5446
5447    fn render_dock(
5448        &self,
5449        position: DockPosition,
5450        dock: &Entity<Dock>,
5451        window: &mut Window,
5452        cx: &mut App,
5453    ) -> Option<Div> {
5454        if self.zoomed_position == Some(position) {
5455            return None;
5456        }
5457
5458        let leader_border = dock.read(cx).active_panel().and_then(|panel| {
5459            let pane = panel.pane(cx)?;
5460            let follower_states = &self.follower_states;
5461            leader_border_for_pane(follower_states, &pane, window, cx)
5462        });
5463
5464        Some(
5465            div()
5466                .flex()
5467                .flex_none()
5468                .overflow_hidden()
5469                .child(dock.clone())
5470                .children(leader_border),
5471        )
5472    }
5473
5474    pub fn for_window(window: &mut Window, _: &mut App) -> Option<Entity<Workspace>> {
5475        window.root().flatten()
5476    }
5477
5478    pub fn zoomed_item(&self) -> Option<&AnyWeakView> {
5479        self.zoomed.as_ref()
5480    }
5481
5482    pub fn activate_next_window(&mut self, cx: &mut Context<Self>) {
5483        let Some(current_window_id) = cx.active_window().map(|a| a.window_id()) else {
5484            return;
5485        };
5486        let windows = cx.windows();
5487        let Some(next_window) = windows
5488            .iter()
5489            .cycle()
5490            .skip_while(|window| window.window_id() != current_window_id)
5491            .nth(1)
5492        else {
5493            return;
5494        };
5495        next_window
5496            .update(cx, |_, window, _| window.activate_window())
5497            .ok();
5498    }
5499
5500    pub fn activate_previous_window(&mut self, cx: &mut Context<Self>) {
5501        let Some(current_window_id) = cx.active_window().map(|a| a.window_id()) else {
5502            return;
5503        };
5504        let windows = cx.windows();
5505        let Some(prev_window) = windows
5506            .iter()
5507            .rev()
5508            .cycle()
5509            .skip_while(|window| window.window_id() != current_window_id)
5510            .nth(1)
5511        else {
5512            return;
5513        };
5514        prev_window
5515            .update(cx, |_, window, _| window.activate_window())
5516            .ok();
5517    }
5518
5519    pub fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
5520        if cx.stop_active_drag(window) {
5521            return;
5522        } else if let Some((notification_id, _)) = self.notifications.pop() {
5523            dismiss_app_notification(&notification_id, cx);
5524        } else {
5525            cx.emit(Event::ClearActivityIndicator);
5526            cx.propagate();
5527        }
5528    }
5529}
5530
5531fn leader_border_for_pane(
5532    follower_states: &HashMap<CollaboratorId, FollowerState>,
5533    pane: &Entity<Pane>,
5534    _: &Window,
5535    cx: &App,
5536) -> Option<Div> {
5537    let (leader_id, _follower_state) = follower_states.iter().find_map(|(leader_id, state)| {
5538        if state.pane() == pane {
5539            Some((*leader_id, state))
5540        } else {
5541            None
5542        }
5543    })?;
5544
5545    let mut leader_color = match leader_id {
5546        CollaboratorId::PeerId(leader_peer_id) => {
5547            let room = ActiveCall::try_global(cx)?.read(cx).room()?.read(cx);
5548            let leader = room.remote_participant_for_peer_id(leader_peer_id)?;
5549
5550            cx.theme()
5551                .players()
5552                .color_for_participant(leader.participant_index.0)
5553                .cursor
5554        }
5555        CollaboratorId::Agent => cx.theme().players().agent().cursor,
5556    };
5557    leader_color.fade_out(0.3);
5558    Some(
5559        div()
5560            .absolute()
5561            .size_full()
5562            .left_0()
5563            .top_0()
5564            .border_2()
5565            .border_color(leader_color),
5566    )
5567}
5568
5569fn window_bounds_env_override() -> Option<Bounds<Pixels>> {
5570    ZED_WINDOW_POSITION
5571        .zip(*ZED_WINDOW_SIZE)
5572        .map(|(position, size)| Bounds {
5573            origin: position,
5574            size,
5575        })
5576}
5577
5578fn open_items(
5579    serialized_workspace: Option<SerializedWorkspace>,
5580    mut project_paths_to_open: Vec<(PathBuf, Option<ProjectPath>)>,
5581    window: &mut Window,
5582    cx: &mut Context<Workspace>,
5583) -> impl 'static + Future<Output = Result<Vec<Option<Result<Box<dyn ItemHandle>>>>>> + use<> {
5584    let restored_items = serialized_workspace.map(|serialized_workspace| {
5585        Workspace::load_workspace(
5586            serialized_workspace,
5587            project_paths_to_open
5588                .iter()
5589                .map(|(_, project_path)| project_path)
5590                .cloned()
5591                .collect(),
5592            window,
5593            cx,
5594        )
5595    });
5596
5597    cx.spawn_in(window, async move |workspace, cx| {
5598        let mut opened_items = Vec::with_capacity(project_paths_to_open.len());
5599
5600        if let Some(restored_items) = restored_items {
5601            let restored_items = restored_items.await?;
5602
5603            let restored_project_paths = restored_items
5604                .iter()
5605                .filter_map(|item| {
5606                    cx.update(|_, cx| item.as_ref()?.project_path(cx))
5607                        .ok()
5608                        .flatten()
5609                })
5610                .collect::<HashSet<_>>();
5611
5612            for restored_item in restored_items {
5613                opened_items.push(restored_item.map(Ok));
5614            }
5615
5616            project_paths_to_open
5617                .iter_mut()
5618                .for_each(|(_, project_path)| {
5619                    if let Some(project_path_to_open) = project_path {
5620                        if restored_project_paths.contains(project_path_to_open) {
5621                            *project_path = None;
5622                        }
5623                    }
5624                });
5625        } else {
5626            for _ in 0..project_paths_to_open.len() {
5627                opened_items.push(None);
5628            }
5629        }
5630        assert!(opened_items.len() == project_paths_to_open.len());
5631
5632        let tasks =
5633            project_paths_to_open
5634                .into_iter()
5635                .enumerate()
5636                .map(|(ix, (abs_path, project_path))| {
5637                    let workspace = workspace.clone();
5638                    cx.spawn(async move |cx| {
5639                        let file_project_path = project_path?;
5640                        let abs_path_task = workspace.update(cx, |workspace, cx| {
5641                            workspace.project().update(cx, |project, cx| {
5642                                project.resolve_abs_path(abs_path.to_string_lossy().as_ref(), cx)
5643                            })
5644                        });
5645
5646                        // We only want to open file paths here. If one of the items
5647                        // here is a directory, it was already opened further above
5648                        // with a `find_or_create_worktree`.
5649                        if let Ok(task) = abs_path_task {
5650                            if task.await.map_or(true, |p| p.is_file()) {
5651                                return Some((
5652                                    ix,
5653                                    workspace
5654                                        .update_in(cx, |workspace, window, cx| {
5655                                            workspace.open_path(
5656                                                file_project_path,
5657                                                None,
5658                                                true,
5659                                                window,
5660                                                cx,
5661                                            )
5662                                        })
5663                                        .log_err()?
5664                                        .await,
5665                                ));
5666                            }
5667                        }
5668                        None
5669                    })
5670                });
5671
5672        let tasks = tasks.collect::<Vec<_>>();
5673
5674        let tasks = futures::future::join_all(tasks);
5675        for (ix, path_open_result) in tasks.await.into_iter().flatten() {
5676            opened_items[ix] = Some(path_open_result);
5677        }
5678
5679        Ok(opened_items)
5680    })
5681}
5682
5683enum ActivateInDirectionTarget {
5684    Pane(Entity<Pane>),
5685    Dock(Entity<Dock>),
5686}
5687
5688fn notify_if_database_failed(workspace: WindowHandle<Workspace>, cx: &mut AsyncApp) {
5689    workspace
5690        .update(cx, |workspace, _, cx| {
5691            if (*db::ALL_FILE_DB_FAILED).load(std::sync::atomic::Ordering::Acquire) {
5692                struct DatabaseFailedNotification;
5693
5694                workspace.show_notification(
5695                    NotificationId::unique::<DatabaseFailedNotification>(),
5696                    cx,
5697                    |cx| {
5698                        cx.new(|cx| {
5699                            MessageNotification::new("Failed to load the database file.", cx)
5700                                .primary_message("File an Issue")
5701                                .primary_icon(IconName::Plus)
5702                                .primary_on_click(|window, cx| {
5703                                    window.dispatch_action(Box::new(FileBugReport), cx)
5704                                })
5705                        })
5706                    },
5707                );
5708            }
5709        })
5710        .log_err();
5711}
5712
5713impl Focusable for Workspace {
5714    fn focus_handle(&self, cx: &App) -> FocusHandle {
5715        self.active_pane.focus_handle(cx)
5716    }
5717}
5718
5719#[derive(Clone)]
5720struct DraggedDock(DockPosition);
5721
5722impl Render for DraggedDock {
5723    fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
5724        gpui::Empty
5725    }
5726}
5727
5728impl Render for Workspace {
5729    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
5730        let mut context = KeyContext::new_with_defaults();
5731        context.add("Workspace");
5732        context.set("keyboard_layout", cx.keyboard_layout().name().to_string());
5733        let centered_layout = self.centered_layout
5734            && self.center.panes().len() == 1
5735            && self.active_item(cx).is_some();
5736        let render_padding = |size| {
5737            (size > 0.0).then(|| {
5738                div()
5739                    .h_full()
5740                    .w(relative(size))
5741                    .bg(cx.theme().colors().editor_background)
5742                    .border_color(cx.theme().colors().pane_group_border)
5743            })
5744        };
5745        let paddings = if centered_layout {
5746            let settings = WorkspaceSettings::get_global(cx).centered_layout;
5747            (
5748                render_padding(Self::adjust_padding(settings.left_padding)),
5749                render_padding(Self::adjust_padding(settings.right_padding)),
5750            )
5751        } else {
5752            (None, None)
5753        };
5754        let ui_font = theme::setup_ui_font(window, cx);
5755
5756        let theme = cx.theme().clone();
5757        let colors = theme.colors();
5758        let notification_entities = self
5759            .notifications
5760            .iter()
5761            .map(|(_, notification)| notification.entity_id())
5762            .collect::<Vec<_>>();
5763
5764        client_side_decorations(
5765            self.actions(div(), window, cx)
5766                .key_context(context)
5767                .relative()
5768                .size_full()
5769                .flex()
5770                .flex_col()
5771                .font(ui_font)
5772                .gap_0()
5773                .justify_start()
5774                .items_start()
5775                .text_color(colors.text)
5776                .overflow_hidden()
5777                .children(self.titlebar_item.clone())
5778                .on_modifiers_changed(move |_, _, cx| {
5779                    for &id in &notification_entities {
5780                        cx.notify(id);
5781                    }
5782                })
5783                .child(
5784                    div()
5785                        .size_full()
5786                        .relative()
5787                        .flex_1()
5788                        .flex()
5789                        .flex_col()
5790                        .child(
5791                            div()
5792                                .id("workspace")
5793                                .bg(colors.background)
5794                                .relative()
5795                                .flex_1()
5796                                .w_full()
5797                                .flex()
5798                                .flex_col()
5799                                .overflow_hidden()
5800                                .border_t_1()
5801                                .border_b_1()
5802                                .border_color(colors.border)
5803                                .child({
5804                                    let this = cx.entity().clone();
5805                                    canvas(
5806                                        move |bounds, window, cx| {
5807                                            this.update(cx, |this, cx| {
5808                                                let bounds_changed = this.bounds != bounds;
5809                                                this.bounds = bounds;
5810
5811                                                if bounds_changed {
5812                                                    this.left_dock.update(cx, |dock, cx| {
5813                                                        dock.clamp_panel_size(
5814                                                            bounds.size.width,
5815                                                            window,
5816                                                            cx,
5817                                                        )
5818                                                    });
5819
5820                                                    this.right_dock.update(cx, |dock, cx| {
5821                                                        dock.clamp_panel_size(
5822                                                            bounds.size.width,
5823                                                            window,
5824                                                            cx,
5825                                                        )
5826                                                    });
5827
5828                                                    this.bottom_dock.update(cx, |dock, cx| {
5829                                                        dock.clamp_panel_size(
5830                                                            bounds.size.height,
5831                                                            window,
5832                                                            cx,
5833                                                        )
5834                                                    });
5835                                                }
5836                                            })
5837                                        },
5838                                        |_, _, _, _| {},
5839                                    )
5840                                    .absolute()
5841                                    .size_full()
5842                                })
5843                                .when(self.zoomed.is_none(), |this| {
5844                                    this.on_drag_move(cx.listener(
5845                                        move |workspace,
5846                                              e: &DragMoveEvent<DraggedDock>,
5847                                              window,
5848                                              cx| {
5849                                            if workspace.previous_dock_drag_coordinates
5850                                                != Some(e.event.position)
5851                                            {
5852                                                workspace.previous_dock_drag_coordinates =
5853                                                    Some(e.event.position);
5854                                                match e.drag(cx).0 {
5855                                                    DockPosition::Left => {
5856                                                        resize_left_dock(
5857                                                            e.event.position.x
5858                                                                - workspace.bounds.left(),
5859                                                            workspace,
5860                                                            window,
5861                                                            cx,
5862                                                        );
5863                                                    }
5864                                                    DockPosition::Right => {
5865                                                        resize_right_dock(
5866                                                            workspace.bounds.right()
5867                                                                - e.event.position.x,
5868                                                            workspace,
5869                                                            window,
5870                                                            cx,
5871                                                        );
5872                                                    }
5873                                                    DockPosition::Bottom => {
5874                                                        resize_bottom_dock(
5875                                                            workspace.bounds.bottom()
5876                                                                - e.event.position.y,
5877                                                            workspace,
5878                                                            window,
5879                                                            cx,
5880                                                        );
5881                                                    }
5882                                                };
5883                                                workspace.serialize_workspace(window, cx);
5884                                            }
5885                                        },
5886                                    ))
5887                                })
5888                                .child({
5889                                    match self.bottom_dock_layout {
5890                                        BottomDockLayout::Full => div()
5891                                            .flex()
5892                                            .flex_col()
5893                                            .h_full()
5894                                            .child(
5895                                                div()
5896                                                    .flex()
5897                                                    .flex_row()
5898                                                    .flex_1()
5899                                                    .overflow_hidden()
5900                                                    .children(self.render_dock(
5901                                                        DockPosition::Left,
5902                                                        &self.left_dock,
5903                                                        window,
5904                                                        cx,
5905                                                    ))
5906                                                    .child(
5907                                                        div()
5908                                                            .flex()
5909                                                            .flex_col()
5910                                                            .flex_1()
5911                                                            .overflow_hidden()
5912                                                            .child(
5913                                                                h_flex()
5914                                                                    .flex_1()
5915                                                                    .when_some(
5916                                                                        paddings.0,
5917                                                                        |this, p| {
5918                                                                            this.child(
5919                                                                                p.border_r_1(),
5920                                                                            )
5921                                                                        },
5922                                                                    )
5923                                                                    .child(self.center.render(
5924                                                                        self.zoomed.as_ref(),
5925                                                                        &PaneRenderContext {
5926                                                                            follower_states:
5927                                                                                &self.follower_states,
5928                                                                            active_call: self.active_call(),
5929                                                                            active_pane: &self.active_pane,
5930                                                                            app_state: &self.app_state,
5931                                                                            project: &self.project,
5932                                                                            workspace: &self.weak_self,
5933                                                                        },
5934                                                                        window,
5935                                                                        cx,
5936                                                                    ))
5937                                                                    .when_some(
5938                                                                        paddings.1,
5939                                                                        |this, p| {
5940                                                                            this.child(
5941                                                                                p.border_l_1(),
5942                                                                            )
5943                                                                        },
5944                                                                    ),
5945                                                            ),
5946                                                    )
5947                                                    .children(self.render_dock(
5948                                                        DockPosition::Right,
5949                                                        &self.right_dock,
5950                                                        window,
5951                                                        cx,
5952                                                    )),
5953                                            )
5954                                            .child(div().w_full().children(self.render_dock(
5955                                                DockPosition::Bottom,
5956                                                &self.bottom_dock,
5957                                                window,
5958                                                cx
5959                                            ))),
5960
5961                                        BottomDockLayout::LeftAligned => div()
5962                                            .flex()
5963                                            .flex_row()
5964                                            .h_full()
5965                                            .child(
5966                                                div()
5967                                                    .flex()
5968                                                    .flex_col()
5969                                                    .flex_1()
5970                                                    .h_full()
5971                                                    .child(
5972                                                        div()
5973                                                            .flex()
5974                                                            .flex_row()
5975                                                            .flex_1()
5976                                                            .children(self.render_dock(DockPosition::Left, &self.left_dock, window, cx))
5977                                                            .child(
5978                                                                div()
5979                                                                    .flex()
5980                                                                    .flex_col()
5981                                                                    .flex_1()
5982                                                                    .overflow_hidden()
5983                                                                    .child(
5984                                                                        h_flex()
5985                                                                            .flex_1()
5986                                                                            .when_some(paddings.0, |this, p| this.child(p.border_r_1()))
5987                                                                            .child(self.center.render(
5988                                                                                self.zoomed.as_ref(),
5989                                                                                &PaneRenderContext {
5990                                                                                    follower_states:
5991                                                                                        &self.follower_states,
5992                                                                                    active_call: self.active_call(),
5993                                                                                    active_pane: &self.active_pane,
5994                                                                                    app_state: &self.app_state,
5995                                                                                    project: &self.project,
5996                                                                                    workspace: &self.weak_self,
5997                                                                                },
5998                                                                                window,
5999                                                                                cx,
6000                                                                            ))
6001                                                                            .when_some(paddings.1, |this, p| this.child(p.border_l_1())),
6002                                                                    )
6003                                                            )
6004                                                    )
6005                                                    .child(
6006                                                        div()
6007                                                            .w_full()
6008                                                            .children(self.render_dock(DockPosition::Bottom, &self.bottom_dock, window, cx))
6009                                                    ),
6010                                            )
6011                                            .children(self.render_dock(
6012                                                DockPosition::Right,
6013                                                &self.right_dock,
6014                                                window,
6015                                                cx,
6016                                            )),
6017
6018                                        BottomDockLayout::RightAligned => div()
6019                                            .flex()
6020                                            .flex_row()
6021                                            .h_full()
6022                                            .children(self.render_dock(
6023                                                DockPosition::Left,
6024                                                &self.left_dock,
6025                                                window,
6026                                                cx,
6027                                            ))
6028                                            .child(
6029                                                div()
6030                                                    .flex()
6031                                                    .flex_col()
6032                                                    .flex_1()
6033                                                    .h_full()
6034                                                    .child(
6035                                                        div()
6036                                                            .flex()
6037                                                            .flex_row()
6038                                                            .flex_1()
6039                                                            .child(
6040                                                                div()
6041                                                                    .flex()
6042                                                                    .flex_col()
6043                                                                    .flex_1()
6044                                                                    .overflow_hidden()
6045                                                                    .child(
6046                                                                        h_flex()
6047                                                                            .flex_1()
6048                                                                            .when_some(paddings.0, |this, p| this.child(p.border_r_1()))
6049                                                                            .child(self.center.render(
6050                                                                                self.zoomed.as_ref(),
6051                                                                                &PaneRenderContext {
6052                                                                                    follower_states:
6053                                                                                        &self.follower_states,
6054                                                                                    active_call: self.active_call(),
6055                                                                                    active_pane: &self.active_pane,
6056                                                                                    app_state: &self.app_state,
6057                                                                                    project: &self.project,
6058                                                                                    workspace: &self.weak_self,
6059                                                                                },
6060                                                                                window,
6061                                                                                cx,
6062                                                                            ))
6063                                                                            .when_some(paddings.1, |this, p| this.child(p.border_l_1())),
6064                                                                    )
6065                                                            )
6066                                                            .children(self.render_dock(DockPosition::Right, &self.right_dock, window, cx))
6067                                                    )
6068                                                    .child(
6069                                                        div()
6070                                                            .w_full()
6071                                                            .children(self.render_dock(DockPosition::Bottom, &self.bottom_dock, window, cx))
6072                                                    ),
6073                                            ),
6074
6075                                        BottomDockLayout::Contained => div()
6076                                            .flex()
6077                                            .flex_row()
6078                                            .h_full()
6079                                            .children(self.render_dock(
6080                                                DockPosition::Left,
6081                                                &self.left_dock,
6082                                                window,
6083                                                cx,
6084                                            ))
6085                                            .child(
6086                                                div()
6087                                                    .flex()
6088                                                    .flex_col()
6089                                                    .flex_1()
6090                                                    .overflow_hidden()
6091                                                    .child(
6092                                                        h_flex()
6093                                                            .flex_1()
6094                                                            .when_some(paddings.0, |this, p| {
6095                                                                this.child(p.border_r_1())
6096                                                            })
6097                                                            .child(self.center.render(
6098                                                                self.zoomed.as_ref(),
6099                                                                &PaneRenderContext {
6100                                                                    follower_states:
6101                                                                        &self.follower_states,
6102                                                                    active_call: self.active_call(),
6103                                                                    active_pane: &self.active_pane,
6104                                                                    app_state: &self.app_state,
6105                                                                    project: &self.project,
6106                                                                    workspace: &self.weak_self,
6107                                                                },
6108                                                                window,
6109                                                                cx,
6110                                                            ))
6111                                                            .when_some(paddings.1, |this, p| {
6112                                                                this.child(p.border_l_1())
6113                                                            }),
6114                                                    )
6115                                                    .children(self.render_dock(
6116                                                        DockPosition::Bottom,
6117                                                        &self.bottom_dock,
6118                                                        window,
6119                                                        cx,
6120                                                    )),
6121                                            )
6122                                            .children(self.render_dock(
6123                                                DockPosition::Right,
6124                                                &self.right_dock,
6125                                                window,
6126                                                cx,
6127                                            )),
6128                                    }
6129                                })
6130                                .children(self.zoomed.as_ref().and_then(|view| {
6131                                    let zoomed_view = view.upgrade()?;
6132                                    let div = div()
6133                                        .occlude()
6134                                        .absolute()
6135                                        .overflow_hidden()
6136                                        .border_color(colors.border)
6137                                        .bg(colors.background)
6138                                        .child(zoomed_view)
6139                                        .inset_0()
6140                                        .shadow_lg();
6141
6142                                    Some(match self.zoomed_position {
6143                                        Some(DockPosition::Left) => div.right_2().border_r_1(),
6144                                        Some(DockPosition::Right) => div.left_2().border_l_1(),
6145                                        Some(DockPosition::Bottom) => div.top_2().border_t_1(),
6146                                        None => {
6147                                            div.top_2().bottom_2().left_2().right_2().border_1()
6148                                        }
6149                                    })
6150                                }))
6151                                .children(self.render_notifications(window, cx)),
6152                        )
6153                        .child(self.status_bar.clone())
6154                        .child(self.modal_layer.clone())
6155                        .child(self.toast_layer.clone()),
6156                ),
6157            window,
6158            cx,
6159        )
6160    }
6161}
6162
6163fn resize_bottom_dock(
6164    new_size: Pixels,
6165    workspace: &mut Workspace,
6166    window: &mut Window,
6167    cx: &mut App,
6168) {
6169    let size =
6170        new_size.min(workspace.bounds.bottom() - RESIZE_HANDLE_SIZE - workspace.bounds.top());
6171    workspace.bottom_dock.update(cx, |bottom_dock, cx| {
6172        bottom_dock.resize_active_panel(Some(size), window, cx);
6173    });
6174}
6175
6176fn resize_right_dock(
6177    new_size: Pixels,
6178    workspace: &mut Workspace,
6179    window: &mut Window,
6180    cx: &mut App,
6181) {
6182    let size = new_size.max(workspace.bounds.left() - RESIZE_HANDLE_SIZE);
6183    workspace.right_dock.update(cx, |right_dock, cx| {
6184        right_dock.resize_active_panel(Some(size), window, cx);
6185    });
6186}
6187
6188fn resize_left_dock(
6189    new_size: Pixels,
6190    workspace: &mut Workspace,
6191    window: &mut Window,
6192    cx: &mut App,
6193) {
6194    let size = new_size.min(workspace.bounds.right() - RESIZE_HANDLE_SIZE);
6195
6196    workspace.left_dock.update(cx, |left_dock, cx| {
6197        left_dock.resize_active_panel(Some(size), window, cx);
6198    });
6199}
6200
6201impl WorkspaceStore {
6202    pub fn new(client: Arc<Client>, cx: &mut Context<Self>) -> Self {
6203        Self {
6204            workspaces: Default::default(),
6205            _subscriptions: vec![
6206                client.add_request_handler(cx.weak_entity(), Self::handle_follow),
6207                client.add_message_handler(cx.weak_entity(), Self::handle_update_followers),
6208            ],
6209            client,
6210        }
6211    }
6212
6213    pub fn update_followers(
6214        &self,
6215        project_id: Option<u64>,
6216        update: proto::update_followers::Variant,
6217        cx: &App,
6218    ) -> Option<()> {
6219        let active_call = ActiveCall::try_global(cx)?;
6220        let room_id = active_call.read(cx).room()?.read(cx).id();
6221        self.client
6222            .send(proto::UpdateFollowers {
6223                room_id,
6224                project_id,
6225                variant: Some(update),
6226            })
6227            .log_err()
6228    }
6229
6230    pub async fn handle_follow(
6231        this: Entity<Self>,
6232        envelope: TypedEnvelope<proto::Follow>,
6233        mut cx: AsyncApp,
6234    ) -> Result<proto::FollowResponse> {
6235        this.update(&mut cx, |this, cx| {
6236            let follower = Follower {
6237                project_id: envelope.payload.project_id,
6238                peer_id: envelope.original_sender_id()?,
6239            };
6240
6241            let mut response = proto::FollowResponse::default();
6242            this.workspaces.retain(|workspace| {
6243                workspace
6244                    .update(cx, |workspace, window, cx| {
6245                        let handler_response =
6246                            workspace.handle_follow(follower.project_id, window, cx);
6247                        if let Some(active_view) = handler_response.active_view.clone() {
6248                            if workspace.project.read(cx).remote_id() == follower.project_id {
6249                                response.active_view = Some(active_view)
6250                            }
6251                        }
6252                    })
6253                    .is_ok()
6254            });
6255
6256            Ok(response)
6257        })?
6258    }
6259
6260    async fn handle_update_followers(
6261        this: Entity<Self>,
6262        envelope: TypedEnvelope<proto::UpdateFollowers>,
6263        mut cx: AsyncApp,
6264    ) -> Result<()> {
6265        let leader_id = envelope.original_sender_id()?;
6266        let update = envelope.payload;
6267
6268        this.update(&mut cx, |this, cx| {
6269            this.workspaces.retain(|workspace| {
6270                workspace
6271                    .update(cx, |workspace, window, cx| {
6272                        let project_id = workspace.project.read(cx).remote_id();
6273                        if update.project_id != project_id && update.project_id.is_some() {
6274                            return;
6275                        }
6276                        workspace.handle_update_followers(leader_id, update.clone(), window, cx);
6277                    })
6278                    .is_ok()
6279            });
6280            Ok(())
6281        })?
6282    }
6283}
6284
6285impl ViewId {
6286    pub(crate) fn from_proto(message: proto::ViewId) -> Result<Self> {
6287        Ok(Self {
6288            creator: message
6289                .creator
6290                .map(CollaboratorId::PeerId)
6291                .ok_or_else(|| anyhow!("creator is missing"))?,
6292            id: message.id,
6293        })
6294    }
6295
6296    pub(crate) fn to_proto(self) -> Option<proto::ViewId> {
6297        if let CollaboratorId::PeerId(peer_id) = self.creator {
6298            Some(proto::ViewId {
6299                creator: Some(peer_id),
6300                id: self.id,
6301            })
6302        } else {
6303            None
6304        }
6305    }
6306}
6307
6308impl FollowerState {
6309    fn pane(&self) -> &Entity<Pane> {
6310        self.dock_pane.as_ref().unwrap_or(&self.center_pane)
6311    }
6312}
6313
6314pub trait WorkspaceHandle {
6315    fn file_project_paths(&self, cx: &App) -> Vec<ProjectPath>;
6316}
6317
6318impl WorkspaceHandle for Entity<Workspace> {
6319    fn file_project_paths(&self, cx: &App) -> Vec<ProjectPath> {
6320        self.read(cx)
6321            .worktrees(cx)
6322            .flat_map(|worktree| {
6323                let worktree_id = worktree.read(cx).id();
6324                worktree.read(cx).files(true, 0).map(move |f| ProjectPath {
6325                    worktree_id,
6326                    path: f.path.clone(),
6327                })
6328            })
6329            .collect::<Vec<_>>()
6330    }
6331}
6332
6333impl std::fmt::Debug for OpenPaths {
6334    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
6335        f.debug_struct("OpenPaths")
6336            .field("paths", &self.paths)
6337            .finish()
6338    }
6339}
6340
6341pub async fn last_opened_workspace_location() -> Option<SerializedWorkspaceLocation> {
6342    DB.last_workspace().await.log_err().flatten()
6343}
6344
6345pub fn last_session_workspace_locations(
6346    last_session_id: &str,
6347    last_session_window_stack: Option<Vec<WindowId>>,
6348) -> Option<Vec<SerializedWorkspaceLocation>> {
6349    DB.last_session_workspace_locations(last_session_id, last_session_window_stack)
6350        .log_err()
6351}
6352
6353actions!(
6354    collab,
6355    [
6356        OpenChannelNotes,
6357        Mute,
6358        Deafen,
6359        LeaveCall,
6360        ShareProject,
6361        ScreenShare
6362    ]
6363);
6364actions!(zed, [OpenLog]);
6365
6366async fn join_channel_internal(
6367    channel_id: ChannelId,
6368    app_state: &Arc<AppState>,
6369    requesting_window: Option<WindowHandle<Workspace>>,
6370    active_call: &Entity<ActiveCall>,
6371    cx: &mut AsyncApp,
6372) -> Result<bool> {
6373    let (should_prompt, open_room) = active_call.update(cx, |active_call, cx| {
6374        let Some(room) = active_call.room().map(|room| room.read(cx)) else {
6375            return (false, None);
6376        };
6377
6378        let already_in_channel = room.channel_id() == Some(channel_id);
6379        let should_prompt = room.is_sharing_project()
6380            && !room.remote_participants().is_empty()
6381            && !already_in_channel;
6382        let open_room = if already_in_channel {
6383            active_call.room().cloned()
6384        } else {
6385            None
6386        };
6387        (should_prompt, open_room)
6388    })?;
6389
6390    if let Some(room) = open_room {
6391        let task = room.update(cx, |room, cx| {
6392            if let Some((project, host)) = room.most_active_project(cx) {
6393                return Some(join_in_room_project(project, host, app_state.clone(), cx));
6394            }
6395
6396            None
6397        })?;
6398        if let Some(task) = task {
6399            task.await?;
6400        }
6401        return anyhow::Ok(true);
6402    }
6403
6404    if should_prompt {
6405        if let Some(workspace) = requesting_window {
6406            let answer = workspace
6407                .update(cx, |_, window, cx| {
6408                    window.prompt(
6409                        PromptLevel::Warning,
6410                        "Do you want to switch channels?",
6411                        Some("Leaving this call will unshare your current project."),
6412                        &["Yes, Join Channel", "Cancel"],
6413                        cx,
6414                    )
6415                })?
6416                .await;
6417
6418            if answer == Ok(1) {
6419                return Ok(false);
6420            }
6421        } else {
6422            return Ok(false); // unreachable!() hopefully
6423        }
6424    }
6425
6426    let client = cx.update(|cx| active_call.read(cx).client())?;
6427
6428    let mut client_status = client.status();
6429
6430    // this loop will terminate within client::CONNECTION_TIMEOUT seconds.
6431    'outer: loop {
6432        let Some(status) = client_status.recv().await else {
6433            return Err(anyhow!("error connecting"));
6434        };
6435
6436        match status {
6437            Status::Connecting
6438            | Status::Authenticating
6439            | Status::Reconnecting
6440            | Status::Reauthenticating => continue,
6441            Status::Connected { .. } => break 'outer,
6442            Status::SignedOut => return Err(ErrorCode::SignedOut.into()),
6443            Status::UpgradeRequired => return Err(ErrorCode::UpgradeRequired.into()),
6444            Status::ConnectionError | Status::ConnectionLost | Status::ReconnectionError { .. } => {
6445                return Err(ErrorCode::Disconnected.into());
6446            }
6447        }
6448    }
6449
6450    let room = active_call
6451        .update(cx, |active_call, cx| {
6452            active_call.join_channel(channel_id, cx)
6453        })?
6454        .await?;
6455
6456    let Some(room) = room else {
6457        return anyhow::Ok(true);
6458    };
6459
6460    room.update(cx, |room, _| room.room_update_completed())?
6461        .await;
6462
6463    let task = room.update(cx, |room, cx| {
6464        if let Some((project, host)) = room.most_active_project(cx) {
6465            return Some(join_in_room_project(project, host, app_state.clone(), cx));
6466        }
6467
6468        // If you are the first to join a channel, see if you should share your project.
6469        if room.remote_participants().is_empty() && !room.local_participant_is_guest() {
6470            if let Some(workspace) = requesting_window {
6471                let project = workspace.update(cx, |workspace, _, cx| {
6472                    let project = workspace.project.read(cx);
6473
6474                    if !CallSettings::get_global(cx).share_on_join {
6475                        return None;
6476                    }
6477
6478                    if (project.is_local() || project.is_via_ssh())
6479                        && project.visible_worktrees(cx).any(|tree| {
6480                            tree.read(cx)
6481                                .root_entry()
6482                                .map_or(false, |entry| entry.is_dir())
6483                        })
6484                    {
6485                        Some(workspace.project.clone())
6486                    } else {
6487                        None
6488                    }
6489                });
6490                if let Ok(Some(project)) = project {
6491                    return Some(cx.spawn(async move |room, cx| {
6492                        room.update(cx, |room, cx| room.share_project(project, cx))?
6493                            .await?;
6494                        Ok(())
6495                    }));
6496                }
6497            }
6498        }
6499
6500        None
6501    })?;
6502    if let Some(task) = task {
6503        task.await?;
6504        return anyhow::Ok(true);
6505    }
6506    anyhow::Ok(false)
6507}
6508
6509pub fn join_channel(
6510    channel_id: ChannelId,
6511    app_state: Arc<AppState>,
6512    requesting_window: Option<WindowHandle<Workspace>>,
6513    cx: &mut App,
6514) -> Task<Result<()>> {
6515    let active_call = ActiveCall::global(cx);
6516    cx.spawn(async move |cx| {
6517        let result = join_channel_internal(
6518            channel_id,
6519            &app_state,
6520            requesting_window,
6521            &active_call,
6522             cx,
6523        )
6524            .await;
6525
6526        // join channel succeeded, and opened a window
6527        if matches!(result, Ok(true)) {
6528            return anyhow::Ok(());
6529        }
6530
6531        // find an existing workspace to focus and show call controls
6532        let mut active_window =
6533            requesting_window.or_else(|| activate_any_workspace_window( cx));
6534        if active_window.is_none() {
6535            // no open workspaces, make one to show the error in (blergh)
6536            let (window_handle, _) = cx
6537                .update(|cx| {
6538                    Workspace::new_local(vec![], app_state.clone(), requesting_window, None, cx)
6539                })?
6540                .await?;
6541
6542            if result.is_ok() {
6543                cx.update(|cx| {
6544                    cx.dispatch_action(&OpenChannelNotes);
6545                }).log_err();
6546            }
6547
6548            active_window = Some(window_handle);
6549        }
6550
6551        if let Err(err) = result {
6552            log::error!("failed to join channel: {}", err);
6553            if let Some(active_window) = active_window {
6554                active_window
6555                    .update(cx, |_, window, cx| {
6556                        let detail: SharedString = match err.error_code() {
6557                            ErrorCode::SignedOut => {
6558                                "Please sign in to continue.".into()
6559                            }
6560                            ErrorCode::UpgradeRequired => {
6561                                "Your are running an unsupported version of Zed. Please update to continue.".into()
6562                            }
6563                            ErrorCode::NoSuchChannel => {
6564                                "No matching channel was found. Please check the link and try again.".into()
6565                            }
6566                            ErrorCode::Forbidden => {
6567                                "This channel is private, and you do not have access. Please ask someone to add you and try again.".into()
6568                            }
6569                            ErrorCode::Disconnected => "Please check your internet connection and try again.".into(),
6570                            _ => format!("{}\n\nPlease try again.", err).into(),
6571                        };
6572                        window.prompt(
6573                            PromptLevel::Critical,
6574                            "Failed to join channel",
6575                            Some(&detail),
6576                            &["Ok"],
6577                        cx)
6578                    })?
6579                    .await
6580                    .ok();
6581            }
6582        }
6583
6584        // return ok, we showed the error to the user.
6585        anyhow::Ok(())
6586    })
6587}
6588
6589pub async fn get_any_active_workspace(
6590    app_state: Arc<AppState>,
6591    mut cx: AsyncApp,
6592) -> anyhow::Result<WindowHandle<Workspace>> {
6593    // find an existing workspace to focus and show call controls
6594    let active_window = activate_any_workspace_window(&mut cx);
6595    if active_window.is_none() {
6596        cx.update(|cx| Workspace::new_local(vec![], app_state.clone(), None, None, cx))?
6597            .await?;
6598    }
6599    activate_any_workspace_window(&mut cx).context("could not open zed")
6600}
6601
6602fn activate_any_workspace_window(cx: &mut AsyncApp) -> Option<WindowHandle<Workspace>> {
6603    cx.update(|cx| {
6604        if let Some(workspace_window) = cx
6605            .active_window()
6606            .and_then(|window| window.downcast::<Workspace>())
6607        {
6608            return Some(workspace_window);
6609        }
6610
6611        for window in cx.windows() {
6612            if let Some(workspace_window) = window.downcast::<Workspace>() {
6613                workspace_window
6614                    .update(cx, |_, window, _| window.activate_window())
6615                    .ok();
6616                return Some(workspace_window);
6617            }
6618        }
6619        None
6620    })
6621    .ok()
6622    .flatten()
6623}
6624
6625pub fn local_workspace_windows(cx: &App) -> Vec<WindowHandle<Workspace>> {
6626    cx.windows()
6627        .into_iter()
6628        .filter_map(|window| window.downcast::<Workspace>())
6629        .filter(|workspace| {
6630            workspace
6631                .read(cx)
6632                .is_ok_and(|workspace| workspace.project.read(cx).is_local())
6633        })
6634        .collect()
6635}
6636
6637#[derive(Default)]
6638pub struct OpenOptions {
6639    pub visible: Option<OpenVisible>,
6640    pub focus: Option<bool>,
6641    pub open_new_workspace: Option<bool>,
6642    pub replace_window: Option<WindowHandle<Workspace>>,
6643    pub env: Option<HashMap<String, String>>,
6644}
6645
6646#[allow(clippy::type_complexity)]
6647pub fn open_paths(
6648    abs_paths: &[PathBuf],
6649    app_state: Arc<AppState>,
6650    open_options: OpenOptions,
6651    cx: &mut App,
6652) -> Task<
6653    anyhow::Result<(
6654        WindowHandle<Workspace>,
6655        Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
6656    )>,
6657> {
6658    let abs_paths = abs_paths.to_vec();
6659    let mut existing = None;
6660    let mut best_match = None;
6661    let mut open_visible = OpenVisible::All;
6662
6663    cx.spawn(async move |cx| {
6664        if open_options.open_new_workspace != Some(true) {
6665            let all_paths = abs_paths.iter().map(|path| app_state.fs.metadata(path));
6666            let all_metadatas = futures::future::join_all(all_paths)
6667                .await
6668                .into_iter()
6669                .filter_map(|result| result.ok().flatten())
6670                .collect::<Vec<_>>();
6671
6672            cx.update(|cx| {
6673                for window in local_workspace_windows(&cx) {
6674                    if let Ok(workspace) = window.read(&cx) {
6675                        let m = workspace.project.read(&cx).visibility_for_paths(
6676                            &abs_paths,
6677                            &all_metadatas,
6678                            open_options.open_new_workspace == None,
6679                            cx,
6680                        );
6681                        if m > best_match {
6682                            existing = Some(window);
6683                            best_match = m;
6684                        } else if best_match.is_none()
6685                            && open_options.open_new_workspace == Some(false)
6686                        {
6687                            existing = Some(window)
6688                        }
6689                    }
6690                }
6691            })?;
6692
6693            if open_options.open_new_workspace.is_none() && existing.is_none() {
6694                if all_metadatas.iter().all(|file| !file.is_dir) {
6695                    cx.update(|cx| {
6696                        if let Some(window) = cx
6697                            .active_window()
6698                            .and_then(|window| window.downcast::<Workspace>())
6699                        {
6700                            if let Ok(workspace) = window.read(cx) {
6701                                let project = workspace.project().read(cx);
6702                                if project.is_local() && !project.is_via_collab() {
6703                                    existing = Some(window);
6704                                    open_visible = OpenVisible::None;
6705                                    return;
6706                                }
6707                            }
6708                        }
6709                        for window in local_workspace_windows(cx) {
6710                            if let Ok(workspace) = window.read(cx) {
6711                                let project = workspace.project().read(cx);
6712                                if project.is_via_collab() {
6713                                    continue;
6714                                }
6715                                existing = Some(window);
6716                                open_visible = OpenVisible::None;
6717                                break;
6718                            }
6719                        }
6720                    })?;
6721                }
6722            }
6723        }
6724
6725        if let Some(existing) = existing {
6726            let open_task = existing
6727                .update(cx, |workspace, window, cx| {
6728                    window.activate_window();
6729                    workspace.open_paths(
6730                        abs_paths,
6731                        OpenOptions {
6732                            visible: Some(open_visible),
6733                            ..Default::default()
6734                        },
6735                        None,
6736                        window,
6737                        cx,
6738                    )
6739                })?
6740                .await;
6741
6742            _ = existing.update(cx, |workspace, _, cx| {
6743                for item in open_task.iter().flatten() {
6744                    if let Err(e) = item {
6745                        workspace.show_error(&e, cx);
6746                    }
6747                }
6748            });
6749
6750            Ok((existing, open_task))
6751        } else {
6752            cx.update(move |cx| {
6753                Workspace::new_local(
6754                    abs_paths,
6755                    app_state.clone(),
6756                    open_options.replace_window,
6757                    open_options.env,
6758                    cx,
6759                )
6760            })?
6761            .await
6762        }
6763    })
6764}
6765
6766pub fn open_new(
6767    open_options: OpenOptions,
6768    app_state: Arc<AppState>,
6769    cx: &mut App,
6770    init: impl FnOnce(&mut Workspace, &mut Window, &mut Context<Workspace>) + 'static + Send,
6771) -> Task<anyhow::Result<()>> {
6772    let task = Workspace::new_local(Vec::new(), app_state, None, open_options.env, cx);
6773    cx.spawn(async move |cx| {
6774        let (workspace, opened_paths) = task.await?;
6775        workspace.update(cx, |workspace, window, cx| {
6776            if opened_paths.is_empty() {
6777                init(workspace, window, cx)
6778            }
6779        })?;
6780        Ok(())
6781    })
6782}
6783
6784pub fn create_and_open_local_file(
6785    path: &'static Path,
6786    window: &mut Window,
6787    cx: &mut Context<Workspace>,
6788    default_content: impl 'static + Send + FnOnce() -> Rope,
6789) -> Task<Result<Box<dyn ItemHandle>>> {
6790    cx.spawn_in(window, async move |workspace, cx| {
6791        let fs = workspace.update(cx, |workspace, _| workspace.app_state().fs.clone())?;
6792        if !fs.is_file(path).await {
6793            fs.create_file(path, Default::default()).await?;
6794            fs.save(path, &default_content(), Default::default())
6795                .await?;
6796        }
6797
6798        let mut items = workspace
6799            .update_in(cx, |workspace, window, cx| {
6800                workspace.with_local_workspace(window, cx, |workspace, window, cx| {
6801                    workspace.open_paths(
6802                        vec![path.to_path_buf()],
6803                        OpenOptions {
6804                            visible: Some(OpenVisible::None),
6805                            ..Default::default()
6806                        },
6807                        None,
6808                        window,
6809                        cx,
6810                    )
6811                })
6812            })?
6813            .await?
6814            .await;
6815
6816        let item = items.pop().flatten();
6817        item.ok_or_else(|| anyhow!("path {path:?} is not a file"))?
6818    })
6819}
6820
6821pub fn open_ssh_project_with_new_connection(
6822    window: WindowHandle<Workspace>,
6823    connection_options: SshConnectionOptions,
6824    cancel_rx: oneshot::Receiver<()>,
6825    delegate: Arc<dyn SshClientDelegate>,
6826    app_state: Arc<AppState>,
6827    paths: Vec<PathBuf>,
6828    cx: &mut App,
6829) -> Task<Result<()>> {
6830    cx.spawn(async move |cx| {
6831        let (serialized_ssh_project, workspace_id, serialized_workspace) =
6832            serialize_ssh_project(connection_options.clone(), paths.clone(), &cx).await?;
6833
6834        let session = match cx
6835            .update(|cx| {
6836                remote::SshRemoteClient::new(
6837                    ConnectionIdentifier::Workspace(workspace_id.0),
6838                    connection_options,
6839                    cancel_rx,
6840                    delegate,
6841                    cx,
6842                )
6843            })?
6844            .await?
6845        {
6846            Some(result) => result,
6847            None => return Ok(()),
6848        };
6849
6850        let project = cx.update(|cx| {
6851            project::Project::ssh(
6852                session,
6853                app_state.client.clone(),
6854                app_state.node_runtime.clone(),
6855                app_state.user_store.clone(),
6856                app_state.languages.clone(),
6857                app_state.fs.clone(),
6858                cx,
6859            )
6860        })?;
6861
6862        open_ssh_project_inner(
6863            project,
6864            paths,
6865            serialized_ssh_project,
6866            workspace_id,
6867            serialized_workspace,
6868            app_state,
6869            window,
6870            cx,
6871        )
6872        .await
6873    })
6874}
6875
6876pub fn open_ssh_project_with_existing_connection(
6877    connection_options: SshConnectionOptions,
6878    project: Entity<Project>,
6879    paths: Vec<PathBuf>,
6880    app_state: Arc<AppState>,
6881    window: WindowHandle<Workspace>,
6882    cx: &mut AsyncApp,
6883) -> Task<Result<()>> {
6884    cx.spawn(async move |cx| {
6885        let (serialized_ssh_project, workspace_id, serialized_workspace) =
6886            serialize_ssh_project(connection_options.clone(), paths.clone(), &cx).await?;
6887
6888        open_ssh_project_inner(
6889            project,
6890            paths,
6891            serialized_ssh_project,
6892            workspace_id,
6893            serialized_workspace,
6894            app_state,
6895            window,
6896            cx,
6897        )
6898        .await
6899    })
6900}
6901
6902async fn open_ssh_project_inner(
6903    project: Entity<Project>,
6904    paths: Vec<PathBuf>,
6905    serialized_ssh_project: SerializedSshProject,
6906    workspace_id: WorkspaceId,
6907    serialized_workspace: Option<SerializedWorkspace>,
6908    app_state: Arc<AppState>,
6909    window: WindowHandle<Workspace>,
6910    cx: &mut AsyncApp,
6911) -> Result<()> {
6912    let toolchains = DB.toolchains(workspace_id).await?;
6913    for (toolchain, worktree_id, path) in toolchains {
6914        project
6915            .update(cx, |this, cx| {
6916                this.activate_toolchain(ProjectPath { worktree_id, path }, toolchain, cx)
6917            })?
6918            .await;
6919    }
6920    let mut project_paths_to_open = vec![];
6921    let mut project_path_errors = vec![];
6922
6923    for path in paths {
6924        let result = cx
6925            .update(|cx| Workspace::project_path_for_path(project.clone(), &path, true, cx))?
6926            .await;
6927        match result {
6928            Ok((_, project_path)) => {
6929                project_paths_to_open.push((path.clone(), Some(project_path)));
6930            }
6931            Err(error) => {
6932                project_path_errors.push(error);
6933            }
6934        };
6935    }
6936
6937    if project_paths_to_open.is_empty() {
6938        return Err(project_path_errors
6939            .pop()
6940            .unwrap_or_else(|| anyhow!("no paths given")));
6941    }
6942
6943    cx.update_window(window.into(), |_, window, cx| {
6944        window.replace_root(cx, |window, cx| {
6945            telemetry::event!("SSH Project Opened");
6946
6947            let mut workspace =
6948                Workspace::new(Some(workspace_id), project, app_state.clone(), window, cx);
6949            workspace.set_serialized_ssh_project(serialized_ssh_project);
6950            workspace.update_history(cx);
6951            workspace
6952        });
6953    })?;
6954
6955    window
6956        .update(cx, |_, window, cx| {
6957            window.activate_window();
6958            open_items(serialized_workspace, project_paths_to_open, window, cx)
6959        })?
6960        .await?;
6961
6962    window.update(cx, |workspace, _, cx| {
6963        for error in project_path_errors {
6964            if error.error_code() == proto::ErrorCode::DevServerProjectPathDoesNotExist {
6965                if let Some(path) = error.error_tag("path") {
6966                    workspace.show_error(&anyhow!("'{path}' does not exist"), cx)
6967                }
6968            } else {
6969                workspace.show_error(&error, cx)
6970            }
6971        }
6972    })?;
6973
6974    Ok(())
6975}
6976
6977fn serialize_ssh_project(
6978    connection_options: SshConnectionOptions,
6979    paths: Vec<PathBuf>,
6980    cx: &AsyncApp,
6981) -> Task<
6982    Result<(
6983        SerializedSshProject,
6984        WorkspaceId,
6985        Option<SerializedWorkspace>,
6986    )>,
6987> {
6988    cx.background_spawn(async move {
6989        let serialized_ssh_project = persistence::DB
6990            .get_or_create_ssh_project(
6991                connection_options.host.clone(),
6992                connection_options.port,
6993                paths
6994                    .iter()
6995                    .map(|path| path.to_string_lossy().to_string())
6996                    .collect::<Vec<_>>(),
6997                connection_options.username.clone(),
6998            )
6999            .await?;
7000
7001        let serialized_workspace =
7002            persistence::DB.workspace_for_ssh_project(&serialized_ssh_project);
7003
7004        let workspace_id = if let Some(workspace_id) =
7005            serialized_workspace.as_ref().map(|workspace| workspace.id)
7006        {
7007            workspace_id
7008        } else {
7009            persistence::DB.next_id().await?
7010        };
7011
7012        Ok((serialized_ssh_project, workspace_id, serialized_workspace))
7013    })
7014}
7015
7016pub fn join_in_room_project(
7017    project_id: u64,
7018    follow_user_id: u64,
7019    app_state: Arc<AppState>,
7020    cx: &mut App,
7021) -> Task<Result<()>> {
7022    let windows = cx.windows();
7023    cx.spawn(async move |cx| {
7024        let existing_workspace = windows.into_iter().find_map(|window_handle| {
7025            window_handle
7026                .downcast::<Workspace>()
7027                .and_then(|window_handle| {
7028                    window_handle
7029                        .update(cx, |workspace, _window, cx| {
7030                            if workspace.project().read(cx).remote_id() == Some(project_id) {
7031                                Some(window_handle)
7032                            } else {
7033                                None
7034                            }
7035                        })
7036                        .unwrap_or(None)
7037                })
7038        });
7039
7040        let workspace = if let Some(existing_workspace) = existing_workspace {
7041            existing_workspace
7042        } else {
7043            let active_call = cx.update(|cx| ActiveCall::global(cx))?;
7044            let room = active_call
7045                .read_with(cx, |call, _| call.room().cloned())?
7046                .ok_or_else(|| anyhow!("not in a call"))?;
7047            let project = room
7048                .update(cx, |room, cx| {
7049                    room.join_project(
7050                        project_id,
7051                        app_state.languages.clone(),
7052                        app_state.fs.clone(),
7053                        cx,
7054                    )
7055                })?
7056                .await?;
7057
7058            let window_bounds_override = window_bounds_env_override();
7059            cx.update(|cx| {
7060                let mut options = (app_state.build_window_options)(None, cx);
7061                options.window_bounds = window_bounds_override.map(WindowBounds::Windowed);
7062                cx.open_window(options, |window, cx| {
7063                    cx.new(|cx| {
7064                        Workspace::new(Default::default(), project, app_state.clone(), window, cx)
7065                    })
7066                })
7067            })??
7068        };
7069
7070        workspace.update(cx, |workspace, window, cx| {
7071            cx.activate(true);
7072            window.activate_window();
7073
7074            if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
7075                let follow_peer_id = room
7076                    .read(cx)
7077                    .remote_participants()
7078                    .iter()
7079                    .find(|(_, participant)| participant.user.id == follow_user_id)
7080                    .map(|(_, p)| p.peer_id)
7081                    .or_else(|| {
7082                        // If we couldn't follow the given user, follow the host instead.
7083                        let collaborator = workspace
7084                            .project()
7085                            .read(cx)
7086                            .collaborators()
7087                            .values()
7088                            .find(|collaborator| collaborator.is_host)?;
7089                        Some(collaborator.peer_id)
7090                    });
7091
7092                if let Some(follow_peer_id) = follow_peer_id {
7093                    workspace.follow(follow_peer_id, window, cx);
7094                }
7095            }
7096        })?;
7097
7098        anyhow::Ok(())
7099    })
7100}
7101
7102pub fn reload(reload: &Reload, cx: &mut App) {
7103    let should_confirm = WorkspaceSettings::get_global(cx).confirm_quit;
7104    let mut workspace_windows = cx
7105        .windows()
7106        .into_iter()
7107        .filter_map(|window| window.downcast::<Workspace>())
7108        .collect::<Vec<_>>();
7109
7110    // If multiple windows have unsaved changes, and need a save prompt,
7111    // prompt in the active window before switching to a different window.
7112    workspace_windows.sort_by_key(|window| window.is_active(cx) == Some(false));
7113
7114    let mut prompt = None;
7115    if let (true, Some(window)) = (should_confirm, workspace_windows.first()) {
7116        prompt = window
7117            .update(cx, |_, window, cx| {
7118                window.prompt(
7119                    PromptLevel::Info,
7120                    "Are you sure you want to restart?",
7121                    None,
7122                    &["Restart", "Cancel"],
7123                    cx,
7124                )
7125            })
7126            .ok();
7127    }
7128
7129    let binary_path = reload.binary_path.clone();
7130    cx.spawn(async move |cx| {
7131        if let Some(prompt) = prompt {
7132            let answer = prompt.await?;
7133            if answer != 0 {
7134                return Ok(());
7135            }
7136        }
7137
7138        // If the user cancels any save prompt, then keep the app open.
7139        for window in workspace_windows {
7140            if let Ok(should_close) = window.update(cx, |workspace, window, cx| {
7141                workspace.prepare_to_close(CloseIntent::Quit, window, cx)
7142            }) {
7143                if !should_close.await? {
7144                    return Ok(());
7145                }
7146            }
7147        }
7148
7149        cx.update(|cx| cx.restart(binary_path))
7150    })
7151    .detach_and_log_err(cx);
7152}
7153
7154fn parse_pixel_position_env_var(value: &str) -> Option<Point<Pixels>> {
7155    let mut parts = value.split(',');
7156    let x: usize = parts.next()?.parse().ok()?;
7157    let y: usize = parts.next()?.parse().ok()?;
7158    Some(point(px(x as f32), px(y as f32)))
7159}
7160
7161fn parse_pixel_size_env_var(value: &str) -> Option<Size<Pixels>> {
7162    let mut parts = value.split(',');
7163    let width: usize = parts.next()?.parse().ok()?;
7164    let height: usize = parts.next()?.parse().ok()?;
7165    Some(size(px(width as f32), px(height as f32)))
7166}
7167
7168pub fn client_side_decorations(
7169    element: impl IntoElement,
7170    window: &mut Window,
7171    cx: &mut App,
7172) -> Stateful<Div> {
7173    const BORDER_SIZE: Pixels = px(1.0);
7174    let decorations = window.window_decorations();
7175
7176    if matches!(decorations, Decorations::Client { .. }) {
7177        window.set_client_inset(theme::CLIENT_SIDE_DECORATION_SHADOW);
7178    }
7179
7180    struct GlobalResizeEdge(ResizeEdge);
7181    impl Global for GlobalResizeEdge {}
7182
7183    div()
7184        .id("window-backdrop")
7185        .bg(transparent_black())
7186        .map(|div| match decorations {
7187            Decorations::Server => div,
7188            Decorations::Client { tiling, .. } => div
7189                .when(!(tiling.top || tiling.right), |div| {
7190                    div.rounded_tr(theme::CLIENT_SIDE_DECORATION_ROUNDING)
7191                })
7192                .when(!(tiling.top || tiling.left), |div| {
7193                    div.rounded_tl(theme::CLIENT_SIDE_DECORATION_ROUNDING)
7194                })
7195                .when(!(tiling.bottom || tiling.right), |div| {
7196                    div.rounded_br(theme::CLIENT_SIDE_DECORATION_ROUNDING)
7197                })
7198                .when(!(tiling.bottom || tiling.left), |div| {
7199                    div.rounded_bl(theme::CLIENT_SIDE_DECORATION_ROUNDING)
7200                })
7201                .when(!tiling.top, |div| {
7202                    div.pt(theme::CLIENT_SIDE_DECORATION_SHADOW)
7203                })
7204                .when(!tiling.bottom, |div| {
7205                    div.pb(theme::CLIENT_SIDE_DECORATION_SHADOW)
7206                })
7207                .when(!tiling.left, |div| {
7208                    div.pl(theme::CLIENT_SIDE_DECORATION_SHADOW)
7209                })
7210                .when(!tiling.right, |div| {
7211                    div.pr(theme::CLIENT_SIDE_DECORATION_SHADOW)
7212                })
7213                .on_mouse_move(move |e, window, cx| {
7214                    let size = window.window_bounds().get_bounds().size;
7215                    let pos = e.position;
7216
7217                    let new_edge =
7218                        resize_edge(pos, theme::CLIENT_SIDE_DECORATION_SHADOW, size, tiling);
7219
7220                    let edge = cx.try_global::<GlobalResizeEdge>();
7221                    if new_edge != edge.map(|edge| edge.0) {
7222                        window
7223                            .window_handle()
7224                            .update(cx, |workspace, _, cx| {
7225                                cx.notify(workspace.entity_id());
7226                            })
7227                            .ok();
7228                    }
7229                })
7230                .on_mouse_down(MouseButton::Left, move |e, window, _| {
7231                    let size = window.window_bounds().get_bounds().size;
7232                    let pos = e.position;
7233
7234                    let edge = match resize_edge(
7235                        pos,
7236                        theme::CLIENT_SIDE_DECORATION_SHADOW,
7237                        size,
7238                        tiling,
7239                    ) {
7240                        Some(value) => value,
7241                        None => return,
7242                    };
7243
7244                    window.start_window_resize(edge);
7245                }),
7246        })
7247        .size_full()
7248        .child(
7249            div()
7250                .cursor(CursorStyle::Arrow)
7251                .map(|div| match decorations {
7252                    Decorations::Server => div,
7253                    Decorations::Client { tiling } => div
7254                        .border_color(cx.theme().colors().border)
7255                        .when(!(tiling.top || tiling.right), |div| {
7256                            div.rounded_tr(theme::CLIENT_SIDE_DECORATION_ROUNDING)
7257                        })
7258                        .when(!(tiling.top || tiling.left), |div| {
7259                            div.rounded_tl(theme::CLIENT_SIDE_DECORATION_ROUNDING)
7260                        })
7261                        .when(!(tiling.bottom || tiling.right), |div| {
7262                            div.rounded_br(theme::CLIENT_SIDE_DECORATION_ROUNDING)
7263                        })
7264                        .when(!(tiling.bottom || tiling.left), |div| {
7265                            div.rounded_bl(theme::CLIENT_SIDE_DECORATION_ROUNDING)
7266                        })
7267                        .when(!tiling.top, |div| div.border_t(BORDER_SIZE))
7268                        .when(!tiling.bottom, |div| div.border_b(BORDER_SIZE))
7269                        .when(!tiling.left, |div| div.border_l(BORDER_SIZE))
7270                        .when(!tiling.right, |div| div.border_r(BORDER_SIZE))
7271                        .when(!tiling.is_tiled(), |div| {
7272                            div.shadow(smallvec::smallvec![gpui::BoxShadow {
7273                                color: Hsla {
7274                                    h: 0.,
7275                                    s: 0.,
7276                                    l: 0.,
7277                                    a: 0.4,
7278                                },
7279                                blur_radius: theme::CLIENT_SIDE_DECORATION_SHADOW / 2.,
7280                                spread_radius: px(0.),
7281                                offset: point(px(0.0), px(0.0)),
7282                            }])
7283                        }),
7284                })
7285                .on_mouse_move(|_e, _, cx| {
7286                    cx.stop_propagation();
7287                })
7288                .size_full()
7289                .child(element),
7290        )
7291        .map(|div| match decorations {
7292            Decorations::Server => div,
7293            Decorations::Client { tiling, .. } => div.child(
7294                canvas(
7295                    |_bounds, window, _| {
7296                        window.insert_hitbox(
7297                            Bounds::new(
7298                                point(px(0.0), px(0.0)),
7299                                window.window_bounds().get_bounds().size,
7300                            ),
7301                            false,
7302                        )
7303                    },
7304                    move |_bounds, hitbox, window, cx| {
7305                        let mouse = window.mouse_position();
7306                        let size = window.window_bounds().get_bounds().size;
7307                        let Some(edge) =
7308                            resize_edge(mouse, theme::CLIENT_SIDE_DECORATION_SHADOW, size, tiling)
7309                        else {
7310                            return;
7311                        };
7312                        cx.set_global(GlobalResizeEdge(edge));
7313                        window.set_cursor_style(
7314                            match edge {
7315                                ResizeEdge::Top | ResizeEdge::Bottom => CursorStyle::ResizeUpDown,
7316                                ResizeEdge::Left | ResizeEdge::Right => {
7317                                    CursorStyle::ResizeLeftRight
7318                                }
7319                                ResizeEdge::TopLeft | ResizeEdge::BottomRight => {
7320                                    CursorStyle::ResizeUpLeftDownRight
7321                                }
7322                                ResizeEdge::TopRight | ResizeEdge::BottomLeft => {
7323                                    CursorStyle::ResizeUpRightDownLeft
7324                                }
7325                            },
7326                            Some(&hitbox),
7327                        );
7328                    },
7329                )
7330                .size_full()
7331                .absolute(),
7332            ),
7333        })
7334}
7335
7336fn resize_edge(
7337    pos: Point<Pixels>,
7338    shadow_size: Pixels,
7339    window_size: Size<Pixels>,
7340    tiling: Tiling,
7341) -> Option<ResizeEdge> {
7342    let bounds = Bounds::new(Point::default(), window_size).inset(shadow_size * 1.5);
7343    if bounds.contains(&pos) {
7344        return None;
7345    }
7346
7347    let corner_size = size(shadow_size * 1.5, shadow_size * 1.5);
7348    let top_left_bounds = Bounds::new(Point::new(px(0.), px(0.)), corner_size);
7349    if !tiling.top && top_left_bounds.contains(&pos) {
7350        return Some(ResizeEdge::TopLeft);
7351    }
7352
7353    let top_right_bounds = Bounds::new(
7354        Point::new(window_size.width - corner_size.width, px(0.)),
7355        corner_size,
7356    );
7357    if !tiling.top && top_right_bounds.contains(&pos) {
7358        return Some(ResizeEdge::TopRight);
7359    }
7360
7361    let bottom_left_bounds = Bounds::new(
7362        Point::new(px(0.), window_size.height - corner_size.height),
7363        corner_size,
7364    );
7365    if !tiling.bottom && bottom_left_bounds.contains(&pos) {
7366        return Some(ResizeEdge::BottomLeft);
7367    }
7368
7369    let bottom_right_bounds = Bounds::new(
7370        Point::new(
7371            window_size.width - corner_size.width,
7372            window_size.height - corner_size.height,
7373        ),
7374        corner_size,
7375    );
7376    if !tiling.bottom && bottom_right_bounds.contains(&pos) {
7377        return Some(ResizeEdge::BottomRight);
7378    }
7379
7380    if !tiling.top && pos.y < shadow_size {
7381        Some(ResizeEdge::Top)
7382    } else if !tiling.bottom && pos.y > window_size.height - shadow_size {
7383        Some(ResizeEdge::Bottom)
7384    } else if !tiling.left && pos.x < shadow_size {
7385        Some(ResizeEdge::Left)
7386    } else if !tiling.right && pos.x > window_size.width - shadow_size {
7387        Some(ResizeEdge::Right)
7388    } else {
7389        None
7390    }
7391}
7392
7393fn join_pane_into_active(
7394    active_pane: &Entity<Pane>,
7395    pane: &Entity<Pane>,
7396    window: &mut Window,
7397    cx: &mut App,
7398) {
7399    if pane == active_pane {
7400        return;
7401    } else if pane.read(cx).items_len() == 0 {
7402        pane.update(cx, |_, cx| {
7403            cx.emit(pane::Event::Remove {
7404                focus_on_pane: None,
7405            });
7406        })
7407    } else {
7408        move_all_items(pane, active_pane, window, cx);
7409    }
7410}
7411
7412fn move_all_items(
7413    from_pane: &Entity<Pane>,
7414    to_pane: &Entity<Pane>,
7415    window: &mut Window,
7416    cx: &mut App,
7417) {
7418    let destination_is_different = from_pane != to_pane;
7419    let mut moved_items = 0;
7420    for (item_ix, item_handle) in from_pane
7421        .read(cx)
7422        .items()
7423        .enumerate()
7424        .map(|(ix, item)| (ix, item.clone()))
7425        .collect::<Vec<_>>()
7426    {
7427        let ix = item_ix - moved_items;
7428        if destination_is_different {
7429            // Close item from previous pane
7430            from_pane.update(cx, |source, cx| {
7431                source.remove_item_and_focus_on_pane(ix, false, to_pane.clone(), window, cx);
7432            });
7433            moved_items += 1;
7434        }
7435
7436        // This automatically removes duplicate items in the pane
7437        to_pane.update(cx, |destination, cx| {
7438            destination.add_item(item_handle, true, true, None, window, cx);
7439            window.focus(&destination.focus_handle(cx))
7440        });
7441    }
7442}
7443
7444pub fn move_item(
7445    source: &Entity<Pane>,
7446    destination: &Entity<Pane>,
7447    item_id_to_move: EntityId,
7448    destination_index: usize,
7449    window: &mut Window,
7450    cx: &mut App,
7451) {
7452    let Some((item_ix, item_handle)) = source
7453        .read(cx)
7454        .items()
7455        .enumerate()
7456        .find(|(_, item_handle)| item_handle.item_id() == item_id_to_move)
7457        .map(|(ix, item)| (ix, item.clone()))
7458    else {
7459        // Tab was closed during drag
7460        return;
7461    };
7462
7463    if source != destination {
7464        // Close item from previous pane
7465        source.update(cx, |source, cx| {
7466            source.remove_item_and_focus_on_pane(item_ix, false, destination.clone(), window, cx);
7467        });
7468    }
7469
7470    // This automatically removes duplicate items in the pane
7471    destination.update(cx, |destination, cx| {
7472        destination.add_item(item_handle, true, true, Some(destination_index), window, cx);
7473        window.focus(&destination.focus_handle(cx))
7474    });
7475}
7476
7477pub fn move_active_item(
7478    source: &Entity<Pane>,
7479    destination: &Entity<Pane>,
7480    focus_destination: bool,
7481    close_if_empty: bool,
7482    window: &mut Window,
7483    cx: &mut App,
7484) {
7485    if source == destination {
7486        return;
7487    }
7488    let Some(active_item) = source.read(cx).active_item() else {
7489        return;
7490    };
7491    source.update(cx, |source_pane, cx| {
7492        let item_id = active_item.item_id();
7493        source_pane.remove_item(item_id, false, close_if_empty, window, cx);
7494        destination.update(cx, |target_pane, cx| {
7495            target_pane.add_item(
7496                active_item,
7497                focus_destination,
7498                focus_destination,
7499                Some(target_pane.items_len()),
7500                window,
7501                cx,
7502            );
7503        });
7504    });
7505}
7506
7507#[derive(Debug)]
7508pub struct WorkspacePosition {
7509    pub window_bounds: Option<WindowBounds>,
7510    pub display: Option<Uuid>,
7511    pub centered_layout: bool,
7512}
7513
7514pub fn ssh_workspace_position_from_db(
7515    host: String,
7516    port: Option<u16>,
7517    user: Option<String>,
7518    paths_to_open: &[PathBuf],
7519    cx: &App,
7520) -> Task<Result<WorkspacePosition>> {
7521    let paths = paths_to_open
7522        .iter()
7523        .map(|path| path.to_string_lossy().to_string())
7524        .collect::<Vec<_>>();
7525
7526    cx.background_spawn(async move {
7527        let serialized_ssh_project = persistence::DB
7528            .get_or_create_ssh_project(host, port, paths, user)
7529            .await
7530            .context("fetching serialized ssh project")?;
7531        let serialized_workspace =
7532            persistence::DB.workspace_for_ssh_project(&serialized_ssh_project);
7533
7534        let (window_bounds, display) = if let Some(bounds) = window_bounds_env_override() {
7535            (Some(WindowBounds::Windowed(bounds)), None)
7536        } else {
7537            let restorable_bounds = serialized_workspace
7538                .as_ref()
7539                .and_then(|workspace| Some((workspace.display?, workspace.window_bounds?)))
7540                .or_else(|| {
7541                    let (display, window_bounds) = DB.last_window().log_err()?;
7542                    Some((display?, window_bounds?))
7543                });
7544
7545            if let Some((serialized_display, serialized_status)) = restorable_bounds {
7546                (Some(serialized_status.0), Some(serialized_display))
7547            } else {
7548                (None, None)
7549            }
7550        };
7551
7552        let centered_layout = serialized_workspace
7553            .as_ref()
7554            .map(|w| w.centered_layout)
7555            .unwrap_or(false);
7556
7557        Ok(WorkspacePosition {
7558            window_bounds,
7559            display,
7560            centered_layout,
7561        })
7562    })
7563}
7564
7565#[cfg(test)]
7566mod tests {
7567    use std::{cell::RefCell, rc::Rc};
7568
7569    use super::*;
7570    use crate::{
7571        dock::{PanelEvent, test::TestPanel},
7572        item::{
7573            ItemEvent,
7574            test::{TestItem, TestProjectItem},
7575        },
7576    };
7577    use fs::FakeFs;
7578    use gpui::{
7579        DismissEvent, Empty, EventEmitter, FocusHandle, Focusable, Render, TestAppContext,
7580        UpdateGlobal, VisualTestContext, px,
7581    };
7582    use project::{Project, ProjectEntryId};
7583    use serde_json::json;
7584    use settings::SettingsStore;
7585
7586    #[gpui::test]
7587    async fn test_tab_disambiguation(cx: &mut TestAppContext) {
7588        init_test(cx);
7589
7590        let fs = FakeFs::new(cx.executor());
7591        let project = Project::test(fs, [], cx).await;
7592        let (workspace, cx) =
7593            cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
7594
7595        // Adding an item with no ambiguity renders the tab without detail.
7596        let item1 = cx.new(|cx| {
7597            let mut item = TestItem::new(cx);
7598            item.tab_descriptions = Some(vec!["c", "b1/c", "a/b1/c"]);
7599            item
7600        });
7601        workspace.update_in(cx, |workspace, window, cx| {
7602            workspace.add_item_to_active_pane(Box::new(item1.clone()), None, true, window, cx);
7603        });
7604        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(0)));
7605
7606        // Adding an item that creates ambiguity increases the level of detail on
7607        // both tabs.
7608        let item2 = cx.new_window_entity(|_window, cx| {
7609            let mut item = TestItem::new(cx);
7610            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
7611            item
7612        });
7613        workspace.update_in(cx, |workspace, window, cx| {
7614            workspace.add_item_to_active_pane(Box::new(item2.clone()), None, true, window, cx);
7615        });
7616        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
7617        item2.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
7618
7619        // Adding an item that creates ambiguity increases the level of detail only
7620        // on the ambiguous tabs. In this case, the ambiguity can't be resolved so
7621        // we stop at the highest detail available.
7622        let item3 = cx.new(|cx| {
7623            let mut item = TestItem::new(cx);
7624            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
7625            item
7626        });
7627        workspace.update_in(cx, |workspace, window, cx| {
7628            workspace.add_item_to_active_pane(Box::new(item3.clone()), None, true, window, cx);
7629        });
7630        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
7631        item2.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
7632        item3.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
7633    }
7634
7635    #[gpui::test]
7636    async fn test_tracking_active_path(cx: &mut TestAppContext) {
7637        init_test(cx);
7638
7639        let fs = FakeFs::new(cx.executor());
7640        fs.insert_tree(
7641            "/root1",
7642            json!({
7643                "one.txt": "",
7644                "two.txt": "",
7645            }),
7646        )
7647        .await;
7648        fs.insert_tree(
7649            "/root2",
7650            json!({
7651                "three.txt": "",
7652            }),
7653        )
7654        .await;
7655
7656        let project = Project::test(fs, ["root1".as_ref()], cx).await;
7657        let (workspace, cx) =
7658            cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
7659        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
7660        let worktree_id = project.update(cx, |project, cx| {
7661            project.worktrees(cx).next().unwrap().read(cx).id()
7662        });
7663
7664        let item1 = cx.new(|cx| {
7665            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "one.txt", cx)])
7666        });
7667        let item2 = cx.new(|cx| {
7668            TestItem::new(cx).with_project_items(&[TestProjectItem::new(2, "two.txt", cx)])
7669        });
7670
7671        // Add an item to an empty pane
7672        workspace.update_in(cx, |workspace, window, cx| {
7673            workspace.add_item_to_active_pane(Box::new(item1), None, true, window, cx)
7674        });
7675        project.update(cx, |project, cx| {
7676            assert_eq!(
7677                project.active_entry(),
7678                project
7679                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
7680                    .map(|e| e.id)
7681            );
7682        });
7683        assert_eq!(cx.window_title().as_deref(), Some("root1 — one.txt"));
7684
7685        // Add a second item to a non-empty pane
7686        workspace.update_in(cx, |workspace, window, cx| {
7687            workspace.add_item_to_active_pane(Box::new(item2), None, true, window, cx)
7688        });
7689        assert_eq!(cx.window_title().as_deref(), Some("root1 — two.txt"));
7690        project.update(cx, |project, cx| {
7691            assert_eq!(
7692                project.active_entry(),
7693                project
7694                    .entry_for_path(&(worktree_id, "two.txt").into(), cx)
7695                    .map(|e| e.id)
7696            );
7697        });
7698
7699        // Close the active item
7700        pane.update_in(cx, |pane, window, cx| {
7701            pane.close_active_item(&Default::default(), window, cx)
7702                .unwrap()
7703        })
7704        .await
7705        .unwrap();
7706        assert_eq!(cx.window_title().as_deref(), Some("root1 — one.txt"));
7707        project.update(cx, |project, cx| {
7708            assert_eq!(
7709                project.active_entry(),
7710                project
7711                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
7712                    .map(|e| e.id)
7713            );
7714        });
7715
7716        // Add a project folder
7717        project
7718            .update(cx, |project, cx| {
7719                project.find_or_create_worktree("root2", true, cx)
7720            })
7721            .await
7722            .unwrap();
7723        assert_eq!(cx.window_title().as_deref(), Some("root1, root2 — one.txt"));
7724
7725        // Remove a project folder
7726        project.update(cx, |project, cx| project.remove_worktree(worktree_id, cx));
7727        assert_eq!(cx.window_title().as_deref(), Some("root2 — one.txt"));
7728    }
7729
7730    #[gpui::test]
7731    async fn test_close_window(cx: &mut TestAppContext) {
7732        init_test(cx);
7733
7734        let fs = FakeFs::new(cx.executor());
7735        fs.insert_tree("/root", json!({ "one": "" })).await;
7736
7737        let project = Project::test(fs, ["root".as_ref()], cx).await;
7738        let (workspace, cx) =
7739            cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
7740
7741        // When there are no dirty items, there's nothing to do.
7742        let item1 = cx.new(TestItem::new);
7743        workspace.update_in(cx, |w, window, cx| {
7744            w.add_item_to_active_pane(Box::new(item1.clone()), None, true, window, cx)
7745        });
7746        let task = workspace.update_in(cx, |w, window, cx| {
7747            w.prepare_to_close(CloseIntent::CloseWindow, window, cx)
7748        });
7749        assert!(task.await.unwrap());
7750
7751        // When there are dirty untitled items, prompt to save each one. If the user
7752        // cancels any prompt, then abort.
7753        let item2 = cx.new(|cx| TestItem::new(cx).with_dirty(true));
7754        let item3 = cx.new(|cx| {
7755            TestItem::new(cx)
7756                .with_dirty(true)
7757                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
7758        });
7759        workspace.update_in(cx, |w, window, cx| {
7760            w.add_item_to_active_pane(Box::new(item2.clone()), None, true, window, cx);
7761            w.add_item_to_active_pane(Box::new(item3.clone()), None, true, window, cx);
7762        });
7763        let task = workspace.update_in(cx, |w, window, cx| {
7764            w.prepare_to_close(CloseIntent::CloseWindow, window, cx)
7765        });
7766        cx.executor().run_until_parked();
7767        cx.simulate_prompt_answer("Cancel"); // cancel save all
7768        cx.executor().run_until_parked();
7769        assert!(!cx.has_pending_prompt());
7770        assert!(!task.await.unwrap());
7771    }
7772
7773    #[gpui::test]
7774    async fn test_close_window_with_serializable_items(cx: &mut TestAppContext) {
7775        init_test(cx);
7776
7777        // Register TestItem as a serializable item
7778        cx.update(|cx| {
7779            register_serializable_item::<TestItem>(cx);
7780        });
7781
7782        let fs = FakeFs::new(cx.executor());
7783        fs.insert_tree("/root", json!({ "one": "" })).await;
7784
7785        let project = Project::test(fs, ["root".as_ref()], cx).await;
7786        let (workspace, cx) =
7787            cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
7788
7789        // When there are dirty untitled items, but they can serialize, then there is no prompt.
7790        let item1 = cx.new(|cx| {
7791            TestItem::new(cx)
7792                .with_dirty(true)
7793                .with_serialize(|| Some(Task::ready(Ok(()))))
7794        });
7795        let item2 = cx.new(|cx| {
7796            TestItem::new(cx)
7797                .with_dirty(true)
7798                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
7799                .with_serialize(|| Some(Task::ready(Ok(()))))
7800        });
7801        workspace.update_in(cx, |w, window, cx| {
7802            w.add_item_to_active_pane(Box::new(item1.clone()), None, true, window, cx);
7803            w.add_item_to_active_pane(Box::new(item2.clone()), None, true, window, cx);
7804        });
7805        let task = workspace.update_in(cx, |w, window, cx| {
7806            w.prepare_to_close(CloseIntent::CloseWindow, window, cx)
7807        });
7808        assert!(task.await.unwrap());
7809    }
7810
7811    #[gpui::test]
7812    async fn test_close_pane_items(cx: &mut TestAppContext) {
7813        init_test(cx);
7814
7815        let fs = FakeFs::new(cx.executor());
7816
7817        let project = Project::test(fs, None, cx).await;
7818        let (workspace, cx) =
7819            cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
7820
7821        let item1 = cx.new(|cx| {
7822            TestItem::new(cx)
7823                .with_dirty(true)
7824                .with_project_items(&[dirty_project_item(1, "1.txt", cx)])
7825        });
7826        let item2 = cx.new(|cx| {
7827            TestItem::new(cx)
7828                .with_dirty(true)
7829                .with_conflict(true)
7830                .with_project_items(&[dirty_project_item(2, "2.txt", cx)])
7831        });
7832        let item3 = cx.new(|cx| {
7833            TestItem::new(cx)
7834                .with_dirty(true)
7835                .with_conflict(true)
7836                .with_project_items(&[dirty_project_item(3, "3.txt", cx)])
7837        });
7838        let item4 = cx.new(|cx| {
7839            TestItem::new(cx).with_dirty(true).with_project_items(&[{
7840                let project_item = TestProjectItem::new_untitled(cx);
7841                project_item.update(cx, |project_item, _| project_item.is_dirty = true);
7842                project_item
7843            }])
7844        });
7845        let pane = workspace.update_in(cx, |workspace, window, cx| {
7846            workspace.add_item_to_active_pane(Box::new(item1.clone()), None, true, window, cx);
7847            workspace.add_item_to_active_pane(Box::new(item2.clone()), None, true, window, cx);
7848            workspace.add_item_to_active_pane(Box::new(item3.clone()), None, true, window, cx);
7849            workspace.add_item_to_active_pane(Box::new(item4.clone()), None, true, window, cx);
7850            workspace.active_pane().clone()
7851        });
7852
7853        let close_items = pane.update_in(cx, |pane, window, cx| {
7854            pane.activate_item(1, true, true, window, cx);
7855            assert_eq!(pane.active_item().unwrap().item_id(), item2.item_id());
7856            let item1_id = item1.item_id();
7857            let item3_id = item3.item_id();
7858            let item4_id = item4.item_id();
7859            pane.close_items(window, cx, SaveIntent::Close, move |id| {
7860                [item1_id, item3_id, item4_id].contains(&id)
7861            })
7862        });
7863        cx.executor().run_until_parked();
7864
7865        assert!(cx.has_pending_prompt());
7866        cx.simulate_prompt_answer("Save all");
7867
7868        cx.executor().run_until_parked();
7869
7870        // Item 1 is saved. There's a prompt to save item 3.
7871        pane.update(cx, |pane, cx| {
7872            assert_eq!(item1.read(cx).save_count, 1);
7873            assert_eq!(item1.read(cx).save_as_count, 0);
7874            assert_eq!(item1.read(cx).reload_count, 0);
7875            assert_eq!(pane.items_len(), 3);
7876            assert_eq!(pane.active_item().unwrap().item_id(), item3.item_id());
7877        });
7878        assert!(cx.has_pending_prompt());
7879
7880        // Cancel saving item 3.
7881        cx.simulate_prompt_answer("Discard");
7882        cx.executor().run_until_parked();
7883
7884        // Item 3 is reloaded. There's a prompt to save item 4.
7885        pane.update(cx, |pane, cx| {
7886            assert_eq!(item3.read(cx).save_count, 0);
7887            assert_eq!(item3.read(cx).save_as_count, 0);
7888            assert_eq!(item3.read(cx).reload_count, 1);
7889            assert_eq!(pane.items_len(), 2);
7890            assert_eq!(pane.active_item().unwrap().item_id(), item4.item_id());
7891        });
7892
7893        // There's a prompt for a path for item 4.
7894        cx.simulate_new_path_selection(|_| Some(Default::default()));
7895        close_items.await.unwrap();
7896
7897        // The requested items are closed.
7898        pane.update(cx, |pane, cx| {
7899            assert_eq!(item4.read(cx).save_count, 0);
7900            assert_eq!(item4.read(cx).save_as_count, 1);
7901            assert_eq!(item4.read(cx).reload_count, 0);
7902            assert_eq!(pane.items_len(), 1);
7903            assert_eq!(pane.active_item().unwrap().item_id(), item2.item_id());
7904        });
7905    }
7906
7907    #[gpui::test]
7908    async fn test_prompting_to_save_only_on_last_item_for_entry(cx: &mut TestAppContext) {
7909        init_test(cx);
7910
7911        let fs = FakeFs::new(cx.executor());
7912        let project = Project::test(fs, [], cx).await;
7913        let (workspace, cx) =
7914            cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
7915
7916        // Create several workspace items with single project entries, and two
7917        // workspace items with multiple project entries.
7918        let single_entry_items = (0..=4)
7919            .map(|project_entry_id| {
7920                cx.new(|cx| {
7921                    TestItem::new(cx)
7922                        .with_dirty(true)
7923                        .with_project_items(&[dirty_project_item(
7924                            project_entry_id,
7925                            &format!("{project_entry_id}.txt"),
7926                            cx,
7927                        )])
7928                })
7929            })
7930            .collect::<Vec<_>>();
7931        let item_2_3 = cx.new(|cx| {
7932            TestItem::new(cx)
7933                .with_dirty(true)
7934                .with_singleton(false)
7935                .with_project_items(&[
7936                    single_entry_items[2].read(cx).project_items[0].clone(),
7937                    single_entry_items[3].read(cx).project_items[0].clone(),
7938                ])
7939        });
7940        let item_3_4 = cx.new(|cx| {
7941            TestItem::new(cx)
7942                .with_dirty(true)
7943                .with_singleton(false)
7944                .with_project_items(&[
7945                    single_entry_items[3].read(cx).project_items[0].clone(),
7946                    single_entry_items[4].read(cx).project_items[0].clone(),
7947                ])
7948        });
7949
7950        // Create two panes that contain the following project entries:
7951        //   left pane:
7952        //     multi-entry items:   (2, 3)
7953        //     single-entry items:  0, 2, 3, 4
7954        //   right pane:
7955        //     single-entry items:  4, 1
7956        //     multi-entry items:   (3, 4)
7957        let (left_pane, right_pane) = workspace.update_in(cx, |workspace, window, cx| {
7958            let left_pane = workspace.active_pane().clone();
7959            workspace.add_item_to_active_pane(Box::new(item_2_3.clone()), None, true, window, cx);
7960            workspace.add_item_to_active_pane(
7961                single_entry_items[0].boxed_clone(),
7962                None,
7963                true,
7964                window,
7965                cx,
7966            );
7967            workspace.add_item_to_active_pane(
7968                single_entry_items[2].boxed_clone(),
7969                None,
7970                true,
7971                window,
7972                cx,
7973            );
7974            workspace.add_item_to_active_pane(
7975                single_entry_items[3].boxed_clone(),
7976                None,
7977                true,
7978                window,
7979                cx,
7980            );
7981            workspace.add_item_to_active_pane(
7982                single_entry_items[4].boxed_clone(),
7983                None,
7984                true,
7985                window,
7986                cx,
7987            );
7988
7989            let right_pane = workspace
7990                .split_and_clone(left_pane.clone(), SplitDirection::Right, window, cx)
7991                .unwrap();
7992
7993            right_pane.update(cx, |pane, cx| {
7994                pane.add_item(
7995                    single_entry_items[1].boxed_clone(),
7996                    true,
7997                    true,
7998                    None,
7999                    window,
8000                    cx,
8001                );
8002                pane.add_item(Box::new(item_3_4.clone()), true, true, None, window, cx);
8003            });
8004
8005            (left_pane, right_pane)
8006        });
8007
8008        cx.focus(&right_pane);
8009
8010        let mut close = right_pane.update_in(cx, |pane, window, cx| {
8011            pane.close_all_items(&CloseAllItems::default(), window, cx)
8012                .unwrap()
8013        });
8014        cx.executor().run_until_parked();
8015
8016        let msg = cx.pending_prompt().unwrap().0;
8017        assert!(msg.contains("1.txt"));
8018        assert!(!msg.contains("2.txt"));
8019        assert!(!msg.contains("3.txt"));
8020        assert!(!msg.contains("4.txt"));
8021
8022        cx.simulate_prompt_answer("Cancel");
8023        close.await.unwrap();
8024
8025        left_pane
8026            .update_in(cx, |left_pane, window, cx| {
8027                left_pane.close_item_by_id(
8028                    single_entry_items[3].entity_id(),
8029                    SaveIntent::Skip,
8030                    window,
8031                    cx,
8032                )
8033            })
8034            .await
8035            .unwrap();
8036
8037        close = right_pane.update_in(cx, |pane, window, cx| {
8038            pane.close_all_items(&CloseAllItems::default(), window, cx)
8039                .unwrap()
8040        });
8041        cx.executor().run_until_parked();
8042
8043        let details = cx.pending_prompt().unwrap().1;
8044        assert!(details.contains("1.txt"));
8045        assert!(!details.contains("2.txt"));
8046        assert!(details.contains("3.txt"));
8047        // ideally this assertion could be made, but today we can only
8048        // save whole items not project items, so the orphaned item 3 causes
8049        // 4 to be saved too.
8050        // assert!(!details.contains("4.txt"));
8051
8052        cx.simulate_prompt_answer("Save all");
8053
8054        cx.executor().run_until_parked();
8055        close.await.unwrap();
8056        right_pane.update(cx, |pane, _| {
8057            assert_eq!(pane.items_len(), 0);
8058        });
8059    }
8060
8061    #[gpui::test]
8062    async fn test_autosave(cx: &mut gpui::TestAppContext) {
8063        init_test(cx);
8064
8065        let fs = FakeFs::new(cx.executor());
8066        let project = Project::test(fs, [], cx).await;
8067        let (workspace, cx) =
8068            cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
8069        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
8070
8071        let item = cx.new(|cx| {
8072            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
8073        });
8074        let item_id = item.entity_id();
8075        workspace.update_in(cx, |workspace, window, cx| {
8076            workspace.add_item_to_active_pane(Box::new(item.clone()), None, true, window, cx);
8077        });
8078
8079        // Autosave on window change.
8080        item.update(cx, |item, cx| {
8081            SettingsStore::update_global(cx, |settings, cx| {
8082                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
8083                    settings.autosave = Some(AutosaveSetting::OnWindowChange);
8084                })
8085            });
8086            item.is_dirty = true;
8087        });
8088
8089        // Deactivating the window saves the file.
8090        cx.deactivate_window();
8091        item.update(cx, |item, _| assert_eq!(item.save_count, 1));
8092
8093        // Re-activating the window doesn't save the file.
8094        cx.update(|window, _| window.activate_window());
8095        cx.executor().run_until_parked();
8096        item.update(cx, |item, _| assert_eq!(item.save_count, 1));
8097
8098        // Autosave on focus change.
8099        item.update_in(cx, |item, window, cx| {
8100            cx.focus_self(window);
8101            SettingsStore::update_global(cx, |settings, cx| {
8102                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
8103                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
8104                })
8105            });
8106            item.is_dirty = true;
8107        });
8108
8109        // Blurring the item saves the file.
8110        item.update_in(cx, |_, window, _| window.blur());
8111        cx.executor().run_until_parked();
8112        item.update(cx, |item, _| assert_eq!(item.save_count, 2));
8113
8114        // Deactivating the window still saves the file.
8115        item.update_in(cx, |item, window, cx| {
8116            cx.focus_self(window);
8117            item.is_dirty = true;
8118        });
8119        cx.deactivate_window();
8120        item.update(cx, |item, _| assert_eq!(item.save_count, 3));
8121
8122        // Autosave after delay.
8123        item.update(cx, |item, cx| {
8124            SettingsStore::update_global(cx, |settings, cx| {
8125                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
8126                    settings.autosave = Some(AutosaveSetting::AfterDelay { milliseconds: 500 });
8127                })
8128            });
8129            item.is_dirty = true;
8130            cx.emit(ItemEvent::Edit);
8131        });
8132
8133        // Delay hasn't fully expired, so the file is still dirty and unsaved.
8134        cx.executor().advance_clock(Duration::from_millis(250));
8135        item.update(cx, |item, _| assert_eq!(item.save_count, 3));
8136
8137        // After delay expires, the file is saved.
8138        cx.executor().advance_clock(Duration::from_millis(250));
8139        item.update(cx, |item, _| assert_eq!(item.save_count, 4));
8140
8141        // Autosave on focus change, ensuring closing the tab counts as such.
8142        item.update(cx, |item, cx| {
8143            SettingsStore::update_global(cx, |settings, cx| {
8144                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
8145                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
8146                })
8147            });
8148            item.is_dirty = true;
8149            for project_item in &mut item.project_items {
8150                project_item.update(cx, |project_item, _| project_item.is_dirty = true);
8151            }
8152        });
8153
8154        pane.update_in(cx, |pane, window, cx| {
8155            pane.close_items(window, cx, SaveIntent::Close, move |id| id == item_id)
8156        })
8157        .await
8158        .unwrap();
8159        assert!(!cx.has_pending_prompt());
8160        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
8161
8162        // Add the item again, ensuring autosave is prevented if the underlying file has been deleted.
8163        workspace.update_in(cx, |workspace, window, cx| {
8164            workspace.add_item_to_active_pane(Box::new(item.clone()), None, true, window, cx);
8165        });
8166        item.update_in(cx, |item, window, cx| {
8167            item.project_items[0].update(cx, |item, _| {
8168                item.entry_id = None;
8169            });
8170            item.is_dirty = true;
8171            window.blur();
8172        });
8173        cx.run_until_parked();
8174        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
8175
8176        // Ensure autosave is prevented for deleted files also when closing the buffer.
8177        let _close_items = pane.update_in(cx, |pane, window, cx| {
8178            pane.close_items(window, cx, SaveIntent::Close, move |id| id == item_id)
8179        });
8180        cx.run_until_parked();
8181        assert!(cx.has_pending_prompt());
8182        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
8183    }
8184
8185    #[gpui::test]
8186    async fn test_pane_navigation(cx: &mut gpui::TestAppContext) {
8187        init_test(cx);
8188
8189        let fs = FakeFs::new(cx.executor());
8190
8191        let project = Project::test(fs, [], cx).await;
8192        let (workspace, cx) =
8193            cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
8194
8195        let item = cx.new(|cx| {
8196            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
8197        });
8198        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
8199        let toolbar = pane.update(cx, |pane, _| pane.toolbar().clone());
8200        let toolbar_notify_count = Rc::new(RefCell::new(0));
8201
8202        workspace.update_in(cx, |workspace, window, cx| {
8203            workspace.add_item_to_active_pane(Box::new(item.clone()), None, true, window, cx);
8204            let toolbar_notification_count = toolbar_notify_count.clone();
8205            cx.observe_in(&toolbar, window, move |_, _, _, _| {
8206                *toolbar_notification_count.borrow_mut() += 1
8207            })
8208            .detach();
8209        });
8210
8211        pane.update(cx, |pane, _| {
8212            assert!(!pane.can_navigate_backward());
8213            assert!(!pane.can_navigate_forward());
8214        });
8215
8216        item.update_in(cx, |item, _, cx| {
8217            item.set_state("one".to_string(), cx);
8218        });
8219
8220        // Toolbar must be notified to re-render the navigation buttons
8221        assert_eq!(*toolbar_notify_count.borrow(), 1);
8222
8223        pane.update(cx, |pane, _| {
8224            assert!(pane.can_navigate_backward());
8225            assert!(!pane.can_navigate_forward());
8226        });
8227
8228        workspace
8229            .update_in(cx, |workspace, window, cx| {
8230                workspace.go_back(pane.downgrade(), window, cx)
8231            })
8232            .await
8233            .unwrap();
8234
8235        assert_eq!(*toolbar_notify_count.borrow(), 2);
8236        pane.update(cx, |pane, _| {
8237            assert!(!pane.can_navigate_backward());
8238            assert!(pane.can_navigate_forward());
8239        });
8240    }
8241
8242    #[gpui::test]
8243    async fn test_toggle_docks_and_panels(cx: &mut gpui::TestAppContext) {
8244        init_test(cx);
8245        let fs = FakeFs::new(cx.executor());
8246
8247        let project = Project::test(fs, [], cx).await;
8248        let (workspace, cx) =
8249            cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
8250
8251        let panel = workspace.update_in(cx, |workspace, window, cx| {
8252            let panel = cx.new(|cx| TestPanel::new(DockPosition::Right, cx));
8253            workspace.add_panel(panel.clone(), window, cx);
8254
8255            workspace
8256                .right_dock()
8257                .update(cx, |right_dock, cx| right_dock.set_open(true, window, cx));
8258
8259            panel
8260        });
8261
8262        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
8263        pane.update_in(cx, |pane, window, cx| {
8264            let item = cx.new(TestItem::new);
8265            pane.add_item(Box::new(item), true, true, None, window, cx);
8266        });
8267
8268        // Transfer focus from center to panel
8269        workspace.update_in(cx, |workspace, window, cx| {
8270            workspace.toggle_panel_focus::<TestPanel>(window, cx);
8271        });
8272
8273        workspace.update_in(cx, |workspace, window, cx| {
8274            assert!(workspace.right_dock().read(cx).is_open());
8275            assert!(!panel.is_zoomed(window, cx));
8276            assert!(panel.read(cx).focus_handle(cx).contains_focused(window, cx));
8277        });
8278
8279        // Transfer focus from panel to center
8280        workspace.update_in(cx, |workspace, window, cx| {
8281            workspace.toggle_panel_focus::<TestPanel>(window, cx);
8282        });
8283
8284        workspace.update_in(cx, |workspace, window, cx| {
8285            assert!(workspace.right_dock().read(cx).is_open());
8286            assert!(!panel.is_zoomed(window, cx));
8287            assert!(!panel.read(cx).focus_handle(cx).contains_focused(window, cx));
8288        });
8289
8290        // Close the dock
8291        workspace.update_in(cx, |workspace, window, cx| {
8292            workspace.toggle_dock(DockPosition::Right, window, cx);
8293        });
8294
8295        workspace.update_in(cx, |workspace, window, cx| {
8296            assert!(!workspace.right_dock().read(cx).is_open());
8297            assert!(!panel.is_zoomed(window, cx));
8298            assert!(!panel.read(cx).focus_handle(cx).contains_focused(window, cx));
8299        });
8300
8301        // Open the dock
8302        workspace.update_in(cx, |workspace, window, cx| {
8303            workspace.toggle_dock(DockPosition::Right, window, cx);
8304        });
8305
8306        workspace.update_in(cx, |workspace, window, cx| {
8307            assert!(workspace.right_dock().read(cx).is_open());
8308            assert!(!panel.is_zoomed(window, cx));
8309            assert!(panel.read(cx).focus_handle(cx).contains_focused(window, cx));
8310        });
8311
8312        // Focus and zoom panel
8313        panel.update_in(cx, |panel, window, cx| {
8314            cx.focus_self(window);
8315            panel.set_zoomed(true, window, cx)
8316        });
8317
8318        workspace.update_in(cx, |workspace, window, cx| {
8319            assert!(workspace.right_dock().read(cx).is_open());
8320            assert!(panel.is_zoomed(window, cx));
8321            assert!(panel.read(cx).focus_handle(cx).contains_focused(window, cx));
8322        });
8323
8324        // Transfer focus to the center closes the dock
8325        workspace.update_in(cx, |workspace, window, cx| {
8326            workspace.toggle_panel_focus::<TestPanel>(window, cx);
8327        });
8328
8329        workspace.update_in(cx, |workspace, window, cx| {
8330            assert!(!workspace.right_dock().read(cx).is_open());
8331            assert!(panel.is_zoomed(window, cx));
8332            assert!(!panel.read(cx).focus_handle(cx).contains_focused(window, cx));
8333        });
8334
8335        // Transferring focus back to the panel keeps it zoomed
8336        workspace.update_in(cx, |workspace, window, cx| {
8337            workspace.toggle_panel_focus::<TestPanel>(window, cx);
8338        });
8339
8340        workspace.update_in(cx, |workspace, window, cx| {
8341            assert!(workspace.right_dock().read(cx).is_open());
8342            assert!(panel.is_zoomed(window, cx));
8343            assert!(panel.read(cx).focus_handle(cx).contains_focused(window, cx));
8344        });
8345
8346        // Close the dock while it is zoomed
8347        workspace.update_in(cx, |workspace, window, cx| {
8348            workspace.toggle_dock(DockPosition::Right, window, cx)
8349        });
8350
8351        workspace.update_in(cx, |workspace, window, cx| {
8352            assert!(!workspace.right_dock().read(cx).is_open());
8353            assert!(panel.is_zoomed(window, cx));
8354            assert!(workspace.zoomed.is_none());
8355            assert!(!panel.read(cx).focus_handle(cx).contains_focused(window, cx));
8356        });
8357
8358        // Opening the dock, when it's zoomed, retains focus
8359        workspace.update_in(cx, |workspace, window, cx| {
8360            workspace.toggle_dock(DockPosition::Right, window, cx)
8361        });
8362
8363        workspace.update_in(cx, |workspace, window, cx| {
8364            assert!(workspace.right_dock().read(cx).is_open());
8365            assert!(panel.is_zoomed(window, cx));
8366            assert!(workspace.zoomed.is_some());
8367            assert!(panel.read(cx).focus_handle(cx).contains_focused(window, cx));
8368        });
8369
8370        // Unzoom and close the panel, zoom the active pane.
8371        panel.update_in(cx, |panel, window, cx| panel.set_zoomed(false, window, cx));
8372        workspace.update_in(cx, |workspace, window, cx| {
8373            workspace.toggle_dock(DockPosition::Right, window, cx)
8374        });
8375        pane.update_in(cx, |pane, window, cx| {
8376            pane.toggle_zoom(&Default::default(), window, cx)
8377        });
8378
8379        // Opening a dock unzooms the pane.
8380        workspace.update_in(cx, |workspace, window, cx| {
8381            workspace.toggle_dock(DockPosition::Right, window, cx)
8382        });
8383        workspace.update_in(cx, |workspace, window, cx| {
8384            let pane = pane.read(cx);
8385            assert!(!pane.is_zoomed());
8386            assert!(!pane.focus_handle(cx).is_focused(window));
8387            assert!(workspace.right_dock().read(cx).is_open());
8388            assert!(workspace.zoomed.is_none());
8389        });
8390    }
8391
8392    #[gpui::test]
8393    async fn test_join_pane_into_next(cx: &mut gpui::TestAppContext) {
8394        init_test(cx);
8395
8396        let fs = FakeFs::new(cx.executor());
8397
8398        let project = Project::test(fs, None, cx).await;
8399        let (workspace, cx) =
8400            cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
8401
8402        // Let's arrange the panes like this:
8403        //
8404        // +-----------------------+
8405        // |         top           |
8406        // +------+--------+-------+
8407        // | left | center | right |
8408        // +------+--------+-------+
8409        // |        bottom         |
8410        // +-----------------------+
8411
8412        let top_item = cx.new(|cx| {
8413            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "top.txt", cx)])
8414        });
8415        let bottom_item = cx.new(|cx| {
8416            TestItem::new(cx).with_project_items(&[TestProjectItem::new(2, "bottom.txt", cx)])
8417        });
8418        let left_item = cx.new(|cx| {
8419            TestItem::new(cx).with_project_items(&[TestProjectItem::new(3, "left.txt", cx)])
8420        });
8421        let right_item = cx.new(|cx| {
8422            TestItem::new(cx).with_project_items(&[TestProjectItem::new(4, "right.txt", cx)])
8423        });
8424        let center_item = cx.new(|cx| {
8425            TestItem::new(cx).with_project_items(&[TestProjectItem::new(5, "center.txt", cx)])
8426        });
8427
8428        let top_pane_id = workspace.update_in(cx, |workspace, window, cx| {
8429            let top_pane_id = workspace.active_pane().entity_id();
8430            workspace.add_item_to_active_pane(Box::new(top_item.clone()), None, false, window, cx);
8431            workspace.split_pane(
8432                workspace.active_pane().clone(),
8433                SplitDirection::Down,
8434                window,
8435                cx,
8436            );
8437            top_pane_id
8438        });
8439        let bottom_pane_id = workspace.update_in(cx, |workspace, window, cx| {
8440            let bottom_pane_id = workspace.active_pane().entity_id();
8441            workspace.add_item_to_active_pane(
8442                Box::new(bottom_item.clone()),
8443                None,
8444                false,
8445                window,
8446                cx,
8447            );
8448            workspace.split_pane(
8449                workspace.active_pane().clone(),
8450                SplitDirection::Up,
8451                window,
8452                cx,
8453            );
8454            bottom_pane_id
8455        });
8456        let left_pane_id = workspace.update_in(cx, |workspace, window, cx| {
8457            let left_pane_id = workspace.active_pane().entity_id();
8458            workspace.add_item_to_active_pane(Box::new(left_item.clone()), None, false, window, cx);
8459            workspace.split_pane(
8460                workspace.active_pane().clone(),
8461                SplitDirection::Right,
8462                window,
8463                cx,
8464            );
8465            left_pane_id
8466        });
8467        let right_pane_id = workspace.update_in(cx, |workspace, window, cx| {
8468            let right_pane_id = workspace.active_pane().entity_id();
8469            workspace.add_item_to_active_pane(
8470                Box::new(right_item.clone()),
8471                None,
8472                false,
8473                window,
8474                cx,
8475            );
8476            workspace.split_pane(
8477                workspace.active_pane().clone(),
8478                SplitDirection::Left,
8479                window,
8480                cx,
8481            );
8482            right_pane_id
8483        });
8484        let center_pane_id = workspace.update_in(cx, |workspace, window, cx| {
8485            let center_pane_id = workspace.active_pane().entity_id();
8486            workspace.add_item_to_active_pane(
8487                Box::new(center_item.clone()),
8488                None,
8489                false,
8490                window,
8491                cx,
8492            );
8493            center_pane_id
8494        });
8495        cx.executor().run_until_parked();
8496
8497        workspace.update_in(cx, |workspace, window, cx| {
8498            assert_eq!(center_pane_id, workspace.active_pane().entity_id());
8499
8500            // Join into next from center pane into right
8501            workspace.join_pane_into_next(workspace.active_pane().clone(), window, cx);
8502        });
8503
8504        workspace.update_in(cx, |workspace, window, cx| {
8505            let active_pane = workspace.active_pane();
8506            assert_eq!(right_pane_id, active_pane.entity_id());
8507            assert_eq!(2, active_pane.read(cx).items_len());
8508            let item_ids_in_pane =
8509                HashSet::from_iter(active_pane.read(cx).items().map(|item| item.item_id()));
8510            assert!(item_ids_in_pane.contains(&center_item.item_id()));
8511            assert!(item_ids_in_pane.contains(&right_item.item_id()));
8512
8513            // Join into next from right pane into bottom
8514            workspace.join_pane_into_next(workspace.active_pane().clone(), window, cx);
8515        });
8516
8517        workspace.update_in(cx, |workspace, window, cx| {
8518            let active_pane = workspace.active_pane();
8519            assert_eq!(bottom_pane_id, active_pane.entity_id());
8520            assert_eq!(3, active_pane.read(cx).items_len());
8521            let item_ids_in_pane =
8522                HashSet::from_iter(active_pane.read(cx).items().map(|item| item.item_id()));
8523            assert!(item_ids_in_pane.contains(&center_item.item_id()));
8524            assert!(item_ids_in_pane.contains(&right_item.item_id()));
8525            assert!(item_ids_in_pane.contains(&bottom_item.item_id()));
8526
8527            // Join into next from bottom pane into left
8528            workspace.join_pane_into_next(workspace.active_pane().clone(), window, cx);
8529        });
8530
8531        workspace.update_in(cx, |workspace, window, cx| {
8532            let active_pane = workspace.active_pane();
8533            assert_eq!(left_pane_id, active_pane.entity_id());
8534            assert_eq!(4, active_pane.read(cx).items_len());
8535            let item_ids_in_pane =
8536                HashSet::from_iter(active_pane.read(cx).items().map(|item| item.item_id()));
8537            assert!(item_ids_in_pane.contains(&center_item.item_id()));
8538            assert!(item_ids_in_pane.contains(&right_item.item_id()));
8539            assert!(item_ids_in_pane.contains(&bottom_item.item_id()));
8540            assert!(item_ids_in_pane.contains(&left_item.item_id()));
8541
8542            // Join into next from left pane into top
8543            workspace.join_pane_into_next(workspace.active_pane().clone(), window, cx);
8544        });
8545
8546        workspace.update_in(cx, |workspace, window, cx| {
8547            let active_pane = workspace.active_pane();
8548            assert_eq!(top_pane_id, active_pane.entity_id());
8549            assert_eq!(5, active_pane.read(cx).items_len());
8550            let item_ids_in_pane =
8551                HashSet::from_iter(active_pane.read(cx).items().map(|item| item.item_id()));
8552            assert!(item_ids_in_pane.contains(&center_item.item_id()));
8553            assert!(item_ids_in_pane.contains(&right_item.item_id()));
8554            assert!(item_ids_in_pane.contains(&bottom_item.item_id()));
8555            assert!(item_ids_in_pane.contains(&left_item.item_id()));
8556            assert!(item_ids_in_pane.contains(&top_item.item_id()));
8557
8558            // Single pane left: no-op
8559            workspace.join_pane_into_next(workspace.active_pane().clone(), window, cx)
8560        });
8561
8562        workspace.update(cx, |workspace, _cx| {
8563            let active_pane = workspace.active_pane();
8564            assert_eq!(top_pane_id, active_pane.entity_id());
8565        });
8566    }
8567
8568    fn add_an_item_to_active_pane(
8569        cx: &mut VisualTestContext,
8570        workspace: &Entity<Workspace>,
8571        item_id: u64,
8572    ) -> Entity<TestItem> {
8573        let item = cx.new(|cx| {
8574            TestItem::new(cx).with_project_items(&[TestProjectItem::new(
8575                item_id,
8576                "item{item_id}.txt",
8577                cx,
8578            )])
8579        });
8580        workspace.update_in(cx, |workspace, window, cx| {
8581            workspace.add_item_to_active_pane(Box::new(item.clone()), None, false, window, cx);
8582        });
8583        return item;
8584    }
8585
8586    fn split_pane(cx: &mut VisualTestContext, workspace: &Entity<Workspace>) -> Entity<Pane> {
8587        return workspace.update_in(cx, |workspace, window, cx| {
8588            let new_pane = workspace.split_pane(
8589                workspace.active_pane().clone(),
8590                SplitDirection::Right,
8591                window,
8592                cx,
8593            );
8594            new_pane
8595        });
8596    }
8597
8598    #[gpui::test]
8599    async fn test_join_all_panes(cx: &mut gpui::TestAppContext) {
8600        init_test(cx);
8601        let fs = FakeFs::new(cx.executor());
8602        let project = Project::test(fs, None, cx).await;
8603        let (workspace, cx) =
8604            cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
8605
8606        add_an_item_to_active_pane(cx, &workspace, 1);
8607        split_pane(cx, &workspace);
8608        add_an_item_to_active_pane(cx, &workspace, 2);
8609        split_pane(cx, &workspace); // empty pane
8610        split_pane(cx, &workspace);
8611        let last_item = add_an_item_to_active_pane(cx, &workspace, 3);
8612
8613        cx.executor().run_until_parked();
8614
8615        workspace.update(cx, |workspace, cx| {
8616            let num_panes = workspace.panes().len();
8617            let num_items_in_current_pane = workspace.active_pane().read(cx).items().count();
8618            let active_item = workspace
8619                .active_pane()
8620                .read(cx)
8621                .active_item()
8622                .expect("item is in focus");
8623
8624            assert_eq!(num_panes, 4);
8625            assert_eq!(num_items_in_current_pane, 1);
8626            assert_eq!(active_item.item_id(), last_item.item_id());
8627        });
8628
8629        workspace.update_in(cx, |workspace, window, cx| {
8630            workspace.join_all_panes(window, cx);
8631        });
8632
8633        workspace.update(cx, |workspace, cx| {
8634            let num_panes = workspace.panes().len();
8635            let num_items_in_current_pane = workspace.active_pane().read(cx).items().count();
8636            let active_item = workspace
8637                .active_pane()
8638                .read(cx)
8639                .active_item()
8640                .expect("item is in focus");
8641
8642            assert_eq!(num_panes, 1);
8643            assert_eq!(num_items_in_current_pane, 3);
8644            assert_eq!(active_item.item_id(), last_item.item_id());
8645        });
8646    }
8647    struct TestModal(FocusHandle);
8648
8649    impl TestModal {
8650        fn new(_: &mut Window, cx: &mut Context<Self>) -> Self {
8651            Self(cx.focus_handle())
8652        }
8653    }
8654
8655    impl EventEmitter<DismissEvent> for TestModal {}
8656
8657    impl Focusable for TestModal {
8658        fn focus_handle(&self, _cx: &App) -> FocusHandle {
8659            self.0.clone()
8660        }
8661    }
8662
8663    impl ModalView for TestModal {}
8664
8665    impl Render for TestModal {
8666        fn render(
8667            &mut self,
8668            _window: &mut Window,
8669            _cx: &mut Context<TestModal>,
8670        ) -> impl IntoElement {
8671            div().track_focus(&self.0)
8672        }
8673    }
8674
8675    #[gpui::test]
8676    async fn test_panels(cx: &mut gpui::TestAppContext) {
8677        init_test(cx);
8678        let fs = FakeFs::new(cx.executor());
8679
8680        let project = Project::test(fs, [], cx).await;
8681        let (workspace, cx) =
8682            cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
8683
8684        let (panel_1, panel_2) = workspace.update_in(cx, |workspace, window, cx| {
8685            let panel_1 = cx.new(|cx| TestPanel::new(DockPosition::Left, cx));
8686            workspace.add_panel(panel_1.clone(), window, cx);
8687            workspace.toggle_dock(DockPosition::Left, window, cx);
8688            let panel_2 = cx.new(|cx| TestPanel::new(DockPosition::Right, cx));
8689            workspace.add_panel(panel_2.clone(), window, cx);
8690            workspace.toggle_dock(DockPosition::Right, window, cx);
8691
8692            let left_dock = workspace.left_dock();
8693            assert_eq!(
8694                left_dock.read(cx).visible_panel().unwrap().panel_id(),
8695                panel_1.panel_id()
8696            );
8697            assert_eq!(
8698                left_dock.read(cx).active_panel_size(window, cx).unwrap(),
8699                panel_1.size(window, cx)
8700            );
8701
8702            left_dock.update(cx, |left_dock, cx| {
8703                left_dock.resize_active_panel(Some(px(1337.)), window, cx)
8704            });
8705            assert_eq!(
8706                workspace
8707                    .right_dock()
8708                    .read(cx)
8709                    .visible_panel()
8710                    .unwrap()
8711                    .panel_id(),
8712                panel_2.panel_id(),
8713            );
8714
8715            (panel_1, panel_2)
8716        });
8717
8718        // Move panel_1 to the right
8719        panel_1.update_in(cx, |panel_1, window, cx| {
8720            panel_1.set_position(DockPosition::Right, window, cx)
8721        });
8722
8723        workspace.update_in(cx, |workspace, window, cx| {
8724            // Since panel_1 was visible on the left, it should now be visible now that it's been moved to the right.
8725            // Since it was the only panel on the left, the left dock should now be closed.
8726            assert!(!workspace.left_dock().read(cx).is_open());
8727            assert!(workspace.left_dock().read(cx).visible_panel().is_none());
8728            let right_dock = workspace.right_dock();
8729            assert_eq!(
8730                right_dock.read(cx).visible_panel().unwrap().panel_id(),
8731                panel_1.panel_id()
8732            );
8733            assert_eq!(
8734                right_dock.read(cx).active_panel_size(window, cx).unwrap(),
8735                px(1337.)
8736            );
8737
8738            // Now we move panel_2 to the left
8739            panel_2.set_position(DockPosition::Left, window, cx);
8740        });
8741
8742        workspace.update(cx, |workspace, cx| {
8743            // Since panel_2 was not visible on the right, we don't open the left dock.
8744            assert!(!workspace.left_dock().read(cx).is_open());
8745            // And the right dock is unaffected in its displaying of panel_1
8746            assert!(workspace.right_dock().read(cx).is_open());
8747            assert_eq!(
8748                workspace
8749                    .right_dock()
8750                    .read(cx)
8751                    .visible_panel()
8752                    .unwrap()
8753                    .panel_id(),
8754                panel_1.panel_id(),
8755            );
8756        });
8757
8758        // Move panel_1 back to the left
8759        panel_1.update_in(cx, |panel_1, window, cx| {
8760            panel_1.set_position(DockPosition::Left, window, cx)
8761        });
8762
8763        workspace.update_in(cx, |workspace, window, cx| {
8764            // Since panel_1 was visible on the right, we open the left dock and make panel_1 active.
8765            let left_dock = workspace.left_dock();
8766            assert!(left_dock.read(cx).is_open());
8767            assert_eq!(
8768                left_dock.read(cx).visible_panel().unwrap().panel_id(),
8769                panel_1.panel_id()
8770            );
8771            assert_eq!(
8772                left_dock.read(cx).active_panel_size(window, cx).unwrap(),
8773                px(1337.)
8774            );
8775            // And the right dock should be closed as it no longer has any panels.
8776            assert!(!workspace.right_dock().read(cx).is_open());
8777
8778            // Now we move panel_1 to the bottom
8779            panel_1.set_position(DockPosition::Bottom, window, cx);
8780        });
8781
8782        workspace.update_in(cx, |workspace, window, cx| {
8783            // Since panel_1 was visible on the left, we close the left dock.
8784            assert!(!workspace.left_dock().read(cx).is_open());
8785            // The bottom dock is sized based on the panel's default size,
8786            // since the panel orientation changed from vertical to horizontal.
8787            let bottom_dock = workspace.bottom_dock();
8788            assert_eq!(
8789                bottom_dock.read(cx).active_panel_size(window, cx).unwrap(),
8790                panel_1.size(window, cx),
8791            );
8792            // Close bottom dock and move panel_1 back to the left.
8793            bottom_dock.update(cx, |bottom_dock, cx| {
8794                bottom_dock.set_open(false, window, cx)
8795            });
8796            panel_1.set_position(DockPosition::Left, window, cx);
8797        });
8798
8799        // Emit activated event on panel 1
8800        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::Activate));
8801
8802        // Now the left dock is open and panel_1 is active and focused.
8803        workspace.update_in(cx, |workspace, window, cx| {
8804            let left_dock = workspace.left_dock();
8805            assert!(left_dock.read(cx).is_open());
8806            assert_eq!(
8807                left_dock.read(cx).visible_panel().unwrap().panel_id(),
8808                panel_1.panel_id(),
8809            );
8810            assert!(panel_1.focus_handle(cx).is_focused(window));
8811        });
8812
8813        // Emit closed event on panel 2, which is not active
8814        panel_2.update(cx, |_, cx| cx.emit(PanelEvent::Close));
8815
8816        // Wo don't close the left dock, because panel_2 wasn't the active panel
8817        workspace.update(cx, |workspace, cx| {
8818            let left_dock = workspace.left_dock();
8819            assert!(left_dock.read(cx).is_open());
8820            assert_eq!(
8821                left_dock.read(cx).visible_panel().unwrap().panel_id(),
8822                panel_1.panel_id(),
8823            );
8824        });
8825
8826        // Emitting a ZoomIn event shows the panel as zoomed.
8827        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::ZoomIn));
8828        workspace.update(cx, |workspace, _| {
8829            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
8830            assert_eq!(workspace.zoomed_position, Some(DockPosition::Left));
8831        });
8832
8833        // Move panel to another dock while it is zoomed
8834        panel_1.update_in(cx, |panel, window, cx| {
8835            panel.set_position(DockPosition::Right, window, cx)
8836        });
8837        workspace.update(cx, |workspace, _| {
8838            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
8839
8840            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
8841        });
8842
8843        // This is a helper for getting a:
8844        // - valid focus on an element,
8845        // - that isn't a part of the panes and panels system of the Workspace,
8846        // - and doesn't trigger the 'on_focus_lost' API.
8847        let focus_other_view = {
8848            let workspace = workspace.clone();
8849            move |cx: &mut VisualTestContext| {
8850                workspace.update_in(cx, |workspace, window, cx| {
8851                    if let Some(_) = workspace.active_modal::<TestModal>(cx) {
8852                        workspace.toggle_modal(window, cx, TestModal::new);
8853                        workspace.toggle_modal(window, cx, TestModal::new);
8854                    } else {
8855                        workspace.toggle_modal(window, cx, TestModal::new);
8856                    }
8857                })
8858            }
8859        };
8860
8861        // If focus is transferred to another view that's not a panel or another pane, we still show
8862        // the panel as zoomed.
8863        focus_other_view(cx);
8864        workspace.update(cx, |workspace, _| {
8865            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
8866            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
8867        });
8868
8869        // If focus is transferred elsewhere in the workspace, the panel is no longer zoomed.
8870        workspace.update_in(cx, |_workspace, window, cx| {
8871            cx.focus_self(window);
8872        });
8873        workspace.update(cx, |workspace, _| {
8874            assert_eq!(workspace.zoomed, None);
8875            assert_eq!(workspace.zoomed_position, None);
8876        });
8877
8878        // If focus is transferred again to another view that's not a panel or a pane, we won't
8879        // show the panel as zoomed because it wasn't zoomed before.
8880        focus_other_view(cx);
8881        workspace.update(cx, |workspace, _| {
8882            assert_eq!(workspace.zoomed, None);
8883            assert_eq!(workspace.zoomed_position, None);
8884        });
8885
8886        // When the panel is activated, it is zoomed again.
8887        cx.dispatch_action(ToggleRightDock);
8888        workspace.update(cx, |workspace, _| {
8889            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
8890            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
8891        });
8892
8893        // Emitting a ZoomOut event unzooms the panel.
8894        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::ZoomOut));
8895        workspace.update(cx, |workspace, _| {
8896            assert_eq!(workspace.zoomed, None);
8897            assert_eq!(workspace.zoomed_position, None);
8898        });
8899
8900        // Emit closed event on panel 1, which is active
8901        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::Close));
8902
8903        // Now the left dock is closed, because panel_1 was the active panel
8904        workspace.update(cx, |workspace, cx| {
8905            let right_dock = workspace.right_dock();
8906            assert!(!right_dock.read(cx).is_open());
8907        });
8908    }
8909
8910    #[gpui::test]
8911    async fn test_no_save_prompt_when_multi_buffer_dirty_items_closed(cx: &mut TestAppContext) {
8912        init_test(cx);
8913
8914        let fs = FakeFs::new(cx.background_executor.clone());
8915        let project = Project::test(fs, [], cx).await;
8916        let (workspace, cx) =
8917            cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
8918        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
8919
8920        let dirty_regular_buffer = cx.new(|cx| {
8921            TestItem::new(cx)
8922                .with_dirty(true)
8923                .with_label("1.txt")
8924                .with_project_items(&[dirty_project_item(1, "1.txt", cx)])
8925        });
8926        let dirty_regular_buffer_2 = cx.new(|cx| {
8927            TestItem::new(cx)
8928                .with_dirty(true)
8929                .with_label("2.txt")
8930                .with_project_items(&[dirty_project_item(2, "2.txt", cx)])
8931        });
8932        let dirty_multi_buffer_with_both = cx.new(|cx| {
8933            TestItem::new(cx)
8934                .with_dirty(true)
8935                .with_singleton(false)
8936                .with_label("Fake Project Search")
8937                .with_project_items(&[
8938                    dirty_regular_buffer.read(cx).project_items[0].clone(),
8939                    dirty_regular_buffer_2.read(cx).project_items[0].clone(),
8940                ])
8941        });
8942        let multi_buffer_with_both_files_id = dirty_multi_buffer_with_both.item_id();
8943        workspace.update_in(cx, |workspace, window, cx| {
8944            workspace.add_item(
8945                pane.clone(),
8946                Box::new(dirty_regular_buffer.clone()),
8947                None,
8948                false,
8949                false,
8950                window,
8951                cx,
8952            );
8953            workspace.add_item(
8954                pane.clone(),
8955                Box::new(dirty_regular_buffer_2.clone()),
8956                None,
8957                false,
8958                false,
8959                window,
8960                cx,
8961            );
8962            workspace.add_item(
8963                pane.clone(),
8964                Box::new(dirty_multi_buffer_with_both.clone()),
8965                None,
8966                false,
8967                false,
8968                window,
8969                cx,
8970            );
8971        });
8972
8973        pane.update_in(cx, |pane, window, cx| {
8974            pane.activate_item(2, true, true, window, cx);
8975            assert_eq!(
8976                pane.active_item().unwrap().item_id(),
8977                multi_buffer_with_both_files_id,
8978                "Should select the multi buffer in the pane"
8979            );
8980        });
8981        let close_all_but_multi_buffer_task = pane
8982            .update_in(cx, |pane, window, cx| {
8983                pane.close_inactive_items(
8984                    &CloseInactiveItems {
8985                        save_intent: Some(SaveIntent::Save),
8986                        close_pinned: true,
8987                    },
8988                    window,
8989                    cx,
8990                )
8991            })
8992            .expect("should have inactive files to close");
8993        cx.background_executor.run_until_parked();
8994        assert!(!cx.has_pending_prompt());
8995        close_all_but_multi_buffer_task
8996            .await
8997            .expect("Closing all buffers but the multi buffer failed");
8998        pane.update(cx, |pane, cx| {
8999            assert_eq!(dirty_regular_buffer.read(cx).save_count, 1);
9000            assert_eq!(dirty_multi_buffer_with_both.read(cx).save_count, 0);
9001            assert_eq!(dirty_regular_buffer_2.read(cx).save_count, 1);
9002            assert_eq!(pane.items_len(), 1);
9003            assert_eq!(
9004                pane.active_item().unwrap().item_id(),
9005                multi_buffer_with_both_files_id,
9006                "Should have only the multi buffer left in the pane"
9007            );
9008            assert!(
9009                dirty_multi_buffer_with_both.read(cx).is_dirty,
9010                "The multi buffer containing the unsaved buffer should still be dirty"
9011            );
9012        });
9013
9014        dirty_regular_buffer.update(cx, |buffer, cx| {
9015            buffer.project_items[0].update(cx, |pi, _| pi.is_dirty = true)
9016        });
9017
9018        let close_multi_buffer_task = pane
9019            .update_in(cx, |pane, window, cx| {
9020                pane.close_active_item(
9021                    &CloseActiveItem {
9022                        save_intent: Some(SaveIntent::Close),
9023                        close_pinned: false,
9024                    },
9025                    window,
9026                    cx,
9027                )
9028            })
9029            .expect("should have the multi buffer to close");
9030        cx.background_executor.run_until_parked();
9031        assert!(
9032            cx.has_pending_prompt(),
9033            "Dirty multi buffer should prompt a save dialog"
9034        );
9035        cx.simulate_prompt_answer("Save");
9036        cx.background_executor.run_until_parked();
9037        close_multi_buffer_task
9038            .await
9039            .expect("Closing the multi buffer failed");
9040        pane.update(cx, |pane, cx| {
9041            assert_eq!(
9042                dirty_multi_buffer_with_both.read(cx).save_count,
9043                1,
9044                "Multi buffer item should get be saved"
9045            );
9046            // Test impl does not save inner items, so we do not assert them
9047            assert_eq!(
9048                pane.items_len(),
9049                0,
9050                "No more items should be left in the pane"
9051            );
9052            assert!(pane.active_item().is_none());
9053        });
9054    }
9055
9056    #[gpui::test]
9057    async fn test_save_prompt_when_dirty_multi_buffer_closed_with_some_of_its_dirty_items_not_present_in_the_pane(
9058        cx: &mut TestAppContext,
9059    ) {
9060        init_test(cx);
9061
9062        let fs = FakeFs::new(cx.background_executor.clone());
9063        let project = Project::test(fs, [], cx).await;
9064        let (workspace, cx) =
9065            cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
9066        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
9067
9068        let dirty_regular_buffer = cx.new(|cx| {
9069            TestItem::new(cx)
9070                .with_dirty(true)
9071                .with_label("1.txt")
9072                .with_project_items(&[dirty_project_item(1, "1.txt", cx)])
9073        });
9074        let dirty_regular_buffer_2 = cx.new(|cx| {
9075            TestItem::new(cx)
9076                .with_dirty(true)
9077                .with_label("2.txt")
9078                .with_project_items(&[dirty_project_item(2, "2.txt", cx)])
9079        });
9080        let clear_regular_buffer = cx.new(|cx| {
9081            TestItem::new(cx)
9082                .with_label("3.txt")
9083                .with_project_items(&[TestProjectItem::new(3, "3.txt", cx)])
9084        });
9085
9086        let dirty_multi_buffer_with_both = cx.new(|cx| {
9087            TestItem::new(cx)
9088                .with_dirty(true)
9089                .with_singleton(false)
9090                .with_label("Fake Project Search")
9091                .with_project_items(&[
9092                    dirty_regular_buffer.read(cx).project_items[0].clone(),
9093                    dirty_regular_buffer_2.read(cx).project_items[0].clone(),
9094                    clear_regular_buffer.read(cx).project_items[0].clone(),
9095                ])
9096        });
9097        let multi_buffer_with_both_files_id = dirty_multi_buffer_with_both.item_id();
9098        workspace.update_in(cx, |workspace, window, cx| {
9099            workspace.add_item(
9100                pane.clone(),
9101                Box::new(dirty_regular_buffer.clone()),
9102                None,
9103                false,
9104                false,
9105                window,
9106                cx,
9107            );
9108            workspace.add_item(
9109                pane.clone(),
9110                Box::new(dirty_multi_buffer_with_both.clone()),
9111                None,
9112                false,
9113                false,
9114                window,
9115                cx,
9116            );
9117        });
9118
9119        pane.update_in(cx, |pane, window, cx| {
9120            pane.activate_item(1, true, true, window, cx);
9121            assert_eq!(
9122                pane.active_item().unwrap().item_id(),
9123                multi_buffer_with_both_files_id,
9124                "Should select the multi buffer in the pane"
9125            );
9126        });
9127        let _close_multi_buffer_task = pane
9128            .update_in(cx, |pane, window, cx| {
9129                pane.close_active_item(
9130                    &CloseActiveItem {
9131                        save_intent: None,
9132                        close_pinned: false,
9133                    },
9134                    window,
9135                    cx,
9136                )
9137            })
9138            .expect("should have active multi buffer to close");
9139        cx.background_executor.run_until_parked();
9140        assert!(
9141            cx.has_pending_prompt(),
9142            "With one dirty item from the multi buffer not being in the pane, a save prompt should be shown"
9143        );
9144    }
9145
9146    #[gpui::test]
9147    async fn test_no_save_prompt_when_dirty_multi_buffer_closed_with_all_of_its_dirty_items_present_in_the_pane(
9148        cx: &mut TestAppContext,
9149    ) {
9150        init_test(cx);
9151
9152        let fs = FakeFs::new(cx.background_executor.clone());
9153        let project = Project::test(fs, [], cx).await;
9154        let (workspace, cx) =
9155            cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
9156        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
9157
9158        let dirty_regular_buffer = cx.new(|cx| {
9159            TestItem::new(cx)
9160                .with_dirty(true)
9161                .with_label("1.txt")
9162                .with_project_items(&[dirty_project_item(1, "1.txt", cx)])
9163        });
9164        let dirty_regular_buffer_2 = cx.new(|cx| {
9165            TestItem::new(cx)
9166                .with_dirty(true)
9167                .with_label("2.txt")
9168                .with_project_items(&[dirty_project_item(2, "2.txt", cx)])
9169        });
9170        let clear_regular_buffer = cx.new(|cx| {
9171            TestItem::new(cx)
9172                .with_label("3.txt")
9173                .with_project_items(&[TestProjectItem::new(3, "3.txt", cx)])
9174        });
9175
9176        let dirty_multi_buffer = cx.new(|cx| {
9177            TestItem::new(cx)
9178                .with_dirty(true)
9179                .with_singleton(false)
9180                .with_label("Fake Project Search")
9181                .with_project_items(&[
9182                    dirty_regular_buffer.read(cx).project_items[0].clone(),
9183                    dirty_regular_buffer_2.read(cx).project_items[0].clone(),
9184                    clear_regular_buffer.read(cx).project_items[0].clone(),
9185                ])
9186        });
9187        workspace.update_in(cx, |workspace, window, cx| {
9188            workspace.add_item(
9189                pane.clone(),
9190                Box::new(dirty_regular_buffer.clone()),
9191                None,
9192                false,
9193                false,
9194                window,
9195                cx,
9196            );
9197            workspace.add_item(
9198                pane.clone(),
9199                Box::new(dirty_regular_buffer_2.clone()),
9200                None,
9201                false,
9202                false,
9203                window,
9204                cx,
9205            );
9206            workspace.add_item(
9207                pane.clone(),
9208                Box::new(dirty_multi_buffer.clone()),
9209                None,
9210                false,
9211                false,
9212                window,
9213                cx,
9214            );
9215        });
9216
9217        pane.update_in(cx, |pane, window, cx| {
9218            pane.activate_item(2, true, true, window, cx);
9219            assert_eq!(
9220                pane.active_item().unwrap().item_id(),
9221                dirty_multi_buffer.item_id(),
9222                "Should select the multi buffer in the pane"
9223            );
9224        });
9225        let close_multi_buffer_task = pane
9226            .update_in(cx, |pane, window, cx| {
9227                pane.close_active_item(
9228                    &CloseActiveItem {
9229                        save_intent: None,
9230                        close_pinned: false,
9231                    },
9232                    window,
9233                    cx,
9234                )
9235            })
9236            .expect("should have active multi buffer to close");
9237        cx.background_executor.run_until_parked();
9238        assert!(
9239            !cx.has_pending_prompt(),
9240            "All dirty items from the multi buffer are in the pane still, no save prompts should be shown"
9241        );
9242        close_multi_buffer_task
9243            .await
9244            .expect("Closing multi buffer failed");
9245        pane.update(cx, |pane, cx| {
9246            assert_eq!(dirty_regular_buffer.read(cx).save_count, 0);
9247            assert_eq!(dirty_multi_buffer.read(cx).save_count, 0);
9248            assert_eq!(dirty_regular_buffer_2.read(cx).save_count, 0);
9249            assert_eq!(
9250                pane.items()
9251                    .map(|item| item.item_id())
9252                    .sorted()
9253                    .collect::<Vec<_>>(),
9254                vec![
9255                    dirty_regular_buffer.item_id(),
9256                    dirty_regular_buffer_2.item_id(),
9257                ],
9258                "Should have no multi buffer left in the pane"
9259            );
9260            assert!(dirty_regular_buffer.read(cx).is_dirty);
9261            assert!(dirty_regular_buffer_2.read(cx).is_dirty);
9262        });
9263    }
9264
9265    #[gpui::test]
9266    async fn test_move_focused_panel_to_next_position(cx: &mut gpui::TestAppContext) {
9267        init_test(cx);
9268        let fs = FakeFs::new(cx.executor());
9269        let project = Project::test(fs, [], cx).await;
9270        let (workspace, cx) =
9271            cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
9272
9273        // Add a new panel to the right dock, opening the dock and setting the
9274        // focus to the new panel.
9275        let panel = workspace.update_in(cx, |workspace, window, cx| {
9276            let panel = cx.new(|cx| TestPanel::new(DockPosition::Right, cx));
9277            workspace.add_panel(panel.clone(), window, cx);
9278
9279            workspace
9280                .right_dock()
9281                .update(cx, |right_dock, cx| right_dock.set_open(true, window, cx));
9282
9283            workspace.toggle_panel_focus::<TestPanel>(window, cx);
9284
9285            panel
9286        });
9287
9288        // Dispatch the `MoveFocusedPanelToNextPosition` action, moving the
9289        // panel to the next valid position which, in this case, is the left
9290        // dock.
9291        cx.dispatch_action(MoveFocusedPanelToNextPosition);
9292        workspace.update(cx, |workspace, cx| {
9293            assert!(workspace.left_dock().read(cx).is_open());
9294            assert_eq!(panel.read(cx).position, DockPosition::Left);
9295        });
9296
9297        // Dispatch the `MoveFocusedPanelToNextPosition` action, moving the
9298        // panel to the next valid position which, in this case, is the bottom
9299        // dock.
9300        cx.dispatch_action(MoveFocusedPanelToNextPosition);
9301        workspace.update(cx, |workspace, cx| {
9302            assert!(workspace.bottom_dock().read(cx).is_open());
9303            assert_eq!(panel.read(cx).position, DockPosition::Bottom);
9304        });
9305
9306        // Dispatch the `MoveFocusedPanelToNextPosition` action again, this time
9307        // around moving the panel to its initial position, the right dock.
9308        cx.dispatch_action(MoveFocusedPanelToNextPosition);
9309        workspace.update(cx, |workspace, cx| {
9310            assert!(workspace.right_dock().read(cx).is_open());
9311            assert_eq!(panel.read(cx).position, DockPosition::Right);
9312        });
9313
9314        // Remove focus from the panel, ensuring that, if the panel is not
9315        // focused, the `MoveFocusedPanelToNextPosition` action does not update
9316        // the panel's position, so the panel is still in the right dock.
9317        workspace.update_in(cx, |workspace, window, cx| {
9318            workspace.toggle_panel_focus::<TestPanel>(window, cx);
9319        });
9320
9321        cx.dispatch_action(MoveFocusedPanelToNextPosition);
9322        workspace.update(cx, |workspace, cx| {
9323            assert!(workspace.right_dock().read(cx).is_open());
9324            assert_eq!(panel.read(cx).position, DockPosition::Right);
9325        });
9326    }
9327
9328    mod register_project_item_tests {
9329
9330        use super::*;
9331
9332        // View
9333        struct TestPngItemView {
9334            focus_handle: FocusHandle,
9335        }
9336        // Model
9337        struct TestPngItem {}
9338
9339        impl project::ProjectItem for TestPngItem {
9340            fn try_open(
9341                _project: &Entity<Project>,
9342                path: &ProjectPath,
9343                cx: &mut App,
9344            ) -> Option<Task<gpui::Result<Entity<Self>>>> {
9345                if path.path.extension().unwrap() == "png" {
9346                    Some(cx.spawn(async move |cx| cx.new(|_| TestPngItem {})))
9347                } else {
9348                    None
9349                }
9350            }
9351
9352            fn entry_id(&self, _: &App) -> Option<ProjectEntryId> {
9353                None
9354            }
9355
9356            fn project_path(&self, _: &App) -> Option<ProjectPath> {
9357                None
9358            }
9359
9360            fn is_dirty(&self) -> bool {
9361                false
9362            }
9363        }
9364
9365        impl Item for TestPngItemView {
9366            type Event = ();
9367            fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString {
9368                "".into()
9369            }
9370        }
9371        impl EventEmitter<()> for TestPngItemView {}
9372        impl Focusable for TestPngItemView {
9373            fn focus_handle(&self, _cx: &App) -> FocusHandle {
9374                self.focus_handle.clone()
9375            }
9376        }
9377
9378        impl Render for TestPngItemView {
9379            fn render(
9380                &mut self,
9381                _window: &mut Window,
9382                _cx: &mut Context<Self>,
9383            ) -> impl IntoElement {
9384                Empty
9385            }
9386        }
9387
9388        impl ProjectItem for TestPngItemView {
9389            type Item = TestPngItem;
9390
9391            fn for_project_item(
9392                _project: Entity<Project>,
9393                _pane: Option<&Pane>,
9394                _item: Entity<Self::Item>,
9395                _: &mut Window,
9396                cx: &mut Context<Self>,
9397            ) -> Self
9398            where
9399                Self: Sized,
9400            {
9401                Self {
9402                    focus_handle: cx.focus_handle(),
9403                }
9404            }
9405        }
9406
9407        // View
9408        struct TestIpynbItemView {
9409            focus_handle: FocusHandle,
9410        }
9411        // Model
9412        struct TestIpynbItem {}
9413
9414        impl project::ProjectItem for TestIpynbItem {
9415            fn try_open(
9416                _project: &Entity<Project>,
9417                path: &ProjectPath,
9418                cx: &mut App,
9419            ) -> Option<Task<gpui::Result<Entity<Self>>>> {
9420                if path.path.extension().unwrap() == "ipynb" {
9421                    Some(cx.spawn(async move |cx| cx.new(|_| TestIpynbItem {})))
9422                } else {
9423                    None
9424                }
9425            }
9426
9427            fn entry_id(&self, _: &App) -> Option<ProjectEntryId> {
9428                None
9429            }
9430
9431            fn project_path(&self, _: &App) -> Option<ProjectPath> {
9432                None
9433            }
9434
9435            fn is_dirty(&self) -> bool {
9436                false
9437            }
9438        }
9439
9440        impl Item for TestIpynbItemView {
9441            type Event = ();
9442            fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString {
9443                "".into()
9444            }
9445        }
9446        impl EventEmitter<()> for TestIpynbItemView {}
9447        impl Focusable for TestIpynbItemView {
9448            fn focus_handle(&self, _cx: &App) -> FocusHandle {
9449                self.focus_handle.clone()
9450            }
9451        }
9452
9453        impl Render for TestIpynbItemView {
9454            fn render(
9455                &mut self,
9456                _window: &mut Window,
9457                _cx: &mut Context<Self>,
9458            ) -> impl IntoElement {
9459                Empty
9460            }
9461        }
9462
9463        impl ProjectItem for TestIpynbItemView {
9464            type Item = TestIpynbItem;
9465
9466            fn for_project_item(
9467                _project: Entity<Project>,
9468                _pane: Option<&Pane>,
9469                _item: Entity<Self::Item>,
9470                _: &mut Window,
9471                cx: &mut Context<Self>,
9472            ) -> Self
9473            where
9474                Self: Sized,
9475            {
9476                Self {
9477                    focus_handle: cx.focus_handle(),
9478                }
9479            }
9480        }
9481
9482        struct TestAlternatePngItemView {
9483            focus_handle: FocusHandle,
9484        }
9485
9486        impl Item for TestAlternatePngItemView {
9487            type Event = ();
9488            fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString {
9489                "".into()
9490            }
9491        }
9492
9493        impl EventEmitter<()> for TestAlternatePngItemView {}
9494        impl Focusable for TestAlternatePngItemView {
9495            fn focus_handle(&self, _cx: &App) -> FocusHandle {
9496                self.focus_handle.clone()
9497            }
9498        }
9499
9500        impl Render for TestAlternatePngItemView {
9501            fn render(
9502                &mut self,
9503                _window: &mut Window,
9504                _cx: &mut Context<Self>,
9505            ) -> impl IntoElement {
9506                Empty
9507            }
9508        }
9509
9510        impl ProjectItem for TestAlternatePngItemView {
9511            type Item = TestPngItem;
9512
9513            fn for_project_item(
9514                _project: Entity<Project>,
9515                _pane: Option<&Pane>,
9516                _item: Entity<Self::Item>,
9517                _: &mut Window,
9518                cx: &mut Context<Self>,
9519            ) -> Self
9520            where
9521                Self: Sized,
9522            {
9523                Self {
9524                    focus_handle: cx.focus_handle(),
9525                }
9526            }
9527        }
9528
9529        #[gpui::test]
9530        async fn test_register_project_item(cx: &mut TestAppContext) {
9531            init_test(cx);
9532
9533            cx.update(|cx| {
9534                register_project_item::<TestPngItemView>(cx);
9535                register_project_item::<TestIpynbItemView>(cx);
9536            });
9537
9538            let fs = FakeFs::new(cx.executor());
9539            fs.insert_tree(
9540                "/root1",
9541                json!({
9542                    "one.png": "BINARYDATAHERE",
9543                    "two.ipynb": "{ totally a notebook }",
9544                    "three.txt": "editing text, sure why not?"
9545                }),
9546            )
9547            .await;
9548
9549            let project = Project::test(fs, ["root1".as_ref()], cx).await;
9550            let (workspace, cx) =
9551                cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
9552
9553            let worktree_id = project.update(cx, |project, cx| {
9554                project.worktrees(cx).next().unwrap().read(cx).id()
9555            });
9556
9557            let handle = workspace
9558                .update_in(cx, |workspace, window, cx| {
9559                    let project_path = (worktree_id, "one.png");
9560                    workspace.open_path(project_path, None, true, window, cx)
9561                })
9562                .await
9563                .unwrap();
9564
9565            // Now we can check if the handle we got back errored or not
9566            assert_eq!(
9567                handle.to_any().entity_type(),
9568                TypeId::of::<TestPngItemView>()
9569            );
9570
9571            let handle = workspace
9572                .update_in(cx, |workspace, window, cx| {
9573                    let project_path = (worktree_id, "two.ipynb");
9574                    workspace.open_path(project_path, None, true, window, cx)
9575                })
9576                .await
9577                .unwrap();
9578
9579            assert_eq!(
9580                handle.to_any().entity_type(),
9581                TypeId::of::<TestIpynbItemView>()
9582            );
9583
9584            let handle = workspace
9585                .update_in(cx, |workspace, window, cx| {
9586                    let project_path = (worktree_id, "three.txt");
9587                    workspace.open_path(project_path, None, true, window, cx)
9588                })
9589                .await;
9590            assert!(handle.is_err());
9591        }
9592
9593        #[gpui::test]
9594        async fn test_register_project_item_two_enter_one_leaves(cx: &mut TestAppContext) {
9595            init_test(cx);
9596
9597            cx.update(|cx| {
9598                register_project_item::<TestPngItemView>(cx);
9599                register_project_item::<TestAlternatePngItemView>(cx);
9600            });
9601
9602            let fs = FakeFs::new(cx.executor());
9603            fs.insert_tree(
9604                "/root1",
9605                json!({
9606                    "one.png": "BINARYDATAHERE",
9607                    "two.ipynb": "{ totally a notebook }",
9608                    "three.txt": "editing text, sure why not?"
9609                }),
9610            )
9611            .await;
9612            let project = Project::test(fs, ["root1".as_ref()], cx).await;
9613            let (workspace, cx) =
9614                cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
9615            let worktree_id = project.update(cx, |project, cx| {
9616                project.worktrees(cx).next().unwrap().read(cx).id()
9617            });
9618
9619            let handle = workspace
9620                .update_in(cx, |workspace, window, cx| {
9621                    let project_path = (worktree_id, "one.png");
9622                    workspace.open_path(project_path, None, true, window, cx)
9623                })
9624                .await
9625                .unwrap();
9626
9627            // This _must_ be the second item registered
9628            assert_eq!(
9629                handle.to_any().entity_type(),
9630                TypeId::of::<TestAlternatePngItemView>()
9631            );
9632
9633            let handle = workspace
9634                .update_in(cx, |workspace, window, cx| {
9635                    let project_path = (worktree_id, "three.txt");
9636                    workspace.open_path(project_path, None, true, window, cx)
9637                })
9638                .await;
9639            assert!(handle.is_err());
9640        }
9641    }
9642
9643    pub fn init_test(cx: &mut TestAppContext) {
9644        cx.update(|cx| {
9645            let settings_store = SettingsStore::test(cx);
9646            cx.set_global(settings_store);
9647            theme::init(theme::LoadThemes::JustBase, cx);
9648            language::init(cx);
9649            crate::init_settings(cx);
9650            Project::init_settings(cx);
9651        });
9652    }
9653
9654    fn dirty_project_item(id: u64, path: &str, cx: &mut App) -> Entity<TestProjectItem> {
9655        let item = TestProjectItem::new(id, path, cx);
9656        item.update(cx, |item, _| {
9657            item.is_dirty = true;
9658        });
9659        item
9660    }
9661}