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