workspace.rs

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