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<anyhow::Result<Box<dyn ItemHandle>>>>,
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
1373            for (toolchain, worktree_id, path) in toolchains {
1374                let toolchain_path = PathBuf::from(toolchain.path.clone().to_string());
1375                if !app_state.fs.is_file(toolchain_path.as_path()).await {
1376                    continue;
1377                }
1378
1379                project_handle
1380                    .update(cx, |this, cx| {
1381                        this.activate_toolchain(ProjectPath { worktree_id, path }, toolchain, cx)
1382                    })?
1383                    .await;
1384            }
1385            let window = if let Some(window) = requesting_window {
1386                cx.update_window(window.into(), |_, window, cx| {
1387                    window.replace_root(cx, |window, cx| {
1388                        Workspace::new(
1389                            Some(workspace_id),
1390                            project_handle.clone(),
1391                            app_state.clone(),
1392                            window,
1393                            cx,
1394                        )
1395                    });
1396                })?;
1397                window
1398            } else {
1399                let window_bounds_override = window_bounds_env_override();
1400
1401                let (window_bounds, display) = if let Some(bounds) = window_bounds_override {
1402                    (Some(WindowBounds::Windowed(bounds)), None)
1403                } else {
1404                    let restorable_bounds = serialized_workspace
1405                        .as_ref()
1406                        .and_then(|workspace| Some((workspace.display?, workspace.window_bounds?)))
1407                        .or_else(|| {
1408                            let (display, window_bounds) = DB.last_window().log_err()?;
1409                            Some((display?, window_bounds?))
1410                        });
1411
1412                    if let Some((serialized_display, serialized_status)) = restorable_bounds {
1413                        (Some(serialized_status.0), Some(serialized_display))
1414                    } else {
1415                        (None, None)
1416                    }
1417                };
1418
1419                // Use the serialized workspace to construct the new window
1420                let mut options = cx.update(|cx| (app_state.build_window_options)(display, cx))?;
1421                options.window_bounds = window_bounds;
1422                let centered_layout = serialized_workspace
1423                    .as_ref()
1424                    .map(|w| w.centered_layout)
1425                    .unwrap_or(false);
1426                cx.open_window(options, {
1427                    let app_state = app_state.clone();
1428                    let project_handle = project_handle.clone();
1429                    move |window, cx| {
1430                        cx.new(|cx| {
1431                            let mut workspace = Workspace::new(
1432                                Some(workspace_id),
1433                                project_handle,
1434                                app_state,
1435                                window,
1436                                cx,
1437                            );
1438                            workspace.centered_layout = centered_layout;
1439                            workspace
1440                        })
1441                    }
1442                })?
1443            };
1444
1445            notify_if_database_failed(window, cx);
1446            let opened_items = window
1447                .update(cx, |_workspace, window, cx| {
1448                    open_items(serialized_workspace, project_paths, window, cx)
1449                })?
1450                .await
1451                .unwrap_or_default();
1452
1453            window
1454                .update(cx, |workspace, window, cx| {
1455                    window.activate_window();
1456                    workspace.update_history(cx);
1457                })
1458                .log_err();
1459            Ok((window, opened_items))
1460        })
1461    }
1462
1463    pub fn weak_handle(&self) -> WeakEntity<Self> {
1464        self.weak_self.clone()
1465    }
1466
1467    pub fn left_dock(&self) -> &Entity<Dock> {
1468        &self.left_dock
1469    }
1470
1471    pub fn bottom_dock(&self) -> &Entity<Dock> {
1472        &self.bottom_dock
1473    }
1474
1475    pub fn bottom_dock_layout(&self) -> BottomDockLayout {
1476        self.bottom_dock_layout
1477    }
1478
1479    pub fn set_bottom_dock_layout(
1480        &mut self,
1481        layout: BottomDockLayout,
1482        window: &mut Window,
1483        cx: &mut Context<Self>,
1484    ) {
1485        let fs = self.project().read(cx).fs();
1486        settings::update_settings_file::<WorkspaceSettings>(fs.clone(), cx, move |content, _cx| {
1487            content.bottom_dock_layout = Some(layout);
1488        });
1489
1490        self.bottom_dock_layout = layout;
1491        cx.notify();
1492        self.serialize_workspace(window, cx);
1493    }
1494
1495    pub fn right_dock(&self) -> &Entity<Dock> {
1496        &self.right_dock
1497    }
1498
1499    pub fn all_docks(&self) -> [&Entity<Dock>; 3] {
1500        [&self.left_dock, &self.bottom_dock, &self.right_dock]
1501    }
1502
1503    pub fn dock_at_position(&self, position: DockPosition) -> &Entity<Dock> {
1504        match position {
1505            DockPosition::Left => &self.left_dock,
1506            DockPosition::Bottom => &self.bottom_dock,
1507            DockPosition::Right => &self.right_dock,
1508        }
1509    }
1510
1511    pub fn is_edited(&self) -> bool {
1512        self.window_edited
1513    }
1514
1515    pub fn add_panel<T: Panel>(
1516        &mut self,
1517        panel: Entity<T>,
1518        window: &mut Window,
1519        cx: &mut Context<Self>,
1520    ) {
1521        let focus_handle = panel.panel_focus_handle(cx);
1522        cx.on_focus_in(&focus_handle, window, Self::handle_panel_focused)
1523            .detach();
1524
1525        let dock_position = panel.position(window, cx);
1526        let dock = self.dock_at_position(dock_position);
1527
1528        dock.update(cx, |dock, cx| {
1529            dock.add_panel(panel, self.weak_self.clone(), window, cx)
1530        });
1531    }
1532
1533    pub fn status_bar(&self) -> &Entity<StatusBar> {
1534        &self.status_bar
1535    }
1536
1537    pub fn app_state(&self) -> &Arc<AppState> {
1538        &self.app_state
1539    }
1540
1541    pub fn user_store(&self) -> &Entity<UserStore> {
1542        &self.app_state.user_store
1543    }
1544
1545    pub fn project(&self) -> &Entity<Project> {
1546        &self.project
1547    }
1548
1549    pub fn recently_activated_items(&self, cx: &App) -> HashMap<EntityId, usize> {
1550        let mut history: HashMap<EntityId, usize> = HashMap::default();
1551
1552        for pane_handle in &self.panes {
1553            let pane = pane_handle.read(cx);
1554
1555            for entry in pane.activation_history() {
1556                history.insert(
1557                    entry.entity_id,
1558                    history
1559                        .get(&entry.entity_id)
1560                        .cloned()
1561                        .unwrap_or(0)
1562                        .max(entry.timestamp),
1563                );
1564            }
1565        }
1566
1567        history
1568    }
1569
1570    pub fn recent_navigation_history_iter(
1571        &self,
1572        cx: &App,
1573    ) -> impl Iterator<Item = (ProjectPath, Option<PathBuf>)> {
1574        let mut abs_paths_opened: HashMap<PathBuf, HashSet<ProjectPath>> = HashMap::default();
1575        let mut history: HashMap<ProjectPath, (Option<PathBuf>, usize)> = HashMap::default();
1576
1577        for pane in &self.panes {
1578            let pane = pane.read(cx);
1579
1580            pane.nav_history()
1581                .for_each_entry(cx, |entry, (project_path, fs_path)| {
1582                    if let Some(fs_path) = &fs_path {
1583                        abs_paths_opened
1584                            .entry(fs_path.clone())
1585                            .or_default()
1586                            .insert(project_path.clone());
1587                    }
1588                    let timestamp = entry.timestamp;
1589                    match history.entry(project_path) {
1590                        hash_map::Entry::Occupied(mut entry) => {
1591                            let (_, old_timestamp) = entry.get();
1592                            if &timestamp > old_timestamp {
1593                                entry.insert((fs_path, timestamp));
1594                            }
1595                        }
1596                        hash_map::Entry::Vacant(entry) => {
1597                            entry.insert((fs_path, timestamp));
1598                        }
1599                    }
1600                });
1601
1602            if let Some(item) = pane.active_item() {
1603                if let Some(project_path) = item.project_path(cx) {
1604                    let fs_path = self.project.read(cx).absolute_path(&project_path, cx);
1605
1606                    if let Some(fs_path) = &fs_path {
1607                        abs_paths_opened
1608                            .entry(fs_path.clone())
1609                            .or_default()
1610                            .insert(project_path.clone());
1611                    }
1612
1613                    history.insert(project_path, (fs_path, std::usize::MAX));
1614                }
1615            }
1616        }
1617
1618        history
1619            .into_iter()
1620            .sorted_by_key(|(_, (_, order))| *order)
1621            .map(|(project_path, (fs_path, _))| (project_path, fs_path))
1622            .rev()
1623            .filter(move |(history_path, abs_path)| {
1624                let latest_project_path_opened = abs_path
1625                    .as_ref()
1626                    .and_then(|abs_path| abs_paths_opened.get(abs_path))
1627                    .and_then(|project_paths| {
1628                        project_paths
1629                            .iter()
1630                            .max_by(|b1, b2| b1.worktree_id.cmp(&b2.worktree_id))
1631                    });
1632
1633                match latest_project_path_opened {
1634                    Some(latest_project_path_opened) => latest_project_path_opened == history_path,
1635                    None => true,
1636                }
1637            })
1638    }
1639
1640    pub fn recent_navigation_history(
1641        &self,
1642        limit: Option<usize>,
1643        cx: &App,
1644    ) -> Vec<(ProjectPath, Option<PathBuf>)> {
1645        self.recent_navigation_history_iter(cx)
1646            .take(limit.unwrap_or(usize::MAX))
1647            .collect()
1648    }
1649
1650    fn navigate_history(
1651        &mut self,
1652        pane: WeakEntity<Pane>,
1653        mode: NavigationMode,
1654        window: &mut Window,
1655        cx: &mut Context<Workspace>,
1656    ) -> Task<Result<()>> {
1657        let to_load = if let Some(pane) = pane.upgrade() {
1658            pane.update(cx, |pane, cx| {
1659                window.focus(&pane.focus_handle(cx));
1660                loop {
1661                    // Retrieve the weak item handle from the history.
1662                    let entry = pane.nav_history_mut().pop(mode, cx)?;
1663
1664                    // If the item is still present in this pane, then activate it.
1665                    if let Some(index) = entry
1666                        .item
1667                        .upgrade()
1668                        .and_then(|v| pane.index_for_item(v.as_ref()))
1669                    {
1670                        let prev_active_item_index = pane.active_item_index();
1671                        pane.nav_history_mut().set_mode(mode);
1672                        pane.activate_item(index, true, true, window, cx);
1673                        pane.nav_history_mut().set_mode(NavigationMode::Normal);
1674
1675                        let mut navigated = prev_active_item_index != pane.active_item_index();
1676                        if let Some(data) = entry.data {
1677                            navigated |= pane.active_item()?.navigate(data, window, cx);
1678                        }
1679
1680                        if navigated {
1681                            break None;
1682                        }
1683                    } else {
1684                        // If the item is no longer present in this pane, then retrieve its
1685                        // path info in order to reopen it.
1686                        break pane
1687                            .nav_history()
1688                            .path_for_item(entry.item.id())
1689                            .map(|(project_path, abs_path)| (project_path, abs_path, entry));
1690                    }
1691                }
1692            })
1693        } else {
1694            None
1695        };
1696
1697        if let Some((project_path, abs_path, entry)) = to_load {
1698            // If the item was no longer present, then load it again from its previous path, first try the local path
1699            let open_by_project_path = self.load_path(project_path.clone(), window, cx);
1700
1701            cx.spawn_in(window, async move  |workspace, cx| {
1702                let open_by_project_path = open_by_project_path.await;
1703                let mut navigated = false;
1704                match open_by_project_path
1705                    .with_context(|| format!("Navigating to {project_path:?}"))
1706                {
1707                    Ok((project_entry_id, build_item)) => {
1708                        let prev_active_item_id = pane.update(cx, |pane, _| {
1709                            pane.nav_history_mut().set_mode(mode);
1710                            pane.active_item().map(|p| p.item_id())
1711                        })?;
1712
1713                        pane.update_in(cx, |pane, window, cx| {
1714                            let item = pane.open_item(
1715                                project_entry_id,
1716                                project_path,
1717                                true,
1718                                entry.is_preview,
1719                                true,
1720                                None,
1721                                window, cx,
1722                                build_item,
1723                            );
1724                            navigated |= Some(item.item_id()) != prev_active_item_id;
1725                            pane.nav_history_mut().set_mode(NavigationMode::Normal);
1726                            if let Some(data) = entry.data {
1727                                navigated |= item.navigate(data, window, cx);
1728                            }
1729                        })?;
1730                    }
1731                    Err(open_by_project_path_e) => {
1732                        // Fall back to opening by abs path, in case an external file was opened and closed,
1733                        // and its worktree is now dropped
1734                        if let Some(abs_path) = abs_path {
1735                            let prev_active_item_id = pane.update(cx, |pane, _| {
1736                                pane.nav_history_mut().set_mode(mode);
1737                                pane.active_item().map(|p| p.item_id())
1738                            })?;
1739                            let open_by_abs_path = workspace.update_in(cx, |workspace, window, cx| {
1740                                workspace.open_abs_path(abs_path.clone(), OpenOptions { visible: Some(OpenVisible::None), ..Default::default() }, window, cx)
1741                            })?;
1742                            match open_by_abs_path
1743                                .await
1744                                .with_context(|| format!("Navigating to {abs_path:?}"))
1745                            {
1746                                Ok(item) => {
1747                                    pane.update_in(cx, |pane, window, cx| {
1748                                        navigated |= Some(item.item_id()) != prev_active_item_id;
1749                                        pane.nav_history_mut().set_mode(NavigationMode::Normal);
1750                                        if let Some(data) = entry.data {
1751                                            navigated |= item.navigate(data, window, cx);
1752                                        }
1753                                    })?;
1754                                }
1755                                Err(open_by_abs_path_e) => {
1756                                    log::error!("Failed to navigate history: {open_by_project_path_e:#} and {open_by_abs_path_e:#}");
1757                                }
1758                            }
1759                        }
1760                    }
1761                }
1762
1763                if !navigated {
1764                    workspace
1765                        .update_in(cx, |workspace, window, cx| {
1766                            Self::navigate_history(workspace, pane, mode, window, cx)
1767                        })?
1768                        .await?;
1769                }
1770
1771                Ok(())
1772            })
1773        } else {
1774            Task::ready(Ok(()))
1775        }
1776    }
1777
1778    pub fn go_back(
1779        &mut self,
1780        pane: WeakEntity<Pane>,
1781        window: &mut Window,
1782        cx: &mut Context<Workspace>,
1783    ) -> Task<Result<()>> {
1784        self.navigate_history(pane, NavigationMode::GoingBack, window, cx)
1785    }
1786
1787    pub fn go_forward(
1788        &mut self,
1789        pane: WeakEntity<Pane>,
1790        window: &mut Window,
1791        cx: &mut Context<Workspace>,
1792    ) -> Task<Result<()>> {
1793        self.navigate_history(pane, NavigationMode::GoingForward, window, cx)
1794    }
1795
1796    pub fn reopen_closed_item(
1797        &mut self,
1798        window: &mut Window,
1799        cx: &mut Context<Workspace>,
1800    ) -> Task<Result<()>> {
1801        self.navigate_history(
1802            self.active_pane().downgrade(),
1803            NavigationMode::ReopeningClosedItem,
1804            window,
1805            cx,
1806        )
1807    }
1808
1809    pub fn client(&self) -> &Arc<Client> {
1810        &self.app_state.client
1811    }
1812
1813    pub fn set_titlebar_item(&mut self, item: AnyView, _: &mut Window, cx: &mut Context<Self>) {
1814        self.titlebar_item = Some(item);
1815        cx.notify();
1816    }
1817
1818    pub fn set_prompt_for_new_path(&mut self, prompt: PromptForNewPath) {
1819        self.on_prompt_for_new_path = Some(prompt)
1820    }
1821
1822    pub fn set_prompt_for_open_path(&mut self, prompt: PromptForOpenPath) {
1823        self.on_prompt_for_open_path = Some(prompt)
1824    }
1825
1826    pub fn set_terminal_provider(&mut self, provider: impl TerminalProvider + 'static) {
1827        self.terminal_provider = Some(Box::new(provider));
1828    }
1829
1830    pub fn set_debugger_provider(&mut self, provider: impl DebuggerProvider + 'static) {
1831        self.debugger_provider = Some(Box::new(provider));
1832    }
1833
1834    pub fn serialized_ssh_project(&self) -> Option<SerializedSshProject> {
1835        self.serialized_ssh_project.clone()
1836    }
1837
1838    pub fn set_serialized_ssh_project(&mut self, serialized_ssh_project: SerializedSshProject) {
1839        self.serialized_ssh_project = Some(serialized_ssh_project);
1840    }
1841
1842    pub fn prompt_for_open_path(
1843        &mut self,
1844        path_prompt_options: PathPromptOptions,
1845        lister: DirectoryLister,
1846        window: &mut Window,
1847        cx: &mut Context<Self>,
1848    ) -> oneshot::Receiver<Option<Vec<PathBuf>>> {
1849        if !lister.is_local(cx) || !WorkspaceSettings::get_global(cx).use_system_path_prompts {
1850            let prompt = self.on_prompt_for_open_path.take().unwrap();
1851            let rx = prompt(self, lister, window, cx);
1852            self.on_prompt_for_open_path = Some(prompt);
1853            rx
1854        } else {
1855            let (tx, rx) = oneshot::channel();
1856            let abs_path = cx.prompt_for_paths(path_prompt_options);
1857
1858            cx.spawn_in(window, async move |this, cx| {
1859                let Ok(result) = abs_path.await else {
1860                    return Ok(());
1861                };
1862
1863                match result {
1864                    Ok(result) => {
1865                        tx.send(result).log_err();
1866                    }
1867                    Err(err) => {
1868                        let rx = this.update_in(cx, |this, window, cx| {
1869                            this.show_portal_error(err.to_string(), cx);
1870                            let prompt = this.on_prompt_for_open_path.take().unwrap();
1871                            let rx = prompt(this, lister, window, cx);
1872                            this.on_prompt_for_open_path = Some(prompt);
1873                            rx
1874                        })?;
1875                        if let Ok(path) = rx.await {
1876                            tx.send(path).log_err();
1877                        }
1878                    }
1879                };
1880                anyhow::Ok(())
1881            })
1882            .detach();
1883
1884            rx
1885        }
1886    }
1887
1888    pub fn prompt_for_new_path(
1889        &mut self,
1890        window: &mut Window,
1891        cx: &mut Context<Self>,
1892    ) -> oneshot::Receiver<Option<ProjectPath>> {
1893        if (self.project.read(cx).is_via_collab() || self.project.read(cx).is_via_ssh())
1894            || !WorkspaceSettings::get_global(cx).use_system_path_prompts
1895        {
1896            let prompt = self.on_prompt_for_new_path.take().unwrap();
1897            let rx = prompt(self, window, cx);
1898            self.on_prompt_for_new_path = Some(prompt);
1899            return rx;
1900        }
1901
1902        let (tx, rx) = oneshot::channel();
1903        cx.spawn_in(window, async move |this, cx| {
1904            let abs_path = this.update(cx, |this, cx| {
1905                let mut relative_to = this
1906                    .most_recent_active_path(cx)
1907                    .and_then(|p| p.parent().map(|p| p.to_path_buf()));
1908                if relative_to.is_none() {
1909                    let project = this.project.read(cx);
1910                    relative_to = project
1911                        .visible_worktrees(cx)
1912                        .filter_map(|worktree| {
1913                            Some(worktree.read(cx).as_local()?.abs_path().to_path_buf())
1914                        })
1915                        .next()
1916                };
1917
1918                cx.prompt_for_new_path(&relative_to.unwrap_or_else(|| PathBuf::from("")))
1919            })?;
1920            let abs_path = match abs_path.await? {
1921                Ok(path) => path,
1922                Err(err) => {
1923                    let rx = this.update_in(cx, |this, window, cx| {
1924                        this.show_portal_error(err.to_string(), cx);
1925
1926                        let prompt = this.on_prompt_for_new_path.take().unwrap();
1927                        let rx = prompt(this, window, cx);
1928                        this.on_prompt_for_new_path = Some(prompt);
1929                        rx
1930                    })?;
1931                    if let Ok(path) = rx.await {
1932                        tx.send(path).log_err();
1933                    }
1934                    return anyhow::Ok(());
1935                }
1936            };
1937
1938            let project_path = abs_path.and_then(|abs_path| {
1939                this.update(cx, |this, cx| {
1940                    this.project.update(cx, |project, cx| {
1941                        project.find_or_create_worktree(abs_path, true, cx)
1942                    })
1943                })
1944                .ok()
1945            });
1946
1947            if let Some(project_path) = project_path {
1948                let (worktree, path) = project_path.await?;
1949                let worktree_id = worktree.read_with(cx, |worktree, _| worktree.id())?;
1950                tx.send(Some(ProjectPath {
1951                    worktree_id,
1952                    path: path.into(),
1953                }))
1954                .ok();
1955            } else {
1956                tx.send(None).ok();
1957            }
1958            anyhow::Ok(())
1959        })
1960        .detach_and_log_err(cx);
1961
1962        rx
1963    }
1964
1965    pub fn titlebar_item(&self) -> Option<AnyView> {
1966        self.titlebar_item.clone()
1967    }
1968
1969    /// Call the given callback with a workspace whose project is local.
1970    ///
1971    /// If the given workspace has a local project, then it will be passed
1972    /// to the callback. Otherwise, a new empty window will be created.
1973    pub fn with_local_workspace<T, F>(
1974        &mut self,
1975        window: &mut Window,
1976        cx: &mut Context<Self>,
1977        callback: F,
1978    ) -> Task<Result<T>>
1979    where
1980        T: 'static,
1981        F: 'static + FnOnce(&mut Workspace, &mut Window, &mut Context<Workspace>) -> T,
1982    {
1983        if self.project.read(cx).is_local() {
1984            Task::ready(Ok(callback(self, window, cx)))
1985        } else {
1986            let env = self.project.read(cx).cli_environment(cx);
1987            let task = Self::new_local(Vec::new(), self.app_state.clone(), None, env, cx);
1988            cx.spawn_in(window, async move |_vh, cx| {
1989                let (workspace, _) = task.await?;
1990                workspace.update(cx, callback)
1991            })
1992        }
1993    }
1994
1995    pub fn worktrees<'a>(&self, cx: &'a App) -> impl 'a + Iterator<Item = Entity<Worktree>> {
1996        self.project.read(cx).worktrees(cx)
1997    }
1998
1999    pub fn visible_worktrees<'a>(
2000        &self,
2001        cx: &'a App,
2002    ) -> impl 'a + Iterator<Item = Entity<Worktree>> {
2003        self.project.read(cx).visible_worktrees(cx)
2004    }
2005
2006    #[cfg(any(test, feature = "test-support"))]
2007    pub fn worktree_scans_complete(&self, cx: &App) -> impl Future<Output = ()> + 'static + use<> {
2008        let futures = self
2009            .worktrees(cx)
2010            .filter_map(|worktree| worktree.read(cx).as_local())
2011            .map(|worktree| worktree.scan_complete())
2012            .collect::<Vec<_>>();
2013        async move {
2014            for future in futures {
2015                future.await;
2016            }
2017        }
2018    }
2019
2020    pub fn close_global(_: &CloseWindow, cx: &mut App) {
2021        cx.defer(|cx| {
2022            cx.windows().iter().find(|window| {
2023                window
2024                    .update(cx, |_, window, _| {
2025                        if window.is_window_active() {
2026                            //This can only get called when the window's project connection has been lost
2027                            //so we don't need to prompt the user for anything and instead just close the window
2028                            window.remove_window();
2029                            true
2030                        } else {
2031                            false
2032                        }
2033                    })
2034                    .unwrap_or(false)
2035            });
2036        });
2037    }
2038
2039    pub fn close_window(&mut self, _: &CloseWindow, window: &mut Window, cx: &mut Context<Self>) {
2040        let prepare = self.prepare_to_close(CloseIntent::CloseWindow, window, cx);
2041        cx.spawn_in(window, async move |_, cx| {
2042            if prepare.await? {
2043                cx.update(|window, _cx| window.remove_window())?;
2044            }
2045            anyhow::Ok(())
2046        })
2047        .detach_and_log_err(cx)
2048    }
2049
2050    pub fn move_focused_panel_to_next_position(
2051        &mut self,
2052        _: &MoveFocusedPanelToNextPosition,
2053        window: &mut Window,
2054        cx: &mut Context<Self>,
2055    ) {
2056        let docks = self.all_docks();
2057        let active_dock = docks
2058            .into_iter()
2059            .find(|dock| dock.focus_handle(cx).contains_focused(window, cx));
2060
2061        if let Some(dock) = active_dock {
2062            dock.update(cx, |dock, cx| {
2063                let active_panel = dock
2064                    .active_panel()
2065                    .filter(|panel| panel.panel_focus_handle(cx).contains_focused(window, cx));
2066
2067                if let Some(panel) = active_panel {
2068                    panel.move_to_next_position(window, cx);
2069                }
2070            })
2071        }
2072    }
2073
2074    pub fn prepare_to_close(
2075        &mut self,
2076        close_intent: CloseIntent,
2077        window: &mut Window,
2078        cx: &mut Context<Self>,
2079    ) -> Task<Result<bool>> {
2080        let active_call = self.active_call().cloned();
2081
2082        // On Linux and Windows, closing the last window should restore the last workspace.
2083        let save_last_workspace = cfg!(not(target_os = "macos"))
2084            && close_intent != CloseIntent::ReplaceWindow
2085            && cx.windows().len() == 1;
2086
2087        cx.spawn_in(window, async move |this, cx| {
2088            let workspace_count = cx.update(|_window, cx| {
2089                cx.windows()
2090                    .iter()
2091                    .filter(|window| window.downcast::<Workspace>().is_some())
2092                    .count()
2093            })?;
2094
2095            if let Some(active_call) = active_call {
2096                if close_intent != CloseIntent::Quit
2097                    && workspace_count == 1
2098                    && active_call.read_with(cx, |call, _| call.room().is_some())?
2099                {
2100                    let answer = cx.update(|window, cx| {
2101                        window.prompt(
2102                            PromptLevel::Warning,
2103                            "Do you want to leave the current call?",
2104                            None,
2105                            &["Close window and hang up", "Cancel"],
2106                            cx,
2107                        )
2108                    })?;
2109
2110                    if answer.await.log_err() == Some(1) {
2111                        return anyhow::Ok(false);
2112                    } else {
2113                        active_call
2114                            .update(cx, |call, cx| call.hang_up(cx))?
2115                            .await
2116                            .log_err();
2117                    }
2118                }
2119            }
2120
2121            let save_result = this
2122                .update_in(cx, |this, window, cx| {
2123                    this.save_all_internal(SaveIntent::Close, window, cx)
2124                })?
2125                .await;
2126
2127            // If we're not quitting, but closing, we remove the workspace from
2128            // the current session.
2129            if close_intent != CloseIntent::Quit
2130                && !save_last_workspace
2131                && save_result.as_ref().map_or(false, |&res| res)
2132            {
2133                this.update_in(cx, |this, window, cx| this.remove_from_session(window, cx))?
2134                    .await;
2135            }
2136
2137            save_result
2138        })
2139    }
2140
2141    fn save_all(&mut self, action: &SaveAll, window: &mut Window, cx: &mut Context<Self>) {
2142        self.save_all_internal(
2143            action.save_intent.unwrap_or(SaveIntent::SaveAll),
2144            window,
2145            cx,
2146        )
2147        .detach_and_log_err(cx);
2148    }
2149
2150    fn send_keystrokes(
2151        &mut self,
2152        action: &SendKeystrokes,
2153        window: &mut Window,
2154        cx: &mut Context<Self>,
2155    ) {
2156        let mut state = self.dispatching_keystrokes.borrow_mut();
2157        if !state.0.insert(action.0.clone()) {
2158            cx.propagate();
2159            return;
2160        }
2161        let mut keystrokes: Vec<Keystroke> = action
2162            .0
2163            .split(' ')
2164            .flat_map(|k| Keystroke::parse(k).log_err())
2165            .collect();
2166        keystrokes.reverse();
2167
2168        state.1.append(&mut keystrokes);
2169        drop(state);
2170
2171        let keystrokes = self.dispatching_keystrokes.clone();
2172        window
2173            .spawn(cx, async move |cx| {
2174                // limit to 100 keystrokes to avoid infinite recursion.
2175                for _ in 0..100 {
2176                    let Some(keystroke) = keystrokes.borrow_mut().1.pop() else {
2177                        keystrokes.borrow_mut().0.clear();
2178                        return Ok(());
2179                    };
2180                    cx.update(|window, cx| {
2181                        let focused = window.focused(cx);
2182                        window.dispatch_keystroke(keystroke.clone(), cx);
2183                        if window.focused(cx) != focused {
2184                            // dispatch_keystroke may cause the focus to change.
2185                            // draw's side effect is to schedule the FocusChanged events in the current flush effect cycle
2186                            // And we need that to happen before the next keystroke to keep vim mode happy...
2187                            // (Note that the tests always do this implicitly, so you must manually test with something like:
2188                            //   "bindings": { "g z": ["workspace::SendKeystrokes", ": j <enter> u"]}
2189                            // )
2190                            window.draw(cx);
2191                        }
2192                    })?;
2193                }
2194
2195                *keystrokes.borrow_mut() = Default::default();
2196                anyhow::bail!("over 100 keystrokes passed to send_keystrokes");
2197            })
2198            .detach_and_log_err(cx);
2199    }
2200
2201    fn save_all_internal(
2202        &mut self,
2203        mut save_intent: SaveIntent,
2204        window: &mut Window,
2205        cx: &mut Context<Self>,
2206    ) -> Task<Result<bool>> {
2207        if self.project.read(cx).is_disconnected(cx) {
2208            return Task::ready(Ok(true));
2209        }
2210        let dirty_items = self
2211            .panes
2212            .iter()
2213            .flat_map(|pane| {
2214                pane.read(cx).items().filter_map(|item| {
2215                    if item.is_dirty(cx) {
2216                        item.tab_content_text(0, cx);
2217                        Some((pane.downgrade(), item.boxed_clone()))
2218                    } else {
2219                        None
2220                    }
2221                })
2222            })
2223            .collect::<Vec<_>>();
2224
2225        let project = self.project.clone();
2226        cx.spawn_in(window, async move |workspace, cx| {
2227            let dirty_items = if save_intent == SaveIntent::Close && !dirty_items.is_empty() {
2228                let (serialize_tasks, remaining_dirty_items) =
2229                    workspace.update_in(cx, |workspace, window, cx| {
2230                        let mut remaining_dirty_items = Vec::new();
2231                        let mut serialize_tasks = Vec::new();
2232                        for (pane, item) in dirty_items {
2233                            if let Some(task) = item
2234                                .to_serializable_item_handle(cx)
2235                                .and_then(|handle| handle.serialize(workspace, true, window, cx))
2236                            {
2237                                serialize_tasks.push(task);
2238                            } else {
2239                                remaining_dirty_items.push((pane, item));
2240                            }
2241                        }
2242                        (serialize_tasks, remaining_dirty_items)
2243                    })?;
2244
2245                futures::future::try_join_all(serialize_tasks).await?;
2246
2247                if remaining_dirty_items.len() > 1 {
2248                    let answer = workspace.update_in(cx, |_, window, cx| {
2249                        let detail = Pane::file_names_for_prompt(
2250                            &mut remaining_dirty_items.iter().map(|(_, handle)| handle),
2251                            cx,
2252                        );
2253                        window.prompt(
2254                            PromptLevel::Warning,
2255                            &"Do you want to save all changes in the following files?",
2256                            Some(&detail),
2257                            &["Save all", "Discard all", "Cancel"],
2258                            cx,
2259                        )
2260                    })?;
2261                    match answer.await.log_err() {
2262                        Some(0) => save_intent = SaveIntent::SaveAll,
2263                        Some(1) => save_intent = SaveIntent::Skip,
2264                        Some(2) => return Ok(false),
2265                        _ => {}
2266                    }
2267                }
2268
2269                remaining_dirty_items
2270            } else {
2271                dirty_items
2272            };
2273
2274            for (pane, item) in dirty_items {
2275                let (singleton, project_entry_ids) =
2276                    cx.update(|_, cx| (item.is_singleton(cx), item.project_entry_ids(cx)))?;
2277                if singleton || !project_entry_ids.is_empty() {
2278                    if !Pane::save_item(project.clone(), &pane, &*item, save_intent, cx).await? {
2279                        return Ok(false);
2280                    }
2281                }
2282            }
2283            Ok(true)
2284        })
2285    }
2286
2287    pub fn open_workspace_for_paths(
2288        &mut self,
2289        replace_current_window: bool,
2290        paths: Vec<PathBuf>,
2291        window: &mut Window,
2292        cx: &mut Context<Self>,
2293    ) -> Task<Result<()>> {
2294        let window_handle = window.window_handle().downcast::<Self>();
2295        let is_remote = self.project.read(cx).is_via_collab();
2296        let has_worktree = self.project.read(cx).worktrees(cx).next().is_some();
2297        let has_dirty_items = self.items(cx).any(|item| item.is_dirty(cx));
2298
2299        let window_to_replace = if replace_current_window {
2300            window_handle
2301        } else if is_remote || has_worktree || has_dirty_items {
2302            None
2303        } else {
2304            window_handle
2305        };
2306        let app_state = self.app_state.clone();
2307
2308        cx.spawn(async move |_, cx| {
2309            cx.update(|cx| {
2310                open_paths(
2311                    &paths,
2312                    app_state,
2313                    OpenOptions {
2314                        replace_window: window_to_replace,
2315                        ..Default::default()
2316                    },
2317                    cx,
2318                )
2319            })?
2320            .await?;
2321            Ok(())
2322        })
2323    }
2324
2325    #[allow(clippy::type_complexity)]
2326    pub fn open_paths(
2327        &mut self,
2328        mut abs_paths: Vec<PathBuf>,
2329        options: OpenOptions,
2330        pane: Option<WeakEntity<Pane>>,
2331        window: &mut Window,
2332        cx: &mut Context<Self>,
2333    ) -> Task<Vec<Option<anyhow::Result<Box<dyn ItemHandle>>>>> {
2334        log::info!("open paths {abs_paths:?}");
2335
2336        let fs = self.app_state.fs.clone();
2337
2338        // Sort the paths to ensure we add worktrees for parents before their children.
2339        abs_paths.sort_unstable();
2340        cx.spawn_in(window, async move |this, cx| {
2341            let mut tasks = Vec::with_capacity(abs_paths.len());
2342
2343            for abs_path in &abs_paths {
2344                let visible = match options.visible.as_ref().unwrap_or(&OpenVisible::None) {
2345                    OpenVisible::All => Some(true),
2346                    OpenVisible::None => Some(false),
2347                    OpenVisible::OnlyFiles => match fs.metadata(abs_path).await.log_err() {
2348                        Some(Some(metadata)) => Some(!metadata.is_dir),
2349                        Some(None) => Some(true),
2350                        None => None,
2351                    },
2352                    OpenVisible::OnlyDirectories => match fs.metadata(abs_path).await.log_err() {
2353                        Some(Some(metadata)) => Some(metadata.is_dir),
2354                        Some(None) => Some(false),
2355                        None => None,
2356                    },
2357                };
2358                let project_path = match visible {
2359                    Some(visible) => match this
2360                        .update(cx, |this, cx| {
2361                            Workspace::project_path_for_path(
2362                                this.project.clone(),
2363                                abs_path,
2364                                visible,
2365                                cx,
2366                            )
2367                        })
2368                        .log_err()
2369                    {
2370                        Some(project_path) => project_path.await.log_err(),
2371                        None => None,
2372                    },
2373                    None => None,
2374                };
2375
2376                let this = this.clone();
2377                let abs_path: Arc<Path> = SanitizedPath::from(abs_path.clone()).into();
2378                let fs = fs.clone();
2379                let pane = pane.clone();
2380                let task = cx.spawn(async move |cx| {
2381                    let (worktree, project_path) = project_path?;
2382                    if fs.is_dir(&abs_path).await {
2383                        this.update(cx, |workspace, cx| {
2384                            let worktree = worktree.read(cx);
2385                            let worktree_abs_path = worktree.abs_path();
2386                            let entry_id = if abs_path.as_ref() == worktree_abs_path.as_ref() {
2387                                worktree.root_entry()
2388                            } else {
2389                                abs_path
2390                                    .strip_prefix(worktree_abs_path.as_ref())
2391                                    .ok()
2392                                    .and_then(|relative_path| {
2393                                        worktree.entry_for_path(relative_path)
2394                                    })
2395                            }
2396                            .map(|entry| entry.id);
2397                            if let Some(entry_id) = entry_id {
2398                                workspace.project.update(cx, |_, cx| {
2399                                    cx.emit(project::Event::ActiveEntryChanged(Some(entry_id)));
2400                                })
2401                            }
2402                        })
2403                        .log_err()?;
2404                        None
2405                    } else {
2406                        Some(
2407                            this.update_in(cx, |this, window, cx| {
2408                                this.open_path(
2409                                    project_path,
2410                                    pane,
2411                                    options.focus.unwrap_or(true),
2412                                    window,
2413                                    cx,
2414                                )
2415                            })
2416                            .log_err()?
2417                            .await,
2418                        )
2419                    }
2420                });
2421                tasks.push(task);
2422            }
2423
2424            futures::future::join_all(tasks).await
2425        })
2426    }
2427
2428    pub fn open_resolved_path(
2429        &mut self,
2430        path: ResolvedPath,
2431        window: &mut Window,
2432        cx: &mut Context<Self>,
2433    ) -> Task<anyhow::Result<Box<dyn ItemHandle>>> {
2434        match path {
2435            ResolvedPath::ProjectPath { project_path, .. } => {
2436                self.open_path(project_path, None, true, window, cx)
2437            }
2438            ResolvedPath::AbsPath { path, .. } => self.open_abs_path(
2439                path,
2440                OpenOptions {
2441                    visible: Some(OpenVisible::None),
2442                    ..Default::default()
2443                },
2444                window,
2445                cx,
2446            ),
2447        }
2448    }
2449
2450    pub fn absolute_path_of_worktree(
2451        &self,
2452        worktree_id: WorktreeId,
2453        cx: &mut Context<Self>,
2454    ) -> Option<PathBuf> {
2455        self.project
2456            .read(cx)
2457            .worktree_for_id(worktree_id, cx)
2458            // TODO: use `abs_path` or `root_dir`
2459            .map(|wt| wt.read(cx).abs_path().as_ref().to_path_buf())
2460    }
2461
2462    fn add_folder_to_project(
2463        &mut self,
2464        _: &AddFolderToProject,
2465        window: &mut Window,
2466        cx: &mut Context<Self>,
2467    ) {
2468        let project = self.project.read(cx);
2469        if project.is_via_collab() {
2470            self.show_error(
2471                &anyhow!("You cannot add folders to someone else's project"),
2472                cx,
2473            );
2474            return;
2475        }
2476        let paths = self.prompt_for_open_path(
2477            PathPromptOptions {
2478                files: false,
2479                directories: true,
2480                multiple: true,
2481            },
2482            DirectoryLister::Project(self.project.clone()),
2483            window,
2484            cx,
2485        );
2486        cx.spawn_in(window, async move |this, cx| {
2487            if let Some(paths) = paths.await.log_err().flatten() {
2488                let results = this
2489                    .update_in(cx, |this, window, cx| {
2490                        this.open_paths(
2491                            paths,
2492                            OpenOptions {
2493                                visible: Some(OpenVisible::All),
2494                                ..Default::default()
2495                            },
2496                            None,
2497                            window,
2498                            cx,
2499                        )
2500                    })?
2501                    .await;
2502                for result in results.into_iter().flatten() {
2503                    result.log_err();
2504                }
2505            }
2506            anyhow::Ok(())
2507        })
2508        .detach_and_log_err(cx);
2509    }
2510
2511    pub fn project_path_for_path(
2512        project: Entity<Project>,
2513        abs_path: &Path,
2514        visible: bool,
2515        cx: &mut App,
2516    ) -> Task<Result<(Entity<Worktree>, ProjectPath)>> {
2517        let entry = project.update(cx, |project, cx| {
2518            project.find_or_create_worktree(abs_path, visible, cx)
2519        });
2520        cx.spawn(async move |cx| {
2521            let (worktree, path) = entry.await?;
2522            let worktree_id = worktree.update(cx, |t, _| t.id())?;
2523            Ok((
2524                worktree,
2525                ProjectPath {
2526                    worktree_id,
2527                    path: path.into(),
2528                },
2529            ))
2530        })
2531    }
2532
2533    pub fn items<'a>(&'a self, cx: &'a App) -> impl 'a + Iterator<Item = &'a Box<dyn ItemHandle>> {
2534        self.panes.iter().flat_map(|pane| pane.read(cx).items())
2535    }
2536
2537    pub fn item_of_type<T: Item>(&self, cx: &App) -> Option<Entity<T>> {
2538        self.items_of_type(cx).max_by_key(|item| item.item_id())
2539    }
2540
2541    pub fn items_of_type<'a, T: Item>(
2542        &'a self,
2543        cx: &'a App,
2544    ) -> impl 'a + Iterator<Item = Entity<T>> {
2545        self.panes
2546            .iter()
2547            .flat_map(|pane| pane.read(cx).items_of_type())
2548    }
2549
2550    pub fn active_item(&self, cx: &App) -> Option<Box<dyn ItemHandle>> {
2551        self.active_pane().read(cx).active_item()
2552    }
2553
2554    pub fn active_item_as<I: 'static>(&self, cx: &App) -> Option<Entity<I>> {
2555        let item = self.active_item(cx)?;
2556        item.to_any().downcast::<I>().ok()
2557    }
2558
2559    fn active_project_path(&self, cx: &App) -> Option<ProjectPath> {
2560        self.active_item(cx).and_then(|item| item.project_path(cx))
2561    }
2562
2563    pub fn most_recent_active_path(&self, cx: &App) -> Option<PathBuf> {
2564        self.recent_navigation_history_iter(cx)
2565            .filter_map(|(path, abs_path)| {
2566                let worktree = self
2567                    .project
2568                    .read(cx)
2569                    .worktree_for_id(path.worktree_id, cx)?;
2570                if worktree.read(cx).is_visible() {
2571                    abs_path
2572                } else {
2573                    None
2574                }
2575            })
2576            .next()
2577    }
2578
2579    pub fn save_active_item(
2580        &mut self,
2581        save_intent: SaveIntent,
2582        window: &mut Window,
2583        cx: &mut App,
2584    ) -> Task<Result<()>> {
2585        let project = self.project.clone();
2586        let pane = self.active_pane();
2587        let item = pane.read(cx).active_item();
2588        let pane = pane.downgrade();
2589
2590        window.spawn(cx, async move |mut cx| {
2591            if let Some(item) = item {
2592                Pane::save_item(project, &pane, item.as_ref(), save_intent, &mut cx)
2593                    .await
2594                    .map(|_| ())
2595            } else {
2596                Ok(())
2597            }
2598        })
2599    }
2600
2601    pub fn close_inactive_items_and_panes(
2602        &mut self,
2603        action: &CloseInactiveTabsAndPanes,
2604        window: &mut Window,
2605        cx: &mut Context<Self>,
2606    ) {
2607        if let Some(task) = self.close_all_internal(
2608            true,
2609            action.save_intent.unwrap_or(SaveIntent::Close),
2610            window,
2611            cx,
2612        ) {
2613            task.detach_and_log_err(cx)
2614        }
2615    }
2616
2617    pub fn close_all_items_and_panes(
2618        &mut self,
2619        action: &CloseAllItemsAndPanes,
2620        window: &mut Window,
2621        cx: &mut Context<Self>,
2622    ) {
2623        if let Some(task) = self.close_all_internal(
2624            false,
2625            action.save_intent.unwrap_or(SaveIntent::Close),
2626            window,
2627            cx,
2628        ) {
2629            task.detach_and_log_err(cx)
2630        }
2631    }
2632
2633    fn close_all_internal(
2634        &mut self,
2635        retain_active_pane: bool,
2636        save_intent: SaveIntent,
2637        window: &mut Window,
2638        cx: &mut Context<Self>,
2639    ) -> Option<Task<Result<()>>> {
2640        let current_pane = self.active_pane();
2641
2642        let mut tasks = Vec::new();
2643
2644        if retain_active_pane {
2645            if let Some(current_pane_close) = current_pane.update(cx, |pane, cx| {
2646                pane.close_inactive_items(
2647                    &CloseInactiveItems {
2648                        save_intent: None,
2649                        close_pinned: false,
2650                    },
2651                    window,
2652                    cx,
2653                )
2654            }) {
2655                tasks.push(current_pane_close);
2656            };
2657        }
2658
2659        for pane in self.panes() {
2660            if retain_active_pane && pane.entity_id() == current_pane.entity_id() {
2661                continue;
2662            }
2663
2664            if let Some(close_pane_items) = pane.update(cx, |pane: &mut Pane, cx| {
2665                pane.close_all_items(
2666                    &CloseAllItems {
2667                        save_intent: Some(save_intent),
2668                        close_pinned: false,
2669                    },
2670                    window,
2671                    cx,
2672                )
2673            }) {
2674                tasks.push(close_pane_items)
2675            }
2676        }
2677
2678        if tasks.is_empty() {
2679            None
2680        } else {
2681            Some(cx.spawn_in(window, async move |_, _| {
2682                for task in tasks {
2683                    task.await?
2684                }
2685                Ok(())
2686            }))
2687        }
2688    }
2689
2690    pub fn is_dock_at_position_open(&self, position: DockPosition, cx: &mut Context<Self>) -> bool {
2691        self.dock_at_position(position).read(cx).is_open()
2692    }
2693
2694    pub fn toggle_dock(
2695        &mut self,
2696        dock_side: DockPosition,
2697        window: &mut Window,
2698        cx: &mut Context<Self>,
2699    ) {
2700        let dock = self.dock_at_position(dock_side);
2701        let mut focus_center = false;
2702        let mut reveal_dock = false;
2703        dock.update(cx, |dock, cx| {
2704            let other_is_zoomed = self.zoomed.is_some() && self.zoomed_position != Some(dock_side);
2705            let was_visible = dock.is_open() && !other_is_zoomed;
2706            dock.set_open(!was_visible, window, cx);
2707
2708            if dock.active_panel().is_none() {
2709                let Some(panel_ix) = dock
2710                    .first_enabled_panel_idx(cx)
2711                    .log_with_level(log::Level::Info)
2712                else {
2713                    return;
2714                };
2715                dock.activate_panel(panel_ix, window, cx);
2716            }
2717
2718            if let Some(active_panel) = dock.active_panel() {
2719                if was_visible {
2720                    if active_panel
2721                        .panel_focus_handle(cx)
2722                        .contains_focused(window, cx)
2723                    {
2724                        focus_center = true;
2725                    }
2726                } else {
2727                    let focus_handle = &active_panel.panel_focus_handle(cx);
2728                    window.focus(focus_handle);
2729                    reveal_dock = true;
2730                }
2731            }
2732        });
2733
2734        if reveal_dock {
2735            self.dismiss_zoomed_items_to_reveal(Some(dock_side), window, cx);
2736        }
2737
2738        if focus_center {
2739            self.active_pane
2740                .update(cx, |pane, cx| window.focus(&pane.focus_handle(cx)))
2741        }
2742
2743        cx.notify();
2744        self.serialize_workspace(window, cx);
2745    }
2746
2747    fn active_dock(&self, window: &Window, cx: &Context<Self>) -> Option<&Entity<Dock>> {
2748        self.all_docks().into_iter().find(|&dock| {
2749            dock.read(cx).is_open() && dock.focus_handle(cx).contains_focused(window, cx)
2750        })
2751    }
2752
2753    fn close_active_dock(&mut self, window: &mut Window, cx: &mut Context<Self>) {
2754        if let Some(dock) = self.active_dock(window, cx) {
2755            dock.update(cx, |dock, cx| {
2756                dock.set_open(false, window, cx);
2757            });
2758        }
2759    }
2760
2761    pub fn close_all_docks(&mut self, window: &mut Window, cx: &mut Context<Self>) {
2762        for dock in self.all_docks() {
2763            dock.update(cx, |dock, cx| {
2764                dock.set_open(false, window, cx);
2765            });
2766        }
2767
2768        cx.focus_self(window);
2769        cx.notify();
2770        self.serialize_workspace(window, cx);
2771    }
2772
2773    /// Transfer focus to the panel of the given type.
2774    pub fn focus_panel<T: Panel>(
2775        &mut self,
2776        window: &mut Window,
2777        cx: &mut Context<Self>,
2778    ) -> Option<Entity<T>> {
2779        let panel = self.focus_or_unfocus_panel::<T>(window, cx, |_, _, _| true)?;
2780        panel.to_any().downcast().ok()
2781    }
2782
2783    /// Focus the panel of the given type if it isn't already focused. If it is
2784    /// already focused, then transfer focus back to the workspace center.
2785    pub fn toggle_panel_focus<T: Panel>(
2786        &mut self,
2787        window: &mut Window,
2788        cx: &mut Context<Self>,
2789    ) -> bool {
2790        let mut did_focus_panel = false;
2791        self.focus_or_unfocus_panel::<T>(window, cx, |panel, window, cx| {
2792            did_focus_panel = !panel.panel_focus_handle(cx).contains_focused(window, cx);
2793            did_focus_panel
2794        });
2795        did_focus_panel
2796    }
2797
2798    pub fn activate_panel_for_proto_id(
2799        &mut self,
2800        panel_id: PanelId,
2801        window: &mut Window,
2802        cx: &mut Context<Self>,
2803    ) -> Option<Arc<dyn PanelHandle>> {
2804        let mut panel = None;
2805        for dock in self.all_docks() {
2806            if let Some(panel_index) = dock.read(cx).panel_index_for_proto_id(panel_id) {
2807                panel = dock.update(cx, |dock, cx| {
2808                    dock.activate_panel(panel_index, window, cx);
2809                    dock.set_open(true, window, cx);
2810                    dock.active_panel().cloned()
2811                });
2812                break;
2813            }
2814        }
2815
2816        if panel.is_some() {
2817            cx.notify();
2818            self.serialize_workspace(window, cx);
2819        }
2820
2821        panel
2822    }
2823
2824    /// Focus or unfocus the given panel type, depending on the given callback.
2825    fn focus_or_unfocus_panel<T: Panel>(
2826        &mut self,
2827        window: &mut Window,
2828        cx: &mut Context<Self>,
2829        mut should_focus: impl FnMut(&dyn PanelHandle, &mut Window, &mut Context<Dock>) -> bool,
2830    ) -> Option<Arc<dyn PanelHandle>> {
2831        let mut result_panel = None;
2832        let mut serialize = false;
2833        for dock in self.all_docks() {
2834            if let Some(panel_index) = dock.read(cx).panel_index_for_type::<T>() {
2835                let mut focus_center = false;
2836                let panel = dock.update(cx, |dock, cx| {
2837                    dock.activate_panel(panel_index, window, cx);
2838
2839                    let panel = dock.active_panel().cloned();
2840                    if let Some(panel) = panel.as_ref() {
2841                        if should_focus(&**panel, window, cx) {
2842                            dock.set_open(true, window, cx);
2843                            panel.panel_focus_handle(cx).focus(window);
2844                        } else {
2845                            focus_center = true;
2846                        }
2847                    }
2848                    panel
2849                });
2850
2851                if focus_center {
2852                    self.active_pane
2853                        .update(cx, |pane, cx| window.focus(&pane.focus_handle(cx)))
2854                }
2855
2856                result_panel = panel;
2857                serialize = true;
2858                break;
2859            }
2860        }
2861
2862        if serialize {
2863            self.serialize_workspace(window, cx);
2864        }
2865
2866        cx.notify();
2867        result_panel
2868    }
2869
2870    /// Open the panel of the given type
2871    pub fn open_panel<T: Panel>(&mut self, window: &mut Window, cx: &mut Context<Self>) {
2872        for dock in self.all_docks() {
2873            if let Some(panel_index) = dock.read(cx).panel_index_for_type::<T>() {
2874                dock.update(cx, |dock, cx| {
2875                    dock.activate_panel(panel_index, window, cx);
2876                    dock.set_open(true, window, cx);
2877                });
2878            }
2879        }
2880    }
2881
2882    pub fn panel<T: Panel>(&self, cx: &App) -> Option<Entity<T>> {
2883        self.all_docks()
2884            .iter()
2885            .find_map(|dock| dock.read(cx).panel::<T>())
2886    }
2887
2888    fn dismiss_zoomed_items_to_reveal(
2889        &mut self,
2890        dock_to_reveal: Option<DockPosition>,
2891        window: &mut Window,
2892        cx: &mut Context<Self>,
2893    ) {
2894        // If a center pane is zoomed, unzoom it.
2895        for pane in &self.panes {
2896            if pane != &self.active_pane || dock_to_reveal.is_some() {
2897                pane.update(cx, |pane, cx| pane.set_zoomed(false, cx));
2898            }
2899        }
2900
2901        // If another dock is zoomed, hide it.
2902        let mut focus_center = false;
2903        for dock in self.all_docks() {
2904            dock.update(cx, |dock, cx| {
2905                if Some(dock.position()) != dock_to_reveal {
2906                    if let Some(panel) = dock.active_panel() {
2907                        if panel.is_zoomed(window, cx) {
2908                            focus_center |=
2909                                panel.panel_focus_handle(cx).contains_focused(window, cx);
2910                            dock.set_open(false, window, cx);
2911                        }
2912                    }
2913                }
2914            });
2915        }
2916
2917        if focus_center {
2918            self.active_pane
2919                .update(cx, |pane, cx| window.focus(&pane.focus_handle(cx)))
2920        }
2921
2922        if self.zoomed_position != dock_to_reveal {
2923            self.zoomed = None;
2924            self.zoomed_position = None;
2925            cx.emit(Event::ZoomChanged);
2926        }
2927
2928        cx.notify();
2929    }
2930
2931    fn add_pane(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Entity<Pane> {
2932        let pane = cx.new(|cx| {
2933            let mut pane = Pane::new(
2934                self.weak_handle(),
2935                self.project.clone(),
2936                self.pane_history_timestamp.clone(),
2937                None,
2938                NewFile.boxed_clone(),
2939                window,
2940                cx,
2941            );
2942            pane.set_can_split(Some(Arc::new(|_, _, _, _| true)));
2943            pane
2944        });
2945        cx.subscribe_in(&pane, window, Self::handle_pane_event)
2946            .detach();
2947        self.panes.push(pane.clone());
2948
2949        window.focus(&pane.focus_handle(cx));
2950
2951        cx.emit(Event::PaneAdded(pane.clone()));
2952        pane
2953    }
2954
2955    pub fn add_item_to_center(
2956        &mut self,
2957        item: Box<dyn ItemHandle>,
2958        window: &mut Window,
2959        cx: &mut Context<Self>,
2960    ) -> bool {
2961        if let Some(center_pane) = self.last_active_center_pane.clone() {
2962            if let Some(center_pane) = center_pane.upgrade() {
2963                center_pane.update(cx, |pane, cx| {
2964                    pane.add_item(item, true, true, None, window, cx)
2965                });
2966                true
2967            } else {
2968                false
2969            }
2970        } else {
2971            false
2972        }
2973    }
2974
2975    pub fn add_item_to_active_pane(
2976        &mut self,
2977        item: Box<dyn ItemHandle>,
2978        destination_index: Option<usize>,
2979        focus_item: bool,
2980        window: &mut Window,
2981        cx: &mut App,
2982    ) {
2983        self.add_item(
2984            self.active_pane.clone(),
2985            item,
2986            destination_index,
2987            false,
2988            focus_item,
2989            window,
2990            cx,
2991        )
2992    }
2993
2994    pub fn add_item(
2995        &mut self,
2996        pane: Entity<Pane>,
2997        item: Box<dyn ItemHandle>,
2998        destination_index: Option<usize>,
2999        activate_pane: bool,
3000        focus_item: bool,
3001        window: &mut Window,
3002        cx: &mut App,
3003    ) {
3004        if let Some(text) = item.telemetry_event_text(cx) {
3005            telemetry::event!(text);
3006        }
3007
3008        pane.update(cx, |pane, cx| {
3009            pane.add_item(
3010                item,
3011                activate_pane,
3012                focus_item,
3013                destination_index,
3014                window,
3015                cx,
3016            )
3017        });
3018    }
3019
3020    pub fn split_item(
3021        &mut self,
3022        split_direction: SplitDirection,
3023        item: Box<dyn ItemHandle>,
3024        window: &mut Window,
3025        cx: &mut Context<Self>,
3026    ) {
3027        let new_pane = self.split_pane(self.active_pane.clone(), split_direction, window, cx);
3028        self.add_item(new_pane, item, None, true, true, window, cx);
3029    }
3030
3031    pub fn open_abs_path(
3032        &mut self,
3033        abs_path: PathBuf,
3034        options: OpenOptions,
3035        window: &mut Window,
3036        cx: &mut Context<Self>,
3037    ) -> Task<anyhow::Result<Box<dyn ItemHandle>>> {
3038        cx.spawn_in(window, async move |workspace, cx| {
3039            let open_paths_task_result = workspace
3040                .update_in(cx, |workspace, window, cx| {
3041                    workspace.open_paths(vec![abs_path.clone()], options, None, window, cx)
3042                })
3043                .with_context(|| format!("open abs path {abs_path:?} task spawn"))?
3044                .await;
3045            anyhow::ensure!(
3046                open_paths_task_result.len() == 1,
3047                "open abs path {abs_path:?} task returned incorrect number of results"
3048            );
3049            match open_paths_task_result
3050                .into_iter()
3051                .next()
3052                .expect("ensured single task result")
3053            {
3054                Some(open_result) => {
3055                    open_result.with_context(|| format!("open abs path {abs_path:?} task join"))
3056                }
3057                None => anyhow::bail!("open abs path {abs_path:?} task returned None"),
3058            }
3059        })
3060    }
3061
3062    pub fn split_abs_path(
3063        &mut self,
3064        abs_path: PathBuf,
3065        visible: bool,
3066        window: &mut Window,
3067        cx: &mut Context<Self>,
3068    ) -> Task<anyhow::Result<Box<dyn ItemHandle>>> {
3069        let project_path_task =
3070            Workspace::project_path_for_path(self.project.clone(), &abs_path, visible, cx);
3071        cx.spawn_in(window, async move |this, cx| {
3072            let (_, path) = project_path_task.await?;
3073            this.update_in(cx, |this, window, cx| this.split_path(path, window, cx))?
3074                .await
3075        })
3076    }
3077
3078    pub fn open_path(
3079        &mut self,
3080        path: impl Into<ProjectPath>,
3081        pane: Option<WeakEntity<Pane>>,
3082        focus_item: bool,
3083        window: &mut Window,
3084        cx: &mut App,
3085    ) -> Task<anyhow::Result<Box<dyn ItemHandle>>> {
3086        self.open_path_preview(path, pane, focus_item, false, true, window, cx)
3087    }
3088
3089    pub fn open_path_preview(
3090        &mut self,
3091        path: impl Into<ProjectPath>,
3092        pane: Option<WeakEntity<Pane>>,
3093        focus_item: bool,
3094        allow_preview: bool,
3095        activate: bool,
3096        window: &mut Window,
3097        cx: &mut App,
3098    ) -> Task<anyhow::Result<Box<dyn ItemHandle>>> {
3099        let pane = pane.unwrap_or_else(|| {
3100            self.last_active_center_pane.clone().unwrap_or_else(|| {
3101                self.panes
3102                    .first()
3103                    .expect("There must be an active pane")
3104                    .downgrade()
3105            })
3106        });
3107
3108        let project_path = path.into();
3109        let task = self.load_path(project_path.clone(), window, cx);
3110        window.spawn(cx, async move |cx| {
3111            let (project_entry_id, build_item) = task.await?;
3112            let result = pane.update_in(cx, |pane, window, cx| {
3113                let result = pane.open_item(
3114                    project_entry_id,
3115                    project_path,
3116                    focus_item,
3117                    allow_preview,
3118                    activate,
3119                    None,
3120                    window,
3121                    cx,
3122                    build_item,
3123                );
3124
3125                result
3126            });
3127            result
3128        })
3129    }
3130
3131    pub fn split_path(
3132        &mut self,
3133        path: impl Into<ProjectPath>,
3134        window: &mut Window,
3135        cx: &mut Context<Self>,
3136    ) -> Task<anyhow::Result<Box<dyn ItemHandle>>> {
3137        self.split_path_preview(path, false, None, window, cx)
3138    }
3139
3140    pub fn split_path_preview(
3141        &mut self,
3142        path: impl Into<ProjectPath>,
3143        allow_preview: bool,
3144        split_direction: Option<SplitDirection>,
3145        window: &mut Window,
3146        cx: &mut Context<Self>,
3147    ) -> Task<anyhow::Result<Box<dyn ItemHandle>>> {
3148        let pane = self.last_active_center_pane.clone().unwrap_or_else(|| {
3149            self.panes
3150                .first()
3151                .expect("There must be an active pane")
3152                .downgrade()
3153        });
3154
3155        if let Member::Pane(center_pane) = &self.center.root {
3156            if center_pane.read(cx).items_len() == 0 {
3157                return self.open_path(path, Some(pane), true, window, cx);
3158            }
3159        }
3160
3161        let project_path = path.into();
3162        let task = self.load_path(project_path.clone(), window, cx);
3163        cx.spawn_in(window, async move |this, cx| {
3164            let (project_entry_id, build_item) = task.await?;
3165            this.update_in(cx, move |this, window, cx| -> Option<_> {
3166                let pane = pane.upgrade()?;
3167                let new_pane = this.split_pane(
3168                    pane,
3169                    split_direction.unwrap_or(SplitDirection::Right),
3170                    window,
3171                    cx,
3172                );
3173                new_pane.update(cx, |new_pane, cx| {
3174                    Some(new_pane.open_item(
3175                        project_entry_id,
3176                        project_path,
3177                        true,
3178                        allow_preview,
3179                        true,
3180                        None,
3181                        window,
3182                        cx,
3183                        build_item,
3184                    ))
3185                })
3186            })
3187            .map(|option| option.context("pane was dropped"))?
3188        })
3189    }
3190
3191    fn load_path(
3192        &mut self,
3193        path: ProjectPath,
3194        window: &mut Window,
3195        cx: &mut App,
3196    ) -> Task<Result<(Option<ProjectEntryId>, WorkspaceItemBuilder)>> {
3197        let project = self.project().clone();
3198        let registry = cx.default_global::<ProjectItemRegistry>().clone();
3199        registry.open_path(&project, &path, window, cx)
3200    }
3201
3202    pub fn find_project_item<T>(
3203        &self,
3204        pane: &Entity<Pane>,
3205        project_item: &Entity<T::Item>,
3206        cx: &App,
3207    ) -> Option<Entity<T>>
3208    where
3209        T: ProjectItem,
3210    {
3211        use project::ProjectItem as _;
3212        let project_item = project_item.read(cx);
3213        let entry_id = project_item.entry_id(cx);
3214        let project_path = project_item.project_path(cx);
3215
3216        let mut item = None;
3217        if let Some(entry_id) = entry_id {
3218            item = pane.read(cx).item_for_entry(entry_id, cx);
3219        }
3220        if item.is_none() {
3221            if let Some(project_path) = project_path {
3222                item = pane.read(cx).item_for_path(project_path, cx);
3223            }
3224        }
3225
3226        item.and_then(|item| item.downcast::<T>())
3227    }
3228
3229    pub fn is_project_item_open<T>(
3230        &self,
3231        pane: &Entity<Pane>,
3232        project_item: &Entity<T::Item>,
3233        cx: &App,
3234    ) -> bool
3235    where
3236        T: ProjectItem,
3237    {
3238        self.find_project_item::<T>(pane, project_item, cx)
3239            .is_some()
3240    }
3241
3242    pub fn open_project_item<T>(
3243        &mut self,
3244        pane: Entity<Pane>,
3245        project_item: Entity<T::Item>,
3246        activate_pane: bool,
3247        focus_item: bool,
3248        window: &mut Window,
3249        cx: &mut Context<Self>,
3250    ) -> Entity<T>
3251    where
3252        T: ProjectItem,
3253    {
3254        if let Some(item) = self.find_project_item(&pane, &project_item, cx) {
3255            self.activate_item(&item, activate_pane, focus_item, window, cx);
3256            return item;
3257        }
3258
3259        let item = pane.update(cx, |pane, cx| {
3260            cx.new(|cx| {
3261                T::for_project_item(self.project().clone(), Some(pane), project_item, window, cx)
3262            })
3263        });
3264        let item_id = item.item_id();
3265        let mut destination_index = None;
3266        pane.update(cx, |pane, cx| {
3267            if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
3268                if let Some(preview_item_id) = pane.preview_item_id() {
3269                    if preview_item_id != item_id {
3270                        destination_index = pane.close_current_preview_item(window, cx);
3271                    }
3272                }
3273            }
3274            pane.set_preview_item_id(Some(item.item_id()), cx)
3275        });
3276
3277        self.add_item(
3278            pane,
3279            Box::new(item.clone()),
3280            destination_index,
3281            activate_pane,
3282            focus_item,
3283            window,
3284            cx,
3285        );
3286        item
3287    }
3288
3289    pub fn open_shared_screen(
3290        &mut self,
3291        peer_id: PeerId,
3292        window: &mut Window,
3293        cx: &mut Context<Self>,
3294    ) {
3295        if let Some(shared_screen) =
3296            self.shared_screen_for_peer(peer_id, &self.active_pane, window, cx)
3297        {
3298            self.active_pane.update(cx, |pane, cx| {
3299                pane.add_item(Box::new(shared_screen), false, true, None, window, cx)
3300            });
3301        }
3302    }
3303
3304    pub fn activate_item(
3305        &mut self,
3306        item: &dyn ItemHandle,
3307        activate_pane: bool,
3308        focus_item: bool,
3309        window: &mut Window,
3310        cx: &mut App,
3311    ) -> bool {
3312        let result = self.panes.iter().find_map(|pane| {
3313            pane.read(cx)
3314                .index_for_item(item)
3315                .map(|ix| (pane.clone(), ix))
3316        });
3317        if let Some((pane, ix)) = result {
3318            pane.update(cx, |pane, cx| {
3319                pane.activate_item(ix, activate_pane, focus_item, window, cx)
3320            });
3321            true
3322        } else {
3323            false
3324        }
3325    }
3326
3327    fn activate_pane_at_index(
3328        &mut self,
3329        action: &ActivatePane,
3330        window: &mut Window,
3331        cx: &mut Context<Self>,
3332    ) {
3333        let panes = self.center.panes();
3334        if let Some(pane) = panes.get(action.0).map(|p| (*p).clone()) {
3335            window.focus(&pane.focus_handle(cx));
3336        } else {
3337            self.split_and_clone(self.active_pane.clone(), SplitDirection::Right, window, cx);
3338        }
3339    }
3340
3341    fn move_item_to_pane_at_index(
3342        &mut self,
3343        action: &MoveItemToPane,
3344        window: &mut Window,
3345        cx: &mut Context<Self>,
3346    ) {
3347        let Some(&target_pane) = self.center.panes().get(action.destination) else {
3348            return;
3349        };
3350        move_active_item(
3351            &self.active_pane,
3352            target_pane,
3353            action.focus,
3354            true,
3355            window,
3356            cx,
3357        );
3358    }
3359
3360    pub fn activate_next_pane(&mut self, window: &mut Window, cx: &mut App) {
3361        let panes = self.center.panes();
3362        if let Some(ix) = panes.iter().position(|pane| **pane == self.active_pane) {
3363            let next_ix = (ix + 1) % panes.len();
3364            let next_pane = panes[next_ix].clone();
3365            window.focus(&next_pane.focus_handle(cx));
3366        }
3367    }
3368
3369    pub fn activate_previous_pane(&mut self, window: &mut Window, cx: &mut App) {
3370        let panes = self.center.panes();
3371        if let Some(ix) = panes.iter().position(|pane| **pane == self.active_pane) {
3372            let prev_ix = cmp::min(ix.wrapping_sub(1), panes.len() - 1);
3373            let prev_pane = panes[prev_ix].clone();
3374            window.focus(&prev_pane.focus_handle(cx));
3375        }
3376    }
3377
3378    pub fn activate_pane_in_direction(
3379        &mut self,
3380        direction: SplitDirection,
3381        window: &mut Window,
3382        cx: &mut App,
3383    ) {
3384        use ActivateInDirectionTarget as Target;
3385        enum Origin {
3386            LeftDock,
3387            RightDock,
3388            BottomDock,
3389            Center,
3390        }
3391
3392        let origin: Origin = [
3393            (&self.left_dock, Origin::LeftDock),
3394            (&self.right_dock, Origin::RightDock),
3395            (&self.bottom_dock, Origin::BottomDock),
3396        ]
3397        .into_iter()
3398        .find_map(|(dock, origin)| {
3399            if dock.focus_handle(cx).contains_focused(window, cx) && dock.read(cx).is_open() {
3400                Some(origin)
3401            } else {
3402                None
3403            }
3404        })
3405        .unwrap_or(Origin::Center);
3406
3407        let get_last_active_pane = || {
3408            let pane = self
3409                .last_active_center_pane
3410                .clone()
3411                .unwrap_or_else(|| {
3412                    self.panes
3413                        .first()
3414                        .expect("There must be an active pane")
3415                        .downgrade()
3416                })
3417                .upgrade()?;
3418            (pane.read(cx).items_len() != 0).then_some(pane)
3419        };
3420
3421        let try_dock =
3422            |dock: &Entity<Dock>| dock.read(cx).is_open().then(|| Target::Dock(dock.clone()));
3423
3424        let target = match (origin, direction) {
3425            // We're in the center, so we first try to go to a different pane,
3426            // otherwise try to go to a dock.
3427            (Origin::Center, direction) => {
3428                if let Some(pane) = self.find_pane_in_direction(direction, cx) {
3429                    Some(Target::Pane(pane))
3430                } else {
3431                    match direction {
3432                        SplitDirection::Up => None,
3433                        SplitDirection::Down => try_dock(&self.bottom_dock),
3434                        SplitDirection::Left => try_dock(&self.left_dock),
3435                        SplitDirection::Right => try_dock(&self.right_dock),
3436                    }
3437                }
3438            }
3439
3440            (Origin::LeftDock, SplitDirection::Right) => {
3441                if let Some(last_active_pane) = get_last_active_pane() {
3442                    Some(Target::Pane(last_active_pane))
3443                } else {
3444                    try_dock(&self.bottom_dock).or_else(|| try_dock(&self.right_dock))
3445                }
3446            }
3447
3448            (Origin::LeftDock, SplitDirection::Down)
3449            | (Origin::RightDock, SplitDirection::Down) => try_dock(&self.bottom_dock),
3450
3451            (Origin::BottomDock, SplitDirection::Up) => get_last_active_pane().map(Target::Pane),
3452            (Origin::BottomDock, SplitDirection::Left) => try_dock(&self.left_dock),
3453            (Origin::BottomDock, SplitDirection::Right) => try_dock(&self.right_dock),
3454
3455            (Origin::RightDock, SplitDirection::Left) => {
3456                if let Some(last_active_pane) = get_last_active_pane() {
3457                    Some(Target::Pane(last_active_pane))
3458                } else {
3459                    try_dock(&self.bottom_dock).or_else(|| try_dock(&self.left_dock))
3460                }
3461            }
3462
3463            _ => None,
3464        };
3465
3466        match target {
3467            Some(ActivateInDirectionTarget::Pane(pane)) => {
3468                window.focus(&pane.focus_handle(cx));
3469            }
3470            Some(ActivateInDirectionTarget::Dock(dock)) => {
3471                // Defer this to avoid a panic when the dock's active panel is already on the stack.
3472                window.defer(cx, move |window, cx| {
3473                    let dock = dock.read(cx);
3474                    if let Some(panel) = dock.active_panel() {
3475                        panel.panel_focus_handle(cx).focus(window);
3476                    } else {
3477                        log::error!("Could not find a focus target when in switching focus in {direction} direction for a {:?} dock", dock.position());
3478                    }
3479                })
3480            }
3481            None => {}
3482        }
3483    }
3484
3485    pub fn move_item_to_pane_in_direction(
3486        &mut self,
3487        action: &MoveItemToPaneInDirection,
3488        window: &mut Window,
3489        cx: &mut App,
3490    ) {
3491        if let Some(destination) = self.find_pane_in_direction(action.direction, cx) {
3492            move_active_item(
3493                &self.active_pane,
3494                &destination,
3495                action.focus,
3496                true,
3497                window,
3498                cx,
3499            );
3500        }
3501    }
3502
3503    pub fn bounding_box_for_pane(&self, pane: &Entity<Pane>) -> Option<Bounds<Pixels>> {
3504        self.center.bounding_box_for_pane(pane)
3505    }
3506
3507    pub fn find_pane_in_direction(
3508        &mut self,
3509        direction: SplitDirection,
3510        cx: &App,
3511    ) -> Option<Entity<Pane>> {
3512        self.center
3513            .find_pane_in_direction(&self.active_pane, direction, cx)
3514            .cloned()
3515    }
3516
3517    pub fn swap_pane_in_direction(&mut self, direction: SplitDirection, cx: &mut Context<Self>) {
3518        if let Some(to) = self.find_pane_in_direction(direction, cx) {
3519            self.center.swap(&self.active_pane, &to);
3520            cx.notify();
3521        }
3522    }
3523
3524    pub fn resize_pane(
3525        &mut self,
3526        axis: gpui::Axis,
3527        amount: Pixels,
3528        window: &mut Window,
3529        cx: &mut Context<Self>,
3530    ) {
3531        let docks = self.all_docks();
3532        let active_dock = docks
3533            .into_iter()
3534            .find(|dock| dock.focus_handle(cx).contains_focused(window, cx));
3535
3536        if let Some(dock) = active_dock {
3537            let Some(panel_size) = dock.read(cx).active_panel_size(window, cx) else {
3538                return;
3539            };
3540            match dock.read(cx).position() {
3541                DockPosition::Left => resize_left_dock(panel_size + amount, self, window, cx),
3542                DockPosition::Bottom => resize_bottom_dock(panel_size + amount, self, window, cx),
3543                DockPosition::Right => resize_right_dock(panel_size + amount, self, window, cx),
3544            }
3545        } else {
3546            self.center
3547                .resize(&self.active_pane, axis, amount, &self.bounds);
3548        }
3549        cx.notify();
3550    }
3551
3552    pub fn reset_pane_sizes(&mut self, cx: &mut Context<Self>) {
3553        self.center.reset_pane_sizes();
3554        cx.notify();
3555    }
3556
3557    fn handle_pane_focused(
3558        &mut self,
3559        pane: Entity<Pane>,
3560        window: &mut Window,
3561        cx: &mut Context<Self>,
3562    ) {
3563        // This is explicitly hoisted out of the following check for pane identity as
3564        // terminal panel panes are not registered as a center panes.
3565        self.status_bar.update(cx, |status_bar, cx| {
3566            status_bar.set_active_pane(&pane, window, cx);
3567        });
3568        if self.active_pane != pane {
3569            self.set_active_pane(&pane, window, cx);
3570        }
3571
3572        if self.last_active_center_pane.is_none() {
3573            self.last_active_center_pane = Some(pane.downgrade());
3574        }
3575
3576        self.dismiss_zoomed_items_to_reveal(None, window, cx);
3577        if pane.read(cx).is_zoomed() {
3578            self.zoomed = Some(pane.downgrade().into());
3579        } else {
3580            self.zoomed = None;
3581        }
3582        self.zoomed_position = None;
3583        cx.emit(Event::ZoomChanged);
3584        self.update_active_view_for_followers(window, cx);
3585        pane.update(cx, |pane, _| {
3586            pane.track_alternate_file_items();
3587        });
3588
3589        cx.notify();
3590    }
3591
3592    fn set_active_pane(
3593        &mut self,
3594        pane: &Entity<Pane>,
3595        window: &mut Window,
3596        cx: &mut Context<Self>,
3597    ) {
3598        self.active_pane = pane.clone();
3599        self.active_item_path_changed(window, cx);
3600        self.last_active_center_pane = Some(pane.downgrade());
3601    }
3602
3603    fn handle_panel_focused(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3604        self.update_active_view_for_followers(window, cx);
3605    }
3606
3607    fn handle_pane_event(
3608        &mut self,
3609        pane: &Entity<Pane>,
3610        event: &pane::Event,
3611        window: &mut Window,
3612        cx: &mut Context<Self>,
3613    ) {
3614        let mut serialize_workspace = true;
3615        match event {
3616            pane::Event::AddItem { item } => {
3617                item.added_to_pane(self, pane.clone(), window, cx);
3618                cx.emit(Event::ItemAdded {
3619                    item: item.boxed_clone(),
3620                });
3621            }
3622            pane::Event::Split(direction) => {
3623                self.split_and_clone(pane.clone(), *direction, window, cx);
3624            }
3625            pane::Event::JoinIntoNext => {
3626                self.join_pane_into_next(pane.clone(), window, cx);
3627            }
3628            pane::Event::JoinAll => {
3629                self.join_all_panes(window, cx);
3630            }
3631            pane::Event::Remove { focus_on_pane } => {
3632                self.remove_pane(pane.clone(), focus_on_pane.clone(), window, cx);
3633            }
3634            pane::Event::ActivateItem {
3635                local,
3636                focus_changed,
3637            } => {
3638                cx.on_next_frame(window, |_, window, _| {
3639                    window.invalidate_character_coordinates();
3640                });
3641
3642                pane.update(cx, |pane, _| {
3643                    pane.track_alternate_file_items();
3644                });
3645                if *local {
3646                    self.unfollow_in_pane(&pane, window, cx);
3647                }
3648                if pane == self.active_pane() {
3649                    self.active_item_path_changed(window, cx);
3650                    self.update_active_view_for_followers(window, cx);
3651                }
3652                serialize_workspace = *focus_changed || pane != self.active_pane();
3653            }
3654            pane::Event::UserSavedItem { item, save_intent } => {
3655                cx.emit(Event::UserSavedItem {
3656                    pane: pane.downgrade(),
3657                    item: item.boxed_clone(),
3658                    save_intent: *save_intent,
3659                });
3660                serialize_workspace = false;
3661            }
3662            pane::Event::ChangeItemTitle => {
3663                if *pane == self.active_pane {
3664                    self.active_item_path_changed(window, cx);
3665                }
3666                serialize_workspace = false;
3667            }
3668            pane::Event::RemoveItem { .. } => {}
3669            pane::Event::RemovedItem { item } => {
3670                cx.emit(Event::ActiveItemChanged);
3671                self.update_window_edited(window, cx);
3672                if let hash_map::Entry::Occupied(entry) = self.panes_by_item.entry(item.item_id()) {
3673                    if entry.get().entity_id() == pane.entity_id() {
3674                        entry.remove();
3675                    }
3676                }
3677            }
3678            pane::Event::Focus => {
3679                cx.on_next_frame(window, |_, window, _| {
3680                    window.invalidate_character_coordinates();
3681                });
3682                self.handle_pane_focused(pane.clone(), window, cx);
3683            }
3684            pane::Event::ZoomIn => {
3685                if *pane == self.active_pane {
3686                    pane.update(cx, |pane, cx| pane.set_zoomed(true, cx));
3687                    if pane.read(cx).has_focus(window, cx) {
3688                        self.zoomed = Some(pane.downgrade().into());
3689                        self.zoomed_position = None;
3690                        cx.emit(Event::ZoomChanged);
3691                    }
3692                    cx.notify();
3693                }
3694            }
3695            pane::Event::ZoomOut => {
3696                pane.update(cx, |pane, cx| pane.set_zoomed(false, cx));
3697                if self.zoomed_position.is_none() {
3698                    self.zoomed = None;
3699                    cx.emit(Event::ZoomChanged);
3700                }
3701                cx.notify();
3702            }
3703        }
3704
3705        if serialize_workspace {
3706            self.serialize_workspace(window, cx);
3707        }
3708    }
3709
3710    pub fn unfollow_in_pane(
3711        &mut self,
3712        pane: &Entity<Pane>,
3713        window: &mut Window,
3714        cx: &mut Context<Workspace>,
3715    ) -> Option<CollaboratorId> {
3716        let leader_id = self.leader_for_pane(pane)?;
3717        self.unfollow(leader_id, window, cx);
3718        Some(leader_id)
3719    }
3720
3721    pub fn split_pane(
3722        &mut self,
3723        pane_to_split: Entity<Pane>,
3724        split_direction: SplitDirection,
3725        window: &mut Window,
3726        cx: &mut Context<Self>,
3727    ) -> Entity<Pane> {
3728        let new_pane = self.add_pane(window, cx);
3729        self.center
3730            .split(&pane_to_split, &new_pane, split_direction)
3731            .unwrap();
3732        cx.notify();
3733        new_pane
3734    }
3735
3736    pub fn split_and_clone(
3737        &mut self,
3738        pane: Entity<Pane>,
3739        direction: SplitDirection,
3740        window: &mut Window,
3741        cx: &mut Context<Self>,
3742    ) -> Option<Entity<Pane>> {
3743        let item = pane.read(cx).active_item()?;
3744        let maybe_pane_handle =
3745            if let Some(clone) = item.clone_on_split(self.database_id(), window, cx) {
3746                let new_pane = self.add_pane(window, cx);
3747                new_pane.update(cx, |pane, cx| {
3748                    pane.add_item(clone, true, true, None, window, cx)
3749                });
3750                self.center.split(&pane, &new_pane, direction).unwrap();
3751                Some(new_pane)
3752            } else {
3753                None
3754            };
3755        cx.notify();
3756        maybe_pane_handle
3757    }
3758
3759    pub fn split_pane_with_item(
3760        &mut self,
3761        pane_to_split: WeakEntity<Pane>,
3762        split_direction: SplitDirection,
3763        from: WeakEntity<Pane>,
3764        item_id_to_move: EntityId,
3765        window: &mut Window,
3766        cx: &mut Context<Self>,
3767    ) {
3768        let Some(pane_to_split) = pane_to_split.upgrade() else {
3769            return;
3770        };
3771        let Some(from) = from.upgrade() else {
3772            return;
3773        };
3774
3775        let new_pane = self.add_pane(window, cx);
3776        move_item(&from, &new_pane, item_id_to_move, 0, window, cx);
3777        self.center
3778            .split(&pane_to_split, &new_pane, split_direction)
3779            .unwrap();
3780        cx.notify();
3781    }
3782
3783    pub fn split_pane_with_project_entry(
3784        &mut self,
3785        pane_to_split: WeakEntity<Pane>,
3786        split_direction: SplitDirection,
3787        project_entry: ProjectEntryId,
3788        window: &mut Window,
3789        cx: &mut Context<Self>,
3790    ) -> Option<Task<Result<()>>> {
3791        let pane_to_split = pane_to_split.upgrade()?;
3792        let new_pane = self.add_pane(window, cx);
3793        self.center
3794            .split(&pane_to_split, &new_pane, split_direction)
3795            .unwrap();
3796
3797        let path = self.project.read(cx).path_for_entry(project_entry, cx)?;
3798        let task = self.open_path(path, Some(new_pane.downgrade()), true, window, cx);
3799        Some(cx.foreground_executor().spawn(async move {
3800            task.await?;
3801            Ok(())
3802        }))
3803    }
3804
3805    pub fn join_all_panes(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3806        let active_item = self.active_pane.read(cx).active_item();
3807        for pane in &self.panes {
3808            join_pane_into_active(&self.active_pane, pane, window, cx);
3809        }
3810        if let Some(active_item) = active_item {
3811            self.activate_item(active_item.as_ref(), true, true, window, cx);
3812        }
3813        cx.notify();
3814    }
3815
3816    pub fn join_pane_into_next(
3817        &mut self,
3818        pane: Entity<Pane>,
3819        window: &mut Window,
3820        cx: &mut Context<Self>,
3821    ) {
3822        let next_pane = self
3823            .find_pane_in_direction(SplitDirection::Right, cx)
3824            .or_else(|| self.find_pane_in_direction(SplitDirection::Down, cx))
3825            .or_else(|| self.find_pane_in_direction(SplitDirection::Left, cx))
3826            .or_else(|| self.find_pane_in_direction(SplitDirection::Up, cx));
3827        let Some(next_pane) = next_pane else {
3828            return;
3829        };
3830        move_all_items(&pane, &next_pane, window, cx);
3831        cx.notify();
3832    }
3833
3834    fn remove_pane(
3835        &mut self,
3836        pane: Entity<Pane>,
3837        focus_on: Option<Entity<Pane>>,
3838        window: &mut Window,
3839        cx: &mut Context<Self>,
3840    ) {
3841        if self.center.remove(&pane).unwrap() {
3842            self.force_remove_pane(&pane, &focus_on, window, cx);
3843            self.unfollow_in_pane(&pane, window, cx);
3844            self.last_leaders_by_pane.remove(&pane.downgrade());
3845            for removed_item in pane.read(cx).items() {
3846                self.panes_by_item.remove(&removed_item.item_id());
3847            }
3848
3849            cx.notify();
3850        } else {
3851            self.active_item_path_changed(window, cx);
3852        }
3853        cx.emit(Event::PaneRemoved);
3854    }
3855
3856    pub fn panes(&self) -> &[Entity<Pane>] {
3857        &self.panes
3858    }
3859
3860    pub fn active_pane(&self) -> &Entity<Pane> {
3861        &self.active_pane
3862    }
3863
3864    pub fn focused_pane(&self, window: &Window, cx: &App) -> Entity<Pane> {
3865        for dock in self.all_docks() {
3866            if dock.focus_handle(cx).contains_focused(window, cx) {
3867                if let Some(pane) = dock
3868                    .read(cx)
3869                    .active_panel()
3870                    .and_then(|panel| panel.pane(cx))
3871                {
3872                    return pane;
3873                }
3874            }
3875        }
3876        self.active_pane().clone()
3877    }
3878
3879    pub fn adjacent_pane(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Entity<Pane> {
3880        self.find_pane_in_direction(SplitDirection::Right, cx)
3881            .or_else(|| self.find_pane_in_direction(SplitDirection::Left, cx))
3882            .unwrap_or_else(|| {
3883                self.split_pane(self.active_pane.clone(), SplitDirection::Right, window, cx)
3884            })
3885            .clone()
3886    }
3887
3888    pub fn pane_for(&self, handle: &dyn ItemHandle) -> Option<Entity<Pane>> {
3889        let weak_pane = self.panes_by_item.get(&handle.item_id())?;
3890        weak_pane.upgrade()
3891    }
3892
3893    fn collaborator_left(&mut self, peer_id: PeerId, window: &mut Window, cx: &mut Context<Self>) {
3894        self.follower_states.retain(|leader_id, state| {
3895            if *leader_id == CollaboratorId::PeerId(peer_id) {
3896                for item in state.items_by_leader_view_id.values() {
3897                    item.view.set_leader_id(None, window, cx);
3898                }
3899                false
3900            } else {
3901                true
3902            }
3903        });
3904        cx.notify();
3905    }
3906
3907    pub fn start_following(
3908        &mut self,
3909        leader_id: impl Into<CollaboratorId>,
3910        window: &mut Window,
3911        cx: &mut Context<Self>,
3912    ) -> Option<Task<Result<()>>> {
3913        let leader_id = leader_id.into();
3914        let pane = self.active_pane().clone();
3915
3916        self.last_leaders_by_pane
3917            .insert(pane.downgrade(), leader_id);
3918        self.unfollow(leader_id, window, cx);
3919        self.unfollow_in_pane(&pane, window, cx);
3920        self.follower_states.insert(
3921            leader_id,
3922            FollowerState {
3923                center_pane: pane.clone(),
3924                dock_pane: None,
3925                active_view_id: None,
3926                items_by_leader_view_id: Default::default(),
3927            },
3928        );
3929        cx.notify();
3930
3931        match leader_id {
3932            CollaboratorId::PeerId(leader_peer_id) => {
3933                let room_id = self.active_call()?.read(cx).room()?.read(cx).id();
3934                let project_id = self.project.read(cx).remote_id();
3935                let request = self.app_state.client.request(proto::Follow {
3936                    room_id,
3937                    project_id,
3938                    leader_id: Some(leader_peer_id),
3939                });
3940
3941                Some(cx.spawn_in(window, async move |this, cx| {
3942                    let response = request.await?;
3943                    this.update(cx, |this, _| {
3944                        let state = this
3945                            .follower_states
3946                            .get_mut(&leader_id)
3947                            .context("following interrupted")?;
3948                        state.active_view_id = response
3949                            .active_view
3950                            .as_ref()
3951                            .and_then(|view| ViewId::from_proto(view.id.clone()?).ok());
3952                        anyhow::Ok(())
3953                    })??;
3954                    if let Some(view) = response.active_view {
3955                        Self::add_view_from_leader(this.clone(), leader_peer_id, &view, cx).await?;
3956                    }
3957                    this.update_in(cx, |this, window, cx| {
3958                        this.leader_updated(leader_id, window, cx)
3959                    })?;
3960                    Ok(())
3961                }))
3962            }
3963            CollaboratorId::Agent => {
3964                self.leader_updated(leader_id, window, cx)?;
3965                Some(Task::ready(Ok(())))
3966            }
3967        }
3968    }
3969
3970    pub fn follow_next_collaborator(
3971        &mut self,
3972        _: &FollowNextCollaborator,
3973        window: &mut Window,
3974        cx: &mut Context<Self>,
3975    ) {
3976        let collaborators = self.project.read(cx).collaborators();
3977        let next_leader_id = if let Some(leader_id) = self.leader_for_pane(&self.active_pane) {
3978            let mut collaborators = collaborators.keys().copied();
3979            for peer_id in collaborators.by_ref() {
3980                if CollaboratorId::PeerId(peer_id) == leader_id {
3981                    break;
3982                }
3983            }
3984            collaborators.next().map(CollaboratorId::PeerId)
3985        } else if let Some(last_leader_id) =
3986            self.last_leaders_by_pane.get(&self.active_pane.downgrade())
3987        {
3988            match last_leader_id {
3989                CollaboratorId::PeerId(peer_id) => {
3990                    if collaborators.contains_key(peer_id) {
3991                        Some(*last_leader_id)
3992                    } else {
3993                        None
3994                    }
3995                }
3996                CollaboratorId::Agent => Some(CollaboratorId::Agent),
3997            }
3998        } else {
3999            None
4000        };
4001
4002        let pane = self.active_pane.clone();
4003        let Some(leader_id) = next_leader_id.or_else(|| {
4004            Some(CollaboratorId::PeerId(
4005                collaborators.keys().copied().next()?,
4006            ))
4007        }) else {
4008            return;
4009        };
4010        if self.unfollow_in_pane(&pane, window, cx) == Some(leader_id) {
4011            return;
4012        }
4013        if let Some(task) = self.start_following(leader_id, window, cx) {
4014            task.detach_and_log_err(cx)
4015        }
4016    }
4017
4018    pub fn follow(
4019        &mut self,
4020        leader_id: impl Into<CollaboratorId>,
4021        window: &mut Window,
4022        cx: &mut Context<Self>,
4023    ) {
4024        let leader_id = leader_id.into();
4025
4026        if let CollaboratorId::PeerId(peer_id) = leader_id {
4027            let Some(room) = ActiveCall::global(cx).read(cx).room() else {
4028                return;
4029            };
4030            let room = room.read(cx);
4031            let Some(remote_participant) = room.remote_participant_for_peer_id(peer_id) else {
4032                return;
4033            };
4034
4035            let project = self.project.read(cx);
4036
4037            let other_project_id = match remote_participant.location {
4038                call::ParticipantLocation::External => None,
4039                call::ParticipantLocation::UnsharedProject => None,
4040                call::ParticipantLocation::SharedProject { project_id } => {
4041                    if Some(project_id) == project.remote_id() {
4042                        None
4043                    } else {
4044                        Some(project_id)
4045                    }
4046                }
4047            };
4048
4049            // if they are active in another project, follow there.
4050            if let Some(project_id) = other_project_id {
4051                let app_state = self.app_state.clone();
4052                crate::join_in_room_project(project_id, remote_participant.user.id, app_state, cx)
4053                    .detach_and_log_err(cx);
4054            }
4055        }
4056
4057        // if you're already following, find the right pane and focus it.
4058        if let Some(follower_state) = self.follower_states.get(&leader_id) {
4059            window.focus(&follower_state.pane().focus_handle(cx));
4060
4061            return;
4062        }
4063
4064        // Otherwise, follow.
4065        if let Some(task) = self.start_following(leader_id, window, cx) {
4066            task.detach_and_log_err(cx)
4067        }
4068    }
4069
4070    pub fn unfollow(
4071        &mut self,
4072        leader_id: impl Into<CollaboratorId>,
4073        window: &mut Window,
4074        cx: &mut Context<Self>,
4075    ) -> Option<()> {
4076        cx.notify();
4077
4078        let leader_id = leader_id.into();
4079        let state = self.follower_states.remove(&leader_id)?;
4080        for (_, item) in state.items_by_leader_view_id {
4081            item.view.set_leader_id(None, window, cx);
4082        }
4083
4084        if let CollaboratorId::PeerId(leader_peer_id) = leader_id {
4085            let project_id = self.project.read(cx).remote_id();
4086            let room_id = self.active_call()?.read(cx).room()?.read(cx).id();
4087            self.app_state
4088                .client
4089                .send(proto::Unfollow {
4090                    room_id,
4091                    project_id,
4092                    leader_id: Some(leader_peer_id),
4093                })
4094                .log_err();
4095        }
4096
4097        Some(())
4098    }
4099
4100    pub fn is_being_followed(&self, id: impl Into<CollaboratorId>) -> bool {
4101        self.follower_states.contains_key(&id.into())
4102    }
4103
4104    fn active_item_path_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4105        cx.emit(Event::ActiveItemChanged);
4106        let active_entry = self.active_project_path(cx);
4107        self.project
4108            .update(cx, |project, cx| project.set_active_path(active_entry, cx));
4109
4110        self.update_window_title(window, cx);
4111    }
4112
4113    fn update_window_title(&mut self, window: &mut Window, cx: &mut App) {
4114        let project = self.project().read(cx);
4115        let mut title = String::new();
4116
4117        for (i, name) in project.worktree_root_names(cx).enumerate() {
4118            if i > 0 {
4119                title.push_str(", ");
4120            }
4121            title.push_str(name);
4122        }
4123
4124        if title.is_empty() {
4125            title = "empty project".to_string();
4126        }
4127
4128        if let Some(path) = self.active_item(cx).and_then(|item| item.project_path(cx)) {
4129            let filename = path
4130                .path
4131                .file_name()
4132                .map(|s| s.to_string_lossy())
4133                .or_else(|| {
4134                    Some(Cow::Borrowed(
4135                        project
4136                            .worktree_for_id(path.worktree_id, cx)?
4137                            .read(cx)
4138                            .root_name(),
4139                    ))
4140                });
4141
4142            if let Some(filename) = filename {
4143                title.push_str("");
4144                title.push_str(filename.as_ref());
4145            }
4146        }
4147
4148        if project.is_via_collab() {
4149            title.push_str("");
4150        } else if project.is_shared() {
4151            title.push_str("");
4152        }
4153
4154        window.set_window_title(&title);
4155    }
4156
4157    fn update_window_edited(&mut self, window: &mut Window, cx: &mut App) {
4158        let is_edited = !self.project.read(cx).is_disconnected(cx) && !self.dirty_items.is_empty();
4159        if is_edited != self.window_edited {
4160            self.window_edited = is_edited;
4161            window.set_window_edited(self.window_edited)
4162        }
4163    }
4164
4165    fn update_item_dirty_state(
4166        &mut self,
4167        item: &dyn ItemHandle,
4168        window: &mut Window,
4169        cx: &mut App,
4170    ) {
4171        let is_dirty = item.is_dirty(cx);
4172        let item_id = item.item_id();
4173        let was_dirty = self.dirty_items.contains_key(&item_id);
4174        if is_dirty == was_dirty {
4175            return;
4176        }
4177        if was_dirty {
4178            self.dirty_items.remove(&item_id);
4179            self.update_window_edited(window, cx);
4180            return;
4181        }
4182        if let Some(window_handle) = window.window_handle().downcast::<Self>() {
4183            let s = item.on_release(
4184                cx,
4185                Box::new(move |cx| {
4186                    window_handle
4187                        .update(cx, |this, window, cx| {
4188                            this.dirty_items.remove(&item_id);
4189                            this.update_window_edited(window, cx)
4190                        })
4191                        .ok();
4192                }),
4193            );
4194            self.dirty_items.insert(item_id, s);
4195            self.update_window_edited(window, cx);
4196        }
4197    }
4198
4199    fn render_notifications(&self, _window: &mut Window, _cx: &mut Context<Self>) -> Option<Div> {
4200        if self.notifications.is_empty() {
4201            None
4202        } else {
4203            Some(
4204                div()
4205                    .absolute()
4206                    .right_3()
4207                    .bottom_3()
4208                    .w_112()
4209                    .h_full()
4210                    .flex()
4211                    .flex_col()
4212                    .justify_end()
4213                    .gap_2()
4214                    .children(
4215                        self.notifications
4216                            .iter()
4217                            .map(|(_, notification)| notification.clone().into_any()),
4218                    ),
4219            )
4220        }
4221    }
4222
4223    // RPC handlers
4224
4225    fn active_view_for_follower(
4226        &self,
4227        follower_project_id: Option<u64>,
4228        window: &mut Window,
4229        cx: &mut Context<Self>,
4230    ) -> Option<proto::View> {
4231        let (item, panel_id) = self.active_item_for_followers(window, cx);
4232        let item = item?;
4233        let leader_id = self
4234            .pane_for(&*item)
4235            .and_then(|pane| self.leader_for_pane(&pane));
4236        let leader_peer_id = match leader_id {
4237            Some(CollaboratorId::PeerId(peer_id)) => Some(peer_id),
4238            Some(CollaboratorId::Agent) | None => None,
4239        };
4240
4241        let item_handle = item.to_followable_item_handle(cx)?;
4242        let id = item_handle.remote_id(&self.app_state.client, window, cx)?;
4243        let variant = item_handle.to_state_proto(window, cx)?;
4244
4245        if item_handle.is_project_item(window, cx)
4246            && (follower_project_id.is_none()
4247                || follower_project_id != self.project.read(cx).remote_id())
4248        {
4249            return None;
4250        }
4251
4252        Some(proto::View {
4253            id: id.to_proto(),
4254            leader_id: leader_peer_id,
4255            variant: Some(variant),
4256            panel_id: panel_id.map(|id| id as i32),
4257        })
4258    }
4259
4260    fn handle_follow(
4261        &mut self,
4262        follower_project_id: Option<u64>,
4263        window: &mut Window,
4264        cx: &mut Context<Self>,
4265    ) -> proto::FollowResponse {
4266        let active_view = self.active_view_for_follower(follower_project_id, window, cx);
4267
4268        cx.notify();
4269        proto::FollowResponse {
4270            // TODO: Remove after version 0.145.x stabilizes.
4271            active_view_id: active_view.as_ref().and_then(|view| view.id.clone()),
4272            views: active_view.iter().cloned().collect(),
4273            active_view,
4274        }
4275    }
4276
4277    fn handle_update_followers(
4278        &mut self,
4279        leader_id: PeerId,
4280        message: proto::UpdateFollowers,
4281        _window: &mut Window,
4282        _cx: &mut Context<Self>,
4283    ) {
4284        self.leader_updates_tx
4285            .unbounded_send((leader_id, message))
4286            .ok();
4287    }
4288
4289    async fn process_leader_update(
4290        this: &WeakEntity<Self>,
4291        leader_id: PeerId,
4292        update: proto::UpdateFollowers,
4293        cx: &mut AsyncWindowContext,
4294    ) -> Result<()> {
4295        match update.variant.context("invalid update")? {
4296            proto::update_followers::Variant::CreateView(view) => {
4297                let view_id = ViewId::from_proto(view.id.clone().context("invalid view id")?)?;
4298                let should_add_view = this.update(cx, |this, _| {
4299                    if let Some(state) = this.follower_states.get_mut(&leader_id.into()) {
4300                        anyhow::Ok(!state.items_by_leader_view_id.contains_key(&view_id))
4301                    } else {
4302                        anyhow::Ok(false)
4303                    }
4304                })??;
4305
4306                if should_add_view {
4307                    Self::add_view_from_leader(this.clone(), leader_id, &view, cx).await?
4308                }
4309            }
4310            proto::update_followers::Variant::UpdateActiveView(update_active_view) => {
4311                let should_add_view = this.update(cx, |this, _| {
4312                    if let Some(state) = this.follower_states.get_mut(&leader_id.into()) {
4313                        state.active_view_id = update_active_view
4314                            .view
4315                            .as_ref()
4316                            .and_then(|view| ViewId::from_proto(view.id.clone()?).ok());
4317
4318                        if state.active_view_id.is_some_and(|view_id| {
4319                            !state.items_by_leader_view_id.contains_key(&view_id)
4320                        }) {
4321                            anyhow::Ok(true)
4322                        } else {
4323                            anyhow::Ok(false)
4324                        }
4325                    } else {
4326                        anyhow::Ok(false)
4327                    }
4328                })??;
4329
4330                if should_add_view {
4331                    if let Some(view) = update_active_view.view {
4332                        Self::add_view_from_leader(this.clone(), leader_id, &view, cx).await?
4333                    }
4334                }
4335            }
4336            proto::update_followers::Variant::UpdateView(update_view) => {
4337                let variant = update_view.variant.context("missing update view variant")?;
4338                let id = update_view.id.context("missing update view id")?;
4339                let mut tasks = Vec::new();
4340                this.update_in(cx, |this, window, cx| {
4341                    let project = this.project.clone();
4342                    if let Some(state) = this.follower_states.get(&leader_id.into()) {
4343                        let view_id = ViewId::from_proto(id.clone())?;
4344                        if let Some(item) = state.items_by_leader_view_id.get(&view_id) {
4345                            tasks.push(item.view.apply_update_proto(
4346                                &project,
4347                                variant.clone(),
4348                                window,
4349                                cx,
4350                            ));
4351                        }
4352                    }
4353                    anyhow::Ok(())
4354                })??;
4355                try_join_all(tasks).await.log_err();
4356            }
4357        }
4358        this.update_in(cx, |this, window, cx| {
4359            this.leader_updated(leader_id, window, cx)
4360        })?;
4361        Ok(())
4362    }
4363
4364    async fn add_view_from_leader(
4365        this: WeakEntity<Self>,
4366        leader_id: PeerId,
4367        view: &proto::View,
4368        cx: &mut AsyncWindowContext,
4369    ) -> Result<()> {
4370        let this = this.upgrade().context("workspace dropped")?;
4371
4372        let Some(id) = view.id.clone() else {
4373            anyhow::bail!("no id for view");
4374        };
4375        let id = ViewId::from_proto(id)?;
4376        let panel_id = view.panel_id.and_then(proto::PanelId::from_i32);
4377
4378        let pane = this.update(cx, |this, _cx| {
4379            let state = this
4380                .follower_states
4381                .get(&leader_id.into())
4382                .context("stopped following")?;
4383            anyhow::Ok(state.pane().clone())
4384        })??;
4385        let existing_item = pane.update_in(cx, |pane, window, cx| {
4386            let client = this.read(cx).client().clone();
4387            pane.items().find_map(|item| {
4388                let item = item.to_followable_item_handle(cx)?;
4389                if item.remote_id(&client, window, cx) == Some(id) {
4390                    Some(item)
4391                } else {
4392                    None
4393                }
4394            })
4395        })?;
4396        let item = if let Some(existing_item) = existing_item {
4397            existing_item
4398        } else {
4399            let variant = view.variant.clone();
4400            anyhow::ensure!(variant.is_some(), "missing view variant");
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                anyhow::bail!(
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                .context("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            anyhow::bail!("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<anyhow::Result<Box<dyn ItemHandle>>>>,
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.with_context(|| format!("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.pop().context("no paths given")?);
6949    }
6950
6951    cx.update_window(window.into(), |_, window, cx| {
6952        window.replace_root(cx, |window, cx| {
6953            telemetry::event!("SSH Project Opened");
6954
6955            let mut workspace =
6956                Workspace::new(Some(workspace_id), project, app_state.clone(), window, cx);
6957            workspace.set_serialized_ssh_project(serialized_ssh_project);
6958            workspace.update_history(cx);
6959            workspace
6960        });
6961    })?;
6962
6963    window
6964        .update(cx, |_, window, cx| {
6965            window.activate_window();
6966            open_items(serialized_workspace, project_paths_to_open, window, cx)
6967        })?
6968        .await?;
6969
6970    window.update(cx, |workspace, _, cx| {
6971        for error in project_path_errors {
6972            if error.error_code() == proto::ErrorCode::DevServerProjectPathDoesNotExist {
6973                if let Some(path) = error.error_tag("path") {
6974                    workspace.show_error(&anyhow!("'{path}' does not exist"), cx)
6975                }
6976            } else {
6977                workspace.show_error(&error, cx)
6978            }
6979        }
6980    })?;
6981
6982    Ok(())
6983}
6984
6985fn serialize_ssh_project(
6986    connection_options: SshConnectionOptions,
6987    paths: Vec<PathBuf>,
6988    cx: &AsyncApp,
6989) -> Task<
6990    Result<(
6991        SerializedSshProject,
6992        WorkspaceId,
6993        Option<SerializedWorkspace>,
6994    )>,
6995> {
6996    cx.background_spawn(async move {
6997        let serialized_ssh_project = persistence::DB
6998            .get_or_create_ssh_project(
6999                connection_options.host.clone(),
7000                connection_options.port,
7001                paths
7002                    .iter()
7003                    .map(|path| path.to_string_lossy().to_string())
7004                    .collect::<Vec<_>>(),
7005                connection_options.username.clone(),
7006            )
7007            .await?;
7008
7009        let serialized_workspace =
7010            persistence::DB.workspace_for_ssh_project(&serialized_ssh_project);
7011
7012        let workspace_id = if let Some(workspace_id) =
7013            serialized_workspace.as_ref().map(|workspace| workspace.id)
7014        {
7015            workspace_id
7016        } else {
7017            persistence::DB.next_id().await?
7018        };
7019
7020        Ok((serialized_ssh_project, workspace_id, serialized_workspace))
7021    })
7022}
7023
7024pub fn join_in_room_project(
7025    project_id: u64,
7026    follow_user_id: u64,
7027    app_state: Arc<AppState>,
7028    cx: &mut App,
7029) -> Task<Result<()>> {
7030    let windows = cx.windows();
7031    cx.spawn(async move |cx| {
7032        let existing_workspace = windows.into_iter().find_map(|window_handle| {
7033            window_handle
7034                .downcast::<Workspace>()
7035                .and_then(|window_handle| {
7036                    window_handle
7037                        .update(cx, |workspace, _window, cx| {
7038                            if workspace.project().read(cx).remote_id() == Some(project_id) {
7039                                Some(window_handle)
7040                            } else {
7041                                None
7042                            }
7043                        })
7044                        .unwrap_or(None)
7045                })
7046        });
7047
7048        let workspace = if let Some(existing_workspace) = existing_workspace {
7049            existing_workspace
7050        } else {
7051            let active_call = cx.update(|cx| ActiveCall::global(cx))?;
7052            let room = active_call
7053                .read_with(cx, |call, _| call.room().cloned())?
7054                .context("not in a call")?;
7055            let project = room
7056                .update(cx, |room, cx| {
7057                    room.join_project(
7058                        project_id,
7059                        app_state.languages.clone(),
7060                        app_state.fs.clone(),
7061                        cx,
7062                    )
7063                })?
7064                .await?;
7065
7066            let window_bounds_override = window_bounds_env_override();
7067            cx.update(|cx| {
7068                let mut options = (app_state.build_window_options)(None, cx);
7069                options.window_bounds = window_bounds_override.map(WindowBounds::Windowed);
7070                cx.open_window(options, |window, cx| {
7071                    cx.new(|cx| {
7072                        Workspace::new(Default::default(), project, app_state.clone(), window, cx)
7073                    })
7074                })
7075            })??
7076        };
7077
7078        workspace.update(cx, |workspace, window, cx| {
7079            cx.activate(true);
7080            window.activate_window();
7081
7082            if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
7083                let follow_peer_id = room
7084                    .read(cx)
7085                    .remote_participants()
7086                    .iter()
7087                    .find(|(_, participant)| participant.user.id == follow_user_id)
7088                    .map(|(_, p)| p.peer_id)
7089                    .or_else(|| {
7090                        // If we couldn't follow the given user, follow the host instead.
7091                        let collaborator = workspace
7092                            .project()
7093                            .read(cx)
7094                            .collaborators()
7095                            .values()
7096                            .find(|collaborator| collaborator.is_host)?;
7097                        Some(collaborator.peer_id)
7098                    });
7099
7100                if let Some(follow_peer_id) = follow_peer_id {
7101                    workspace.follow(follow_peer_id, window, cx);
7102                }
7103            }
7104        })?;
7105
7106        anyhow::Ok(())
7107    })
7108}
7109
7110pub fn reload(reload: &Reload, cx: &mut App) {
7111    let should_confirm = WorkspaceSettings::get_global(cx).confirm_quit;
7112    let mut workspace_windows = cx
7113        .windows()
7114        .into_iter()
7115        .filter_map(|window| window.downcast::<Workspace>())
7116        .collect::<Vec<_>>();
7117
7118    // If multiple windows have unsaved changes, and need a save prompt,
7119    // prompt in the active window before switching to a different window.
7120    workspace_windows.sort_by_key(|window| window.is_active(cx) == Some(false));
7121
7122    let mut prompt = None;
7123    if let (true, Some(window)) = (should_confirm, workspace_windows.first()) {
7124        prompt = window
7125            .update(cx, |_, window, cx| {
7126                window.prompt(
7127                    PromptLevel::Info,
7128                    "Are you sure you want to restart?",
7129                    None,
7130                    &["Restart", "Cancel"],
7131                    cx,
7132                )
7133            })
7134            .ok();
7135    }
7136
7137    let binary_path = reload.binary_path.clone();
7138    cx.spawn(async move |cx| {
7139        if let Some(prompt) = prompt {
7140            let answer = prompt.await?;
7141            if answer != 0 {
7142                return Ok(());
7143            }
7144        }
7145
7146        // If the user cancels any save prompt, then keep the app open.
7147        for window in workspace_windows {
7148            if let Ok(should_close) = window.update(cx, |workspace, window, cx| {
7149                workspace.prepare_to_close(CloseIntent::Quit, window, cx)
7150            }) {
7151                if !should_close.await? {
7152                    return Ok(());
7153                }
7154            }
7155        }
7156
7157        cx.update(|cx| cx.restart(binary_path))
7158    })
7159    .detach_and_log_err(cx);
7160}
7161
7162fn parse_pixel_position_env_var(value: &str) -> Option<Point<Pixels>> {
7163    let mut parts = value.split(',');
7164    let x: usize = parts.next()?.parse().ok()?;
7165    let y: usize = parts.next()?.parse().ok()?;
7166    Some(point(px(x as f32), px(y as f32)))
7167}
7168
7169fn parse_pixel_size_env_var(value: &str) -> Option<Size<Pixels>> {
7170    let mut parts = value.split(',');
7171    let width: usize = parts.next()?.parse().ok()?;
7172    let height: usize = parts.next()?.parse().ok()?;
7173    Some(size(px(width as f32), px(height as f32)))
7174}
7175
7176pub fn client_side_decorations(
7177    element: impl IntoElement,
7178    window: &mut Window,
7179    cx: &mut App,
7180) -> Stateful<Div> {
7181    const BORDER_SIZE: Pixels = px(1.0);
7182    let decorations = window.window_decorations();
7183
7184    if matches!(decorations, Decorations::Client { .. }) {
7185        window.set_client_inset(theme::CLIENT_SIDE_DECORATION_SHADOW);
7186    }
7187
7188    struct GlobalResizeEdge(ResizeEdge);
7189    impl Global for GlobalResizeEdge {}
7190
7191    div()
7192        .id("window-backdrop")
7193        .bg(transparent_black())
7194        .map(|div| match decorations {
7195            Decorations::Server => div,
7196            Decorations::Client { tiling, .. } => div
7197                .when(!(tiling.top || tiling.right), |div| {
7198                    div.rounded_tr(theme::CLIENT_SIDE_DECORATION_ROUNDING)
7199                })
7200                .when(!(tiling.top || tiling.left), |div| {
7201                    div.rounded_tl(theme::CLIENT_SIDE_DECORATION_ROUNDING)
7202                })
7203                .when(!(tiling.bottom || tiling.right), |div| {
7204                    div.rounded_br(theme::CLIENT_SIDE_DECORATION_ROUNDING)
7205                })
7206                .when(!(tiling.bottom || tiling.left), |div| {
7207                    div.rounded_bl(theme::CLIENT_SIDE_DECORATION_ROUNDING)
7208                })
7209                .when(!tiling.top, |div| {
7210                    div.pt(theme::CLIENT_SIDE_DECORATION_SHADOW)
7211                })
7212                .when(!tiling.bottom, |div| {
7213                    div.pb(theme::CLIENT_SIDE_DECORATION_SHADOW)
7214                })
7215                .when(!tiling.left, |div| {
7216                    div.pl(theme::CLIENT_SIDE_DECORATION_SHADOW)
7217                })
7218                .when(!tiling.right, |div| {
7219                    div.pr(theme::CLIENT_SIDE_DECORATION_SHADOW)
7220                })
7221                .on_mouse_move(move |e, window, cx| {
7222                    let size = window.window_bounds().get_bounds().size;
7223                    let pos = e.position;
7224
7225                    let new_edge =
7226                        resize_edge(pos, theme::CLIENT_SIDE_DECORATION_SHADOW, size, tiling);
7227
7228                    let edge = cx.try_global::<GlobalResizeEdge>();
7229                    if new_edge != edge.map(|edge| edge.0) {
7230                        window
7231                            .window_handle()
7232                            .update(cx, |workspace, _, cx| {
7233                                cx.notify(workspace.entity_id());
7234                            })
7235                            .ok();
7236                    }
7237                })
7238                .on_mouse_down(MouseButton::Left, move |e, window, _| {
7239                    let size = window.window_bounds().get_bounds().size;
7240                    let pos = e.position;
7241
7242                    let edge = match resize_edge(
7243                        pos,
7244                        theme::CLIENT_SIDE_DECORATION_SHADOW,
7245                        size,
7246                        tiling,
7247                    ) {
7248                        Some(value) => value,
7249                        None => return,
7250                    };
7251
7252                    window.start_window_resize(edge);
7253                }),
7254        })
7255        .size_full()
7256        .child(
7257            div()
7258                .cursor(CursorStyle::Arrow)
7259                .map(|div| match decorations {
7260                    Decorations::Server => div,
7261                    Decorations::Client { tiling } => div
7262                        .border_color(cx.theme().colors().border)
7263                        .when(!(tiling.top || tiling.right), |div| {
7264                            div.rounded_tr(theme::CLIENT_SIDE_DECORATION_ROUNDING)
7265                        })
7266                        .when(!(tiling.top || tiling.left), |div| {
7267                            div.rounded_tl(theme::CLIENT_SIDE_DECORATION_ROUNDING)
7268                        })
7269                        .when(!(tiling.bottom || tiling.right), |div| {
7270                            div.rounded_br(theme::CLIENT_SIDE_DECORATION_ROUNDING)
7271                        })
7272                        .when(!(tiling.bottom || tiling.left), |div| {
7273                            div.rounded_bl(theme::CLIENT_SIDE_DECORATION_ROUNDING)
7274                        })
7275                        .when(!tiling.top, |div| div.border_t(BORDER_SIZE))
7276                        .when(!tiling.bottom, |div| div.border_b(BORDER_SIZE))
7277                        .when(!tiling.left, |div| div.border_l(BORDER_SIZE))
7278                        .when(!tiling.right, |div| div.border_r(BORDER_SIZE))
7279                        .when(!tiling.is_tiled(), |div| {
7280                            div.shadow(smallvec::smallvec![gpui::BoxShadow {
7281                                color: Hsla {
7282                                    h: 0.,
7283                                    s: 0.,
7284                                    l: 0.,
7285                                    a: 0.4,
7286                                },
7287                                blur_radius: theme::CLIENT_SIDE_DECORATION_SHADOW / 2.,
7288                                spread_radius: px(0.),
7289                                offset: point(px(0.0), px(0.0)),
7290                            }])
7291                        }),
7292                })
7293                .on_mouse_move(|_e, _, cx| {
7294                    cx.stop_propagation();
7295                })
7296                .size_full()
7297                .child(element),
7298        )
7299        .map(|div| match decorations {
7300            Decorations::Server => div,
7301            Decorations::Client { tiling, .. } => div.child(
7302                canvas(
7303                    |_bounds, window, _| {
7304                        window.insert_hitbox(
7305                            Bounds::new(
7306                                point(px(0.0), px(0.0)),
7307                                window.window_bounds().get_bounds().size,
7308                            ),
7309                            false,
7310                        )
7311                    },
7312                    move |_bounds, hitbox, window, cx| {
7313                        let mouse = window.mouse_position();
7314                        let size = window.window_bounds().get_bounds().size;
7315                        let Some(edge) =
7316                            resize_edge(mouse, theme::CLIENT_SIDE_DECORATION_SHADOW, size, tiling)
7317                        else {
7318                            return;
7319                        };
7320                        cx.set_global(GlobalResizeEdge(edge));
7321                        window.set_cursor_style(
7322                            match edge {
7323                                ResizeEdge::Top | ResizeEdge::Bottom => CursorStyle::ResizeUpDown,
7324                                ResizeEdge::Left | ResizeEdge::Right => {
7325                                    CursorStyle::ResizeLeftRight
7326                                }
7327                                ResizeEdge::TopLeft | ResizeEdge::BottomRight => {
7328                                    CursorStyle::ResizeUpLeftDownRight
7329                                }
7330                                ResizeEdge::TopRight | ResizeEdge::BottomLeft => {
7331                                    CursorStyle::ResizeUpRightDownLeft
7332                                }
7333                            },
7334                            Some(&hitbox),
7335                        );
7336                    },
7337                )
7338                .size_full()
7339                .absolute(),
7340            ),
7341        })
7342}
7343
7344fn resize_edge(
7345    pos: Point<Pixels>,
7346    shadow_size: Pixels,
7347    window_size: Size<Pixels>,
7348    tiling: Tiling,
7349) -> Option<ResizeEdge> {
7350    let bounds = Bounds::new(Point::default(), window_size).inset(shadow_size * 1.5);
7351    if bounds.contains(&pos) {
7352        return None;
7353    }
7354
7355    let corner_size = size(shadow_size * 1.5, shadow_size * 1.5);
7356    let top_left_bounds = Bounds::new(Point::new(px(0.), px(0.)), corner_size);
7357    if !tiling.top && top_left_bounds.contains(&pos) {
7358        return Some(ResizeEdge::TopLeft);
7359    }
7360
7361    let top_right_bounds = Bounds::new(
7362        Point::new(window_size.width - corner_size.width, px(0.)),
7363        corner_size,
7364    );
7365    if !tiling.top && top_right_bounds.contains(&pos) {
7366        return Some(ResizeEdge::TopRight);
7367    }
7368
7369    let bottom_left_bounds = Bounds::new(
7370        Point::new(px(0.), window_size.height - corner_size.height),
7371        corner_size,
7372    );
7373    if !tiling.bottom && bottom_left_bounds.contains(&pos) {
7374        return Some(ResizeEdge::BottomLeft);
7375    }
7376
7377    let bottom_right_bounds = Bounds::new(
7378        Point::new(
7379            window_size.width - corner_size.width,
7380            window_size.height - corner_size.height,
7381        ),
7382        corner_size,
7383    );
7384    if !tiling.bottom && bottom_right_bounds.contains(&pos) {
7385        return Some(ResizeEdge::BottomRight);
7386    }
7387
7388    if !tiling.top && pos.y < shadow_size {
7389        Some(ResizeEdge::Top)
7390    } else if !tiling.bottom && pos.y > window_size.height - shadow_size {
7391        Some(ResizeEdge::Bottom)
7392    } else if !tiling.left && pos.x < shadow_size {
7393        Some(ResizeEdge::Left)
7394    } else if !tiling.right && pos.x > window_size.width - shadow_size {
7395        Some(ResizeEdge::Right)
7396    } else {
7397        None
7398    }
7399}
7400
7401fn join_pane_into_active(
7402    active_pane: &Entity<Pane>,
7403    pane: &Entity<Pane>,
7404    window: &mut Window,
7405    cx: &mut App,
7406) {
7407    if pane == active_pane {
7408        return;
7409    } else if pane.read(cx).items_len() == 0 {
7410        pane.update(cx, |_, cx| {
7411            cx.emit(pane::Event::Remove {
7412                focus_on_pane: None,
7413            });
7414        })
7415    } else {
7416        move_all_items(pane, active_pane, window, cx);
7417    }
7418}
7419
7420fn move_all_items(
7421    from_pane: &Entity<Pane>,
7422    to_pane: &Entity<Pane>,
7423    window: &mut Window,
7424    cx: &mut App,
7425) {
7426    let destination_is_different = from_pane != to_pane;
7427    let mut moved_items = 0;
7428    for (item_ix, item_handle) in from_pane
7429        .read(cx)
7430        .items()
7431        .enumerate()
7432        .map(|(ix, item)| (ix, item.clone()))
7433        .collect::<Vec<_>>()
7434    {
7435        let ix = item_ix - moved_items;
7436        if destination_is_different {
7437            // Close item from previous pane
7438            from_pane.update(cx, |source, cx| {
7439                source.remove_item_and_focus_on_pane(ix, false, to_pane.clone(), window, cx);
7440            });
7441            moved_items += 1;
7442        }
7443
7444        // This automatically removes duplicate items in the pane
7445        to_pane.update(cx, |destination, cx| {
7446            destination.add_item(item_handle, true, true, None, window, cx);
7447            window.focus(&destination.focus_handle(cx))
7448        });
7449    }
7450}
7451
7452pub fn move_item(
7453    source: &Entity<Pane>,
7454    destination: &Entity<Pane>,
7455    item_id_to_move: EntityId,
7456    destination_index: usize,
7457    window: &mut Window,
7458    cx: &mut App,
7459) {
7460    let Some((item_ix, item_handle)) = source
7461        .read(cx)
7462        .items()
7463        .enumerate()
7464        .find(|(_, item_handle)| item_handle.item_id() == item_id_to_move)
7465        .map(|(ix, item)| (ix, item.clone()))
7466    else {
7467        // Tab was closed during drag
7468        return;
7469    };
7470
7471    if source != destination {
7472        // Close item from previous pane
7473        source.update(cx, |source, cx| {
7474            source.remove_item_and_focus_on_pane(item_ix, false, destination.clone(), window, cx);
7475        });
7476    }
7477
7478    // This automatically removes duplicate items in the pane
7479    destination.update(cx, |destination, cx| {
7480        destination.add_item(item_handle, true, true, Some(destination_index), window, cx);
7481        window.focus(&destination.focus_handle(cx))
7482    });
7483}
7484
7485pub fn move_active_item(
7486    source: &Entity<Pane>,
7487    destination: &Entity<Pane>,
7488    focus_destination: bool,
7489    close_if_empty: bool,
7490    window: &mut Window,
7491    cx: &mut App,
7492) {
7493    if source == destination {
7494        return;
7495    }
7496    let Some(active_item) = source.read(cx).active_item() else {
7497        return;
7498    };
7499    source.update(cx, |source_pane, cx| {
7500        let item_id = active_item.item_id();
7501        source_pane.remove_item(item_id, false, close_if_empty, window, cx);
7502        destination.update(cx, |target_pane, cx| {
7503            target_pane.add_item(
7504                active_item,
7505                focus_destination,
7506                focus_destination,
7507                Some(target_pane.items_len()),
7508                window,
7509                cx,
7510            );
7511        });
7512    });
7513}
7514
7515#[derive(Debug)]
7516pub struct WorkspacePosition {
7517    pub window_bounds: Option<WindowBounds>,
7518    pub display: Option<Uuid>,
7519    pub centered_layout: bool,
7520}
7521
7522pub fn ssh_workspace_position_from_db(
7523    host: String,
7524    port: Option<u16>,
7525    user: Option<String>,
7526    paths_to_open: &[PathBuf],
7527    cx: &App,
7528) -> Task<Result<WorkspacePosition>> {
7529    let paths = paths_to_open
7530        .iter()
7531        .map(|path| path.to_string_lossy().to_string())
7532        .collect::<Vec<_>>();
7533
7534    cx.background_spawn(async move {
7535        let serialized_ssh_project = persistence::DB
7536            .get_or_create_ssh_project(host, port, paths, user)
7537            .await
7538            .context("fetching serialized ssh project")?;
7539        let serialized_workspace =
7540            persistence::DB.workspace_for_ssh_project(&serialized_ssh_project);
7541
7542        let (window_bounds, display) = if let Some(bounds) = window_bounds_env_override() {
7543            (Some(WindowBounds::Windowed(bounds)), None)
7544        } else {
7545            let restorable_bounds = serialized_workspace
7546                .as_ref()
7547                .and_then(|workspace| Some((workspace.display?, workspace.window_bounds?)))
7548                .or_else(|| {
7549                    let (display, window_bounds) = DB.last_window().log_err()?;
7550                    Some((display?, window_bounds?))
7551                });
7552
7553            if let Some((serialized_display, serialized_status)) = restorable_bounds {
7554                (Some(serialized_status.0), Some(serialized_display))
7555            } else {
7556                (None, None)
7557            }
7558        };
7559
7560        let centered_layout = serialized_workspace
7561            .as_ref()
7562            .map(|w| w.centered_layout)
7563            .unwrap_or(false);
7564
7565        Ok(WorkspacePosition {
7566            window_bounds,
7567            display,
7568            centered_layout,
7569        })
7570    })
7571}
7572
7573#[cfg(test)]
7574mod tests {
7575    use std::{cell::RefCell, rc::Rc};
7576
7577    use super::*;
7578    use crate::{
7579        dock::{PanelEvent, test::TestPanel},
7580        item::{
7581            ItemEvent,
7582            test::{TestItem, TestProjectItem},
7583        },
7584    };
7585    use fs::FakeFs;
7586    use gpui::{
7587        DismissEvent, Empty, EventEmitter, FocusHandle, Focusable, Render, TestAppContext,
7588        UpdateGlobal, VisualTestContext, px,
7589    };
7590    use project::{Project, ProjectEntryId};
7591    use serde_json::json;
7592    use settings::SettingsStore;
7593
7594    #[gpui::test]
7595    async fn test_tab_disambiguation(cx: &mut TestAppContext) {
7596        init_test(cx);
7597
7598        let fs = FakeFs::new(cx.executor());
7599        let project = Project::test(fs, [], cx).await;
7600        let (workspace, cx) =
7601            cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
7602
7603        // Adding an item with no ambiguity renders the tab without detail.
7604        let item1 = cx.new(|cx| {
7605            let mut item = TestItem::new(cx);
7606            item.tab_descriptions = Some(vec!["c", "b1/c", "a/b1/c"]);
7607            item
7608        });
7609        workspace.update_in(cx, |workspace, window, cx| {
7610            workspace.add_item_to_active_pane(Box::new(item1.clone()), None, true, window, cx);
7611        });
7612        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(0)));
7613
7614        // Adding an item that creates ambiguity increases the level of detail on
7615        // both tabs.
7616        let item2 = cx.new_window_entity(|_window, cx| {
7617            let mut item = TestItem::new(cx);
7618            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
7619            item
7620        });
7621        workspace.update_in(cx, |workspace, window, cx| {
7622            workspace.add_item_to_active_pane(Box::new(item2.clone()), None, true, window, cx);
7623        });
7624        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
7625        item2.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
7626
7627        // Adding an item that creates ambiguity increases the level of detail only
7628        // on the ambiguous tabs. In this case, the ambiguity can't be resolved so
7629        // we stop at the highest detail available.
7630        let item3 = cx.new(|cx| {
7631            let mut item = TestItem::new(cx);
7632            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
7633            item
7634        });
7635        workspace.update_in(cx, |workspace, window, cx| {
7636            workspace.add_item_to_active_pane(Box::new(item3.clone()), None, true, window, cx);
7637        });
7638        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
7639        item2.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
7640        item3.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
7641    }
7642
7643    #[gpui::test]
7644    async fn test_tracking_active_path(cx: &mut TestAppContext) {
7645        init_test(cx);
7646
7647        let fs = FakeFs::new(cx.executor());
7648        fs.insert_tree(
7649            "/root1",
7650            json!({
7651                "one.txt": "",
7652                "two.txt": "",
7653            }),
7654        )
7655        .await;
7656        fs.insert_tree(
7657            "/root2",
7658            json!({
7659                "three.txt": "",
7660            }),
7661        )
7662        .await;
7663
7664        let project = Project::test(fs, ["root1".as_ref()], cx).await;
7665        let (workspace, cx) =
7666            cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
7667        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
7668        let worktree_id = project.update(cx, |project, cx| {
7669            project.worktrees(cx).next().unwrap().read(cx).id()
7670        });
7671
7672        let item1 = cx.new(|cx| {
7673            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "one.txt", cx)])
7674        });
7675        let item2 = cx.new(|cx| {
7676            TestItem::new(cx).with_project_items(&[TestProjectItem::new(2, "two.txt", cx)])
7677        });
7678
7679        // Add an item to an empty pane
7680        workspace.update_in(cx, |workspace, window, cx| {
7681            workspace.add_item_to_active_pane(Box::new(item1), None, true, window, cx)
7682        });
7683        project.update(cx, |project, cx| {
7684            assert_eq!(
7685                project.active_entry(),
7686                project
7687                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
7688                    .map(|e| e.id)
7689            );
7690        });
7691        assert_eq!(cx.window_title().as_deref(), Some("root1 — one.txt"));
7692
7693        // Add a second item to a non-empty pane
7694        workspace.update_in(cx, |workspace, window, cx| {
7695            workspace.add_item_to_active_pane(Box::new(item2), None, true, window, cx)
7696        });
7697        assert_eq!(cx.window_title().as_deref(), Some("root1 — two.txt"));
7698        project.update(cx, |project, cx| {
7699            assert_eq!(
7700                project.active_entry(),
7701                project
7702                    .entry_for_path(&(worktree_id, "two.txt").into(), cx)
7703                    .map(|e| e.id)
7704            );
7705        });
7706
7707        // Close the active item
7708        pane.update_in(cx, |pane, window, cx| {
7709            pane.close_active_item(&Default::default(), window, cx)
7710                .unwrap()
7711        })
7712        .await
7713        .unwrap();
7714        assert_eq!(cx.window_title().as_deref(), Some("root1 — one.txt"));
7715        project.update(cx, |project, cx| {
7716            assert_eq!(
7717                project.active_entry(),
7718                project
7719                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
7720                    .map(|e| e.id)
7721            );
7722        });
7723
7724        // Add a project folder
7725        project
7726            .update(cx, |project, cx| {
7727                project.find_or_create_worktree("root2", true, cx)
7728            })
7729            .await
7730            .unwrap();
7731        assert_eq!(cx.window_title().as_deref(), Some("root1, root2 — one.txt"));
7732
7733        // Remove a project folder
7734        project.update(cx, |project, cx| project.remove_worktree(worktree_id, cx));
7735        assert_eq!(cx.window_title().as_deref(), Some("root2 — one.txt"));
7736    }
7737
7738    #[gpui::test]
7739    async fn test_close_window(cx: &mut TestAppContext) {
7740        init_test(cx);
7741
7742        let fs = FakeFs::new(cx.executor());
7743        fs.insert_tree("/root", json!({ "one": "" })).await;
7744
7745        let project = Project::test(fs, ["root".as_ref()], cx).await;
7746        let (workspace, cx) =
7747            cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
7748
7749        // When there are no dirty items, there's nothing to do.
7750        let item1 = cx.new(TestItem::new);
7751        workspace.update_in(cx, |w, window, cx| {
7752            w.add_item_to_active_pane(Box::new(item1.clone()), None, true, window, cx)
7753        });
7754        let task = workspace.update_in(cx, |w, window, cx| {
7755            w.prepare_to_close(CloseIntent::CloseWindow, window, cx)
7756        });
7757        assert!(task.await.unwrap());
7758
7759        // When there are dirty untitled items, prompt to save each one. If the user
7760        // cancels any prompt, then abort.
7761        let item2 = cx.new(|cx| TestItem::new(cx).with_dirty(true));
7762        let item3 = cx.new(|cx| {
7763            TestItem::new(cx)
7764                .with_dirty(true)
7765                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
7766        });
7767        workspace.update_in(cx, |w, window, cx| {
7768            w.add_item_to_active_pane(Box::new(item2.clone()), None, true, window, cx);
7769            w.add_item_to_active_pane(Box::new(item3.clone()), None, true, window, cx);
7770        });
7771        let task = workspace.update_in(cx, |w, window, cx| {
7772            w.prepare_to_close(CloseIntent::CloseWindow, window, cx)
7773        });
7774        cx.executor().run_until_parked();
7775        cx.simulate_prompt_answer("Cancel"); // cancel save all
7776        cx.executor().run_until_parked();
7777        assert!(!cx.has_pending_prompt());
7778        assert!(!task.await.unwrap());
7779    }
7780
7781    #[gpui::test]
7782    async fn test_close_window_with_serializable_items(cx: &mut TestAppContext) {
7783        init_test(cx);
7784
7785        // Register TestItem as a serializable item
7786        cx.update(|cx| {
7787            register_serializable_item::<TestItem>(cx);
7788        });
7789
7790        let fs = FakeFs::new(cx.executor());
7791        fs.insert_tree("/root", json!({ "one": "" })).await;
7792
7793        let project = Project::test(fs, ["root".as_ref()], cx).await;
7794        let (workspace, cx) =
7795            cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
7796
7797        // When there are dirty untitled items, but they can serialize, then there is no prompt.
7798        let item1 = cx.new(|cx| {
7799            TestItem::new(cx)
7800                .with_dirty(true)
7801                .with_serialize(|| Some(Task::ready(Ok(()))))
7802        });
7803        let item2 = cx.new(|cx| {
7804            TestItem::new(cx)
7805                .with_dirty(true)
7806                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
7807                .with_serialize(|| Some(Task::ready(Ok(()))))
7808        });
7809        workspace.update_in(cx, |w, window, cx| {
7810            w.add_item_to_active_pane(Box::new(item1.clone()), None, true, window, cx);
7811            w.add_item_to_active_pane(Box::new(item2.clone()), None, true, window, cx);
7812        });
7813        let task = workspace.update_in(cx, |w, window, cx| {
7814            w.prepare_to_close(CloseIntent::CloseWindow, window, cx)
7815        });
7816        assert!(task.await.unwrap());
7817    }
7818
7819    #[gpui::test]
7820    async fn test_close_pane_items(cx: &mut TestAppContext) {
7821        init_test(cx);
7822
7823        let fs = FakeFs::new(cx.executor());
7824
7825        let project = Project::test(fs, None, cx).await;
7826        let (workspace, cx) =
7827            cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
7828
7829        let item1 = cx.new(|cx| {
7830            TestItem::new(cx)
7831                .with_dirty(true)
7832                .with_project_items(&[dirty_project_item(1, "1.txt", cx)])
7833        });
7834        let item2 = cx.new(|cx| {
7835            TestItem::new(cx)
7836                .with_dirty(true)
7837                .with_conflict(true)
7838                .with_project_items(&[dirty_project_item(2, "2.txt", cx)])
7839        });
7840        let item3 = cx.new(|cx| {
7841            TestItem::new(cx)
7842                .with_dirty(true)
7843                .with_conflict(true)
7844                .with_project_items(&[dirty_project_item(3, "3.txt", cx)])
7845        });
7846        let item4 = cx.new(|cx| {
7847            TestItem::new(cx).with_dirty(true).with_project_items(&[{
7848                let project_item = TestProjectItem::new_untitled(cx);
7849                project_item.update(cx, |project_item, _| project_item.is_dirty = true);
7850                project_item
7851            }])
7852        });
7853        let pane = workspace.update_in(cx, |workspace, window, cx| {
7854            workspace.add_item_to_active_pane(Box::new(item1.clone()), None, true, window, cx);
7855            workspace.add_item_to_active_pane(Box::new(item2.clone()), None, true, window, cx);
7856            workspace.add_item_to_active_pane(Box::new(item3.clone()), None, true, window, cx);
7857            workspace.add_item_to_active_pane(Box::new(item4.clone()), None, true, window, cx);
7858            workspace.active_pane().clone()
7859        });
7860
7861        let close_items = pane.update_in(cx, |pane, window, cx| {
7862            pane.activate_item(1, true, true, window, cx);
7863            assert_eq!(pane.active_item().unwrap().item_id(), item2.item_id());
7864            let item1_id = item1.item_id();
7865            let item3_id = item3.item_id();
7866            let item4_id = item4.item_id();
7867            pane.close_items(window, cx, SaveIntent::Close, move |id| {
7868                [item1_id, item3_id, item4_id].contains(&id)
7869            })
7870        });
7871        cx.executor().run_until_parked();
7872
7873        assert!(cx.has_pending_prompt());
7874        cx.simulate_prompt_answer("Save all");
7875
7876        cx.executor().run_until_parked();
7877
7878        // Item 1 is saved. There's a prompt to save item 3.
7879        pane.update(cx, |pane, cx| {
7880            assert_eq!(item1.read(cx).save_count, 1);
7881            assert_eq!(item1.read(cx).save_as_count, 0);
7882            assert_eq!(item1.read(cx).reload_count, 0);
7883            assert_eq!(pane.items_len(), 3);
7884            assert_eq!(pane.active_item().unwrap().item_id(), item3.item_id());
7885        });
7886        assert!(cx.has_pending_prompt());
7887
7888        // Cancel saving item 3.
7889        cx.simulate_prompt_answer("Discard");
7890        cx.executor().run_until_parked();
7891
7892        // Item 3 is reloaded. There's a prompt to save item 4.
7893        pane.update(cx, |pane, cx| {
7894            assert_eq!(item3.read(cx).save_count, 0);
7895            assert_eq!(item3.read(cx).save_as_count, 0);
7896            assert_eq!(item3.read(cx).reload_count, 1);
7897            assert_eq!(pane.items_len(), 2);
7898            assert_eq!(pane.active_item().unwrap().item_id(), item4.item_id());
7899        });
7900
7901        // There's a prompt for a path for item 4.
7902        cx.simulate_new_path_selection(|_| Some(Default::default()));
7903        close_items.await.unwrap();
7904
7905        // The requested items are closed.
7906        pane.update(cx, |pane, cx| {
7907            assert_eq!(item4.read(cx).save_count, 0);
7908            assert_eq!(item4.read(cx).save_as_count, 1);
7909            assert_eq!(item4.read(cx).reload_count, 0);
7910            assert_eq!(pane.items_len(), 1);
7911            assert_eq!(pane.active_item().unwrap().item_id(), item2.item_id());
7912        });
7913    }
7914
7915    #[gpui::test]
7916    async fn test_prompting_to_save_only_on_last_item_for_entry(cx: &mut TestAppContext) {
7917        init_test(cx);
7918
7919        let fs = FakeFs::new(cx.executor());
7920        let project = Project::test(fs, [], cx).await;
7921        let (workspace, cx) =
7922            cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
7923
7924        // Create several workspace items with single project entries, and two
7925        // workspace items with multiple project entries.
7926        let single_entry_items = (0..=4)
7927            .map(|project_entry_id| {
7928                cx.new(|cx| {
7929                    TestItem::new(cx)
7930                        .with_dirty(true)
7931                        .with_project_items(&[dirty_project_item(
7932                            project_entry_id,
7933                            &format!("{project_entry_id}.txt"),
7934                            cx,
7935                        )])
7936                })
7937            })
7938            .collect::<Vec<_>>();
7939        let item_2_3 = cx.new(|cx| {
7940            TestItem::new(cx)
7941                .with_dirty(true)
7942                .with_singleton(false)
7943                .with_project_items(&[
7944                    single_entry_items[2].read(cx).project_items[0].clone(),
7945                    single_entry_items[3].read(cx).project_items[0].clone(),
7946                ])
7947        });
7948        let item_3_4 = cx.new(|cx| {
7949            TestItem::new(cx)
7950                .with_dirty(true)
7951                .with_singleton(false)
7952                .with_project_items(&[
7953                    single_entry_items[3].read(cx).project_items[0].clone(),
7954                    single_entry_items[4].read(cx).project_items[0].clone(),
7955                ])
7956        });
7957
7958        // Create two panes that contain the following project entries:
7959        //   left pane:
7960        //     multi-entry items:   (2, 3)
7961        //     single-entry items:  0, 2, 3, 4
7962        //   right pane:
7963        //     single-entry items:  4, 1
7964        //     multi-entry items:   (3, 4)
7965        let (left_pane, right_pane) = workspace.update_in(cx, |workspace, window, cx| {
7966            let left_pane = workspace.active_pane().clone();
7967            workspace.add_item_to_active_pane(Box::new(item_2_3.clone()), None, true, window, cx);
7968            workspace.add_item_to_active_pane(
7969                single_entry_items[0].boxed_clone(),
7970                None,
7971                true,
7972                window,
7973                cx,
7974            );
7975            workspace.add_item_to_active_pane(
7976                single_entry_items[2].boxed_clone(),
7977                None,
7978                true,
7979                window,
7980                cx,
7981            );
7982            workspace.add_item_to_active_pane(
7983                single_entry_items[3].boxed_clone(),
7984                None,
7985                true,
7986                window,
7987                cx,
7988            );
7989            workspace.add_item_to_active_pane(
7990                single_entry_items[4].boxed_clone(),
7991                None,
7992                true,
7993                window,
7994                cx,
7995            );
7996
7997            let right_pane = workspace
7998                .split_and_clone(left_pane.clone(), SplitDirection::Right, window, cx)
7999                .unwrap();
8000
8001            right_pane.update(cx, |pane, cx| {
8002                pane.add_item(
8003                    single_entry_items[1].boxed_clone(),
8004                    true,
8005                    true,
8006                    None,
8007                    window,
8008                    cx,
8009                );
8010                pane.add_item(Box::new(item_3_4.clone()), true, true, None, window, cx);
8011            });
8012
8013            (left_pane, right_pane)
8014        });
8015
8016        cx.focus(&right_pane);
8017
8018        let mut close = right_pane.update_in(cx, |pane, window, cx| {
8019            pane.close_all_items(&CloseAllItems::default(), window, cx)
8020                .unwrap()
8021        });
8022        cx.executor().run_until_parked();
8023
8024        let msg = cx.pending_prompt().unwrap().0;
8025        assert!(msg.contains("1.txt"));
8026        assert!(!msg.contains("2.txt"));
8027        assert!(!msg.contains("3.txt"));
8028        assert!(!msg.contains("4.txt"));
8029
8030        cx.simulate_prompt_answer("Cancel");
8031        close.await.unwrap();
8032
8033        left_pane
8034            .update_in(cx, |left_pane, window, cx| {
8035                left_pane.close_item_by_id(
8036                    single_entry_items[3].entity_id(),
8037                    SaveIntent::Skip,
8038                    window,
8039                    cx,
8040                )
8041            })
8042            .await
8043            .unwrap();
8044
8045        close = right_pane.update_in(cx, |pane, window, cx| {
8046            pane.close_all_items(&CloseAllItems::default(), window, cx)
8047                .unwrap()
8048        });
8049        cx.executor().run_until_parked();
8050
8051        let details = cx.pending_prompt().unwrap().1;
8052        assert!(details.contains("1.txt"));
8053        assert!(!details.contains("2.txt"));
8054        assert!(details.contains("3.txt"));
8055        // ideally this assertion could be made, but today we can only
8056        // save whole items not project items, so the orphaned item 3 causes
8057        // 4 to be saved too.
8058        // assert!(!details.contains("4.txt"));
8059
8060        cx.simulate_prompt_answer("Save all");
8061
8062        cx.executor().run_until_parked();
8063        close.await.unwrap();
8064        right_pane.update(cx, |pane, _| {
8065            assert_eq!(pane.items_len(), 0);
8066        });
8067    }
8068
8069    #[gpui::test]
8070    async fn test_autosave(cx: &mut gpui::TestAppContext) {
8071        init_test(cx);
8072
8073        let fs = FakeFs::new(cx.executor());
8074        let project = Project::test(fs, [], cx).await;
8075        let (workspace, cx) =
8076            cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
8077        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
8078
8079        let item = cx.new(|cx| {
8080            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
8081        });
8082        let item_id = item.entity_id();
8083        workspace.update_in(cx, |workspace, window, cx| {
8084            workspace.add_item_to_active_pane(Box::new(item.clone()), None, true, window, cx);
8085        });
8086
8087        // Autosave on window change.
8088        item.update(cx, |item, cx| {
8089            SettingsStore::update_global(cx, |settings, cx| {
8090                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
8091                    settings.autosave = Some(AutosaveSetting::OnWindowChange);
8092                })
8093            });
8094            item.is_dirty = true;
8095        });
8096
8097        // Deactivating the window saves the file.
8098        cx.deactivate_window();
8099        item.update(cx, |item, _| assert_eq!(item.save_count, 1));
8100
8101        // Re-activating the window doesn't save the file.
8102        cx.update(|window, _| window.activate_window());
8103        cx.executor().run_until_parked();
8104        item.update(cx, |item, _| assert_eq!(item.save_count, 1));
8105
8106        // Autosave on focus change.
8107        item.update_in(cx, |item, window, cx| {
8108            cx.focus_self(window);
8109            SettingsStore::update_global(cx, |settings, cx| {
8110                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
8111                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
8112                })
8113            });
8114            item.is_dirty = true;
8115        });
8116
8117        // Blurring the item saves the file.
8118        item.update_in(cx, |_, window, _| window.blur());
8119        cx.executor().run_until_parked();
8120        item.update(cx, |item, _| assert_eq!(item.save_count, 2));
8121
8122        // Deactivating the window still saves the file.
8123        item.update_in(cx, |item, window, cx| {
8124            cx.focus_self(window);
8125            item.is_dirty = true;
8126        });
8127        cx.deactivate_window();
8128        item.update(cx, |item, _| assert_eq!(item.save_count, 3));
8129
8130        // Autosave after delay.
8131        item.update(cx, |item, cx| {
8132            SettingsStore::update_global(cx, |settings, cx| {
8133                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
8134                    settings.autosave = Some(AutosaveSetting::AfterDelay { milliseconds: 500 });
8135                })
8136            });
8137            item.is_dirty = true;
8138            cx.emit(ItemEvent::Edit);
8139        });
8140
8141        // Delay hasn't fully expired, so the file is still dirty and unsaved.
8142        cx.executor().advance_clock(Duration::from_millis(250));
8143        item.update(cx, |item, _| assert_eq!(item.save_count, 3));
8144
8145        // After delay expires, the file is saved.
8146        cx.executor().advance_clock(Duration::from_millis(250));
8147        item.update(cx, |item, _| assert_eq!(item.save_count, 4));
8148
8149        // Autosave on focus change, ensuring closing the tab counts as such.
8150        item.update(cx, |item, cx| {
8151            SettingsStore::update_global(cx, |settings, cx| {
8152                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
8153                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
8154                })
8155            });
8156            item.is_dirty = true;
8157            for project_item in &mut item.project_items {
8158                project_item.update(cx, |project_item, _| project_item.is_dirty = true);
8159            }
8160        });
8161
8162        pane.update_in(cx, |pane, window, cx| {
8163            pane.close_items(window, cx, SaveIntent::Close, move |id| id == item_id)
8164        })
8165        .await
8166        .unwrap();
8167        assert!(!cx.has_pending_prompt());
8168        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
8169
8170        // Add the item again, ensuring autosave is prevented if the underlying file has been deleted.
8171        workspace.update_in(cx, |workspace, window, cx| {
8172            workspace.add_item_to_active_pane(Box::new(item.clone()), None, true, window, cx);
8173        });
8174        item.update_in(cx, |item, window, cx| {
8175            item.project_items[0].update(cx, |item, _| {
8176                item.entry_id = None;
8177            });
8178            item.is_dirty = true;
8179            window.blur();
8180        });
8181        cx.run_until_parked();
8182        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
8183
8184        // Ensure autosave is prevented for deleted files also when closing the buffer.
8185        let _close_items = pane.update_in(cx, |pane, window, cx| {
8186            pane.close_items(window, cx, SaveIntent::Close, move |id| id == item_id)
8187        });
8188        cx.run_until_parked();
8189        assert!(cx.has_pending_prompt());
8190        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
8191    }
8192
8193    #[gpui::test]
8194    async fn test_pane_navigation(cx: &mut gpui::TestAppContext) {
8195        init_test(cx);
8196
8197        let fs = FakeFs::new(cx.executor());
8198
8199        let project = Project::test(fs, [], cx).await;
8200        let (workspace, cx) =
8201            cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
8202
8203        let item = cx.new(|cx| {
8204            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
8205        });
8206        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
8207        let toolbar = pane.update(cx, |pane, _| pane.toolbar().clone());
8208        let toolbar_notify_count = Rc::new(RefCell::new(0));
8209
8210        workspace.update_in(cx, |workspace, window, cx| {
8211            workspace.add_item_to_active_pane(Box::new(item.clone()), None, true, window, cx);
8212            let toolbar_notification_count = toolbar_notify_count.clone();
8213            cx.observe_in(&toolbar, window, move |_, _, _, _| {
8214                *toolbar_notification_count.borrow_mut() += 1
8215            })
8216            .detach();
8217        });
8218
8219        pane.update(cx, |pane, _| {
8220            assert!(!pane.can_navigate_backward());
8221            assert!(!pane.can_navigate_forward());
8222        });
8223
8224        item.update_in(cx, |item, _, cx| {
8225            item.set_state("one".to_string(), cx);
8226        });
8227
8228        // Toolbar must be notified to re-render the navigation buttons
8229        assert_eq!(*toolbar_notify_count.borrow(), 1);
8230
8231        pane.update(cx, |pane, _| {
8232            assert!(pane.can_navigate_backward());
8233            assert!(!pane.can_navigate_forward());
8234        });
8235
8236        workspace
8237            .update_in(cx, |workspace, window, cx| {
8238                workspace.go_back(pane.downgrade(), window, cx)
8239            })
8240            .await
8241            .unwrap();
8242
8243        assert_eq!(*toolbar_notify_count.borrow(), 2);
8244        pane.update(cx, |pane, _| {
8245            assert!(!pane.can_navigate_backward());
8246            assert!(pane.can_navigate_forward());
8247        });
8248    }
8249
8250    #[gpui::test]
8251    async fn test_toggle_docks_and_panels(cx: &mut gpui::TestAppContext) {
8252        init_test(cx);
8253        let fs = FakeFs::new(cx.executor());
8254
8255        let project = Project::test(fs, [], cx).await;
8256        let (workspace, cx) =
8257            cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
8258
8259        let panel = workspace.update_in(cx, |workspace, window, cx| {
8260            let panel = cx.new(|cx| TestPanel::new(DockPosition::Right, cx));
8261            workspace.add_panel(panel.clone(), window, cx);
8262
8263            workspace
8264                .right_dock()
8265                .update(cx, |right_dock, cx| right_dock.set_open(true, window, cx));
8266
8267            panel
8268        });
8269
8270        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
8271        pane.update_in(cx, |pane, window, cx| {
8272            let item = cx.new(TestItem::new);
8273            pane.add_item(Box::new(item), true, true, None, window, cx);
8274        });
8275
8276        // Transfer focus from center to panel
8277        workspace.update_in(cx, |workspace, window, cx| {
8278            workspace.toggle_panel_focus::<TestPanel>(window, cx);
8279        });
8280
8281        workspace.update_in(cx, |workspace, window, cx| {
8282            assert!(workspace.right_dock().read(cx).is_open());
8283            assert!(!panel.is_zoomed(window, cx));
8284            assert!(panel.read(cx).focus_handle(cx).contains_focused(window, cx));
8285        });
8286
8287        // Transfer focus from panel to center
8288        workspace.update_in(cx, |workspace, window, cx| {
8289            workspace.toggle_panel_focus::<TestPanel>(window, cx);
8290        });
8291
8292        workspace.update_in(cx, |workspace, window, cx| {
8293            assert!(workspace.right_dock().read(cx).is_open());
8294            assert!(!panel.is_zoomed(window, cx));
8295            assert!(!panel.read(cx).focus_handle(cx).contains_focused(window, cx));
8296        });
8297
8298        // Close the dock
8299        workspace.update_in(cx, |workspace, window, cx| {
8300            workspace.toggle_dock(DockPosition::Right, window, cx);
8301        });
8302
8303        workspace.update_in(cx, |workspace, window, cx| {
8304            assert!(!workspace.right_dock().read(cx).is_open());
8305            assert!(!panel.is_zoomed(window, cx));
8306            assert!(!panel.read(cx).focus_handle(cx).contains_focused(window, cx));
8307        });
8308
8309        // Open the dock
8310        workspace.update_in(cx, |workspace, window, cx| {
8311            workspace.toggle_dock(DockPosition::Right, window, cx);
8312        });
8313
8314        workspace.update_in(cx, |workspace, window, cx| {
8315            assert!(workspace.right_dock().read(cx).is_open());
8316            assert!(!panel.is_zoomed(window, cx));
8317            assert!(panel.read(cx).focus_handle(cx).contains_focused(window, cx));
8318        });
8319
8320        // Focus and zoom panel
8321        panel.update_in(cx, |panel, window, cx| {
8322            cx.focus_self(window);
8323            panel.set_zoomed(true, window, cx)
8324        });
8325
8326        workspace.update_in(cx, |workspace, window, cx| {
8327            assert!(workspace.right_dock().read(cx).is_open());
8328            assert!(panel.is_zoomed(window, cx));
8329            assert!(panel.read(cx).focus_handle(cx).contains_focused(window, cx));
8330        });
8331
8332        // Transfer focus to the center closes the dock
8333        workspace.update_in(cx, |workspace, window, cx| {
8334            workspace.toggle_panel_focus::<TestPanel>(window, cx);
8335        });
8336
8337        workspace.update_in(cx, |workspace, window, cx| {
8338            assert!(!workspace.right_dock().read(cx).is_open());
8339            assert!(panel.is_zoomed(window, cx));
8340            assert!(!panel.read(cx).focus_handle(cx).contains_focused(window, cx));
8341        });
8342
8343        // Transferring focus back to the panel keeps it zoomed
8344        workspace.update_in(cx, |workspace, window, cx| {
8345            workspace.toggle_panel_focus::<TestPanel>(window, cx);
8346        });
8347
8348        workspace.update_in(cx, |workspace, window, cx| {
8349            assert!(workspace.right_dock().read(cx).is_open());
8350            assert!(panel.is_zoomed(window, cx));
8351            assert!(panel.read(cx).focus_handle(cx).contains_focused(window, cx));
8352        });
8353
8354        // Close the dock while it is zoomed
8355        workspace.update_in(cx, |workspace, window, cx| {
8356            workspace.toggle_dock(DockPosition::Right, window, cx)
8357        });
8358
8359        workspace.update_in(cx, |workspace, window, cx| {
8360            assert!(!workspace.right_dock().read(cx).is_open());
8361            assert!(panel.is_zoomed(window, cx));
8362            assert!(workspace.zoomed.is_none());
8363            assert!(!panel.read(cx).focus_handle(cx).contains_focused(window, cx));
8364        });
8365
8366        // Opening the dock, when it's zoomed, retains focus
8367        workspace.update_in(cx, |workspace, window, cx| {
8368            workspace.toggle_dock(DockPosition::Right, window, cx)
8369        });
8370
8371        workspace.update_in(cx, |workspace, window, cx| {
8372            assert!(workspace.right_dock().read(cx).is_open());
8373            assert!(panel.is_zoomed(window, cx));
8374            assert!(workspace.zoomed.is_some());
8375            assert!(panel.read(cx).focus_handle(cx).contains_focused(window, cx));
8376        });
8377
8378        // Unzoom and close the panel, zoom the active pane.
8379        panel.update_in(cx, |panel, window, cx| panel.set_zoomed(false, window, cx));
8380        workspace.update_in(cx, |workspace, window, cx| {
8381            workspace.toggle_dock(DockPosition::Right, window, cx)
8382        });
8383        pane.update_in(cx, |pane, window, cx| {
8384            pane.toggle_zoom(&Default::default(), window, cx)
8385        });
8386
8387        // Opening a dock unzooms the pane.
8388        workspace.update_in(cx, |workspace, window, cx| {
8389            workspace.toggle_dock(DockPosition::Right, window, cx)
8390        });
8391        workspace.update_in(cx, |workspace, window, cx| {
8392            let pane = pane.read(cx);
8393            assert!(!pane.is_zoomed());
8394            assert!(!pane.focus_handle(cx).is_focused(window));
8395            assert!(workspace.right_dock().read(cx).is_open());
8396            assert!(workspace.zoomed.is_none());
8397        });
8398    }
8399
8400    #[gpui::test]
8401    async fn test_join_pane_into_next(cx: &mut gpui::TestAppContext) {
8402        init_test(cx);
8403
8404        let fs = FakeFs::new(cx.executor());
8405
8406        let project = Project::test(fs, None, cx).await;
8407        let (workspace, cx) =
8408            cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
8409
8410        // Let's arrange the panes like this:
8411        //
8412        // +-----------------------+
8413        // |         top           |
8414        // +------+--------+-------+
8415        // | left | center | right |
8416        // +------+--------+-------+
8417        // |        bottom         |
8418        // +-----------------------+
8419
8420        let top_item = cx.new(|cx| {
8421            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "top.txt", cx)])
8422        });
8423        let bottom_item = cx.new(|cx| {
8424            TestItem::new(cx).with_project_items(&[TestProjectItem::new(2, "bottom.txt", cx)])
8425        });
8426        let left_item = cx.new(|cx| {
8427            TestItem::new(cx).with_project_items(&[TestProjectItem::new(3, "left.txt", cx)])
8428        });
8429        let right_item = cx.new(|cx| {
8430            TestItem::new(cx).with_project_items(&[TestProjectItem::new(4, "right.txt", cx)])
8431        });
8432        let center_item = cx.new(|cx| {
8433            TestItem::new(cx).with_project_items(&[TestProjectItem::new(5, "center.txt", cx)])
8434        });
8435
8436        let top_pane_id = workspace.update_in(cx, |workspace, window, cx| {
8437            let top_pane_id = workspace.active_pane().entity_id();
8438            workspace.add_item_to_active_pane(Box::new(top_item.clone()), None, false, window, cx);
8439            workspace.split_pane(
8440                workspace.active_pane().clone(),
8441                SplitDirection::Down,
8442                window,
8443                cx,
8444            );
8445            top_pane_id
8446        });
8447        let bottom_pane_id = workspace.update_in(cx, |workspace, window, cx| {
8448            let bottom_pane_id = workspace.active_pane().entity_id();
8449            workspace.add_item_to_active_pane(
8450                Box::new(bottom_item.clone()),
8451                None,
8452                false,
8453                window,
8454                cx,
8455            );
8456            workspace.split_pane(
8457                workspace.active_pane().clone(),
8458                SplitDirection::Up,
8459                window,
8460                cx,
8461            );
8462            bottom_pane_id
8463        });
8464        let left_pane_id = workspace.update_in(cx, |workspace, window, cx| {
8465            let left_pane_id = workspace.active_pane().entity_id();
8466            workspace.add_item_to_active_pane(Box::new(left_item.clone()), None, false, window, cx);
8467            workspace.split_pane(
8468                workspace.active_pane().clone(),
8469                SplitDirection::Right,
8470                window,
8471                cx,
8472            );
8473            left_pane_id
8474        });
8475        let right_pane_id = workspace.update_in(cx, |workspace, window, cx| {
8476            let right_pane_id = workspace.active_pane().entity_id();
8477            workspace.add_item_to_active_pane(
8478                Box::new(right_item.clone()),
8479                None,
8480                false,
8481                window,
8482                cx,
8483            );
8484            workspace.split_pane(
8485                workspace.active_pane().clone(),
8486                SplitDirection::Left,
8487                window,
8488                cx,
8489            );
8490            right_pane_id
8491        });
8492        let center_pane_id = workspace.update_in(cx, |workspace, window, cx| {
8493            let center_pane_id = workspace.active_pane().entity_id();
8494            workspace.add_item_to_active_pane(
8495                Box::new(center_item.clone()),
8496                None,
8497                false,
8498                window,
8499                cx,
8500            );
8501            center_pane_id
8502        });
8503        cx.executor().run_until_parked();
8504
8505        workspace.update_in(cx, |workspace, window, cx| {
8506            assert_eq!(center_pane_id, workspace.active_pane().entity_id());
8507
8508            // Join into next from center pane into right
8509            workspace.join_pane_into_next(workspace.active_pane().clone(), window, cx);
8510        });
8511
8512        workspace.update_in(cx, |workspace, window, cx| {
8513            let active_pane = workspace.active_pane();
8514            assert_eq!(right_pane_id, active_pane.entity_id());
8515            assert_eq!(2, active_pane.read(cx).items_len());
8516            let item_ids_in_pane =
8517                HashSet::from_iter(active_pane.read(cx).items().map(|item| item.item_id()));
8518            assert!(item_ids_in_pane.contains(&center_item.item_id()));
8519            assert!(item_ids_in_pane.contains(&right_item.item_id()));
8520
8521            // Join into next from right pane into bottom
8522            workspace.join_pane_into_next(workspace.active_pane().clone(), window, cx);
8523        });
8524
8525        workspace.update_in(cx, |workspace, window, cx| {
8526            let active_pane = workspace.active_pane();
8527            assert_eq!(bottom_pane_id, active_pane.entity_id());
8528            assert_eq!(3, active_pane.read(cx).items_len());
8529            let item_ids_in_pane =
8530                HashSet::from_iter(active_pane.read(cx).items().map(|item| item.item_id()));
8531            assert!(item_ids_in_pane.contains(&center_item.item_id()));
8532            assert!(item_ids_in_pane.contains(&right_item.item_id()));
8533            assert!(item_ids_in_pane.contains(&bottom_item.item_id()));
8534
8535            // Join into next from bottom pane into left
8536            workspace.join_pane_into_next(workspace.active_pane().clone(), window, cx);
8537        });
8538
8539        workspace.update_in(cx, |workspace, window, cx| {
8540            let active_pane = workspace.active_pane();
8541            assert_eq!(left_pane_id, active_pane.entity_id());
8542            assert_eq!(4, active_pane.read(cx).items_len());
8543            let item_ids_in_pane =
8544                HashSet::from_iter(active_pane.read(cx).items().map(|item| item.item_id()));
8545            assert!(item_ids_in_pane.contains(&center_item.item_id()));
8546            assert!(item_ids_in_pane.contains(&right_item.item_id()));
8547            assert!(item_ids_in_pane.contains(&bottom_item.item_id()));
8548            assert!(item_ids_in_pane.contains(&left_item.item_id()));
8549
8550            // Join into next from left pane into top
8551            workspace.join_pane_into_next(workspace.active_pane().clone(), window, cx);
8552        });
8553
8554        workspace.update_in(cx, |workspace, window, cx| {
8555            let active_pane = workspace.active_pane();
8556            assert_eq!(top_pane_id, active_pane.entity_id());
8557            assert_eq!(5, active_pane.read(cx).items_len());
8558            let item_ids_in_pane =
8559                HashSet::from_iter(active_pane.read(cx).items().map(|item| item.item_id()));
8560            assert!(item_ids_in_pane.contains(&center_item.item_id()));
8561            assert!(item_ids_in_pane.contains(&right_item.item_id()));
8562            assert!(item_ids_in_pane.contains(&bottom_item.item_id()));
8563            assert!(item_ids_in_pane.contains(&left_item.item_id()));
8564            assert!(item_ids_in_pane.contains(&top_item.item_id()));
8565
8566            // Single pane left: no-op
8567            workspace.join_pane_into_next(workspace.active_pane().clone(), window, cx)
8568        });
8569
8570        workspace.update(cx, |workspace, _cx| {
8571            let active_pane = workspace.active_pane();
8572            assert_eq!(top_pane_id, active_pane.entity_id());
8573        });
8574    }
8575
8576    fn add_an_item_to_active_pane(
8577        cx: &mut VisualTestContext,
8578        workspace: &Entity<Workspace>,
8579        item_id: u64,
8580    ) -> Entity<TestItem> {
8581        let item = cx.new(|cx| {
8582            TestItem::new(cx).with_project_items(&[TestProjectItem::new(
8583                item_id,
8584                "item{item_id}.txt",
8585                cx,
8586            )])
8587        });
8588        workspace.update_in(cx, |workspace, window, cx| {
8589            workspace.add_item_to_active_pane(Box::new(item.clone()), None, false, window, cx);
8590        });
8591        return item;
8592    }
8593
8594    fn split_pane(cx: &mut VisualTestContext, workspace: &Entity<Workspace>) -> Entity<Pane> {
8595        return workspace.update_in(cx, |workspace, window, cx| {
8596            let new_pane = workspace.split_pane(
8597                workspace.active_pane().clone(),
8598                SplitDirection::Right,
8599                window,
8600                cx,
8601            );
8602            new_pane
8603        });
8604    }
8605
8606    #[gpui::test]
8607    async fn test_join_all_panes(cx: &mut gpui::TestAppContext) {
8608        init_test(cx);
8609        let fs = FakeFs::new(cx.executor());
8610        let project = Project::test(fs, None, cx).await;
8611        let (workspace, cx) =
8612            cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
8613
8614        add_an_item_to_active_pane(cx, &workspace, 1);
8615        split_pane(cx, &workspace);
8616        add_an_item_to_active_pane(cx, &workspace, 2);
8617        split_pane(cx, &workspace); // empty pane
8618        split_pane(cx, &workspace);
8619        let last_item = add_an_item_to_active_pane(cx, &workspace, 3);
8620
8621        cx.executor().run_until_parked();
8622
8623        workspace.update(cx, |workspace, cx| {
8624            let num_panes = workspace.panes().len();
8625            let num_items_in_current_pane = workspace.active_pane().read(cx).items().count();
8626            let active_item = workspace
8627                .active_pane()
8628                .read(cx)
8629                .active_item()
8630                .expect("item is in focus");
8631
8632            assert_eq!(num_panes, 4);
8633            assert_eq!(num_items_in_current_pane, 1);
8634            assert_eq!(active_item.item_id(), last_item.item_id());
8635        });
8636
8637        workspace.update_in(cx, |workspace, window, cx| {
8638            workspace.join_all_panes(window, cx);
8639        });
8640
8641        workspace.update(cx, |workspace, cx| {
8642            let num_panes = workspace.panes().len();
8643            let num_items_in_current_pane = workspace.active_pane().read(cx).items().count();
8644            let active_item = workspace
8645                .active_pane()
8646                .read(cx)
8647                .active_item()
8648                .expect("item is in focus");
8649
8650            assert_eq!(num_panes, 1);
8651            assert_eq!(num_items_in_current_pane, 3);
8652            assert_eq!(active_item.item_id(), last_item.item_id());
8653        });
8654    }
8655    struct TestModal(FocusHandle);
8656
8657    impl TestModal {
8658        fn new(_: &mut Window, cx: &mut Context<Self>) -> Self {
8659            Self(cx.focus_handle())
8660        }
8661    }
8662
8663    impl EventEmitter<DismissEvent> for TestModal {}
8664
8665    impl Focusable for TestModal {
8666        fn focus_handle(&self, _cx: &App) -> FocusHandle {
8667            self.0.clone()
8668        }
8669    }
8670
8671    impl ModalView for TestModal {}
8672
8673    impl Render for TestModal {
8674        fn render(
8675            &mut self,
8676            _window: &mut Window,
8677            _cx: &mut Context<TestModal>,
8678        ) -> impl IntoElement {
8679            div().track_focus(&self.0)
8680        }
8681    }
8682
8683    #[gpui::test]
8684    async fn test_panels(cx: &mut gpui::TestAppContext) {
8685        init_test(cx);
8686        let fs = FakeFs::new(cx.executor());
8687
8688        let project = Project::test(fs, [], cx).await;
8689        let (workspace, cx) =
8690            cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
8691
8692        let (panel_1, panel_2) = workspace.update_in(cx, |workspace, window, cx| {
8693            let panel_1 = cx.new(|cx| TestPanel::new(DockPosition::Left, cx));
8694            workspace.add_panel(panel_1.clone(), window, cx);
8695            workspace.toggle_dock(DockPosition::Left, window, cx);
8696            let panel_2 = cx.new(|cx| TestPanel::new(DockPosition::Right, cx));
8697            workspace.add_panel(panel_2.clone(), window, cx);
8698            workspace.toggle_dock(DockPosition::Right, window, cx);
8699
8700            let left_dock = workspace.left_dock();
8701            assert_eq!(
8702                left_dock.read(cx).visible_panel().unwrap().panel_id(),
8703                panel_1.panel_id()
8704            );
8705            assert_eq!(
8706                left_dock.read(cx).active_panel_size(window, cx).unwrap(),
8707                panel_1.size(window, cx)
8708            );
8709
8710            left_dock.update(cx, |left_dock, cx| {
8711                left_dock.resize_active_panel(Some(px(1337.)), window, cx)
8712            });
8713            assert_eq!(
8714                workspace
8715                    .right_dock()
8716                    .read(cx)
8717                    .visible_panel()
8718                    .unwrap()
8719                    .panel_id(),
8720                panel_2.panel_id(),
8721            );
8722
8723            (panel_1, panel_2)
8724        });
8725
8726        // Move panel_1 to the right
8727        panel_1.update_in(cx, |panel_1, window, cx| {
8728            panel_1.set_position(DockPosition::Right, window, cx)
8729        });
8730
8731        workspace.update_in(cx, |workspace, window, cx| {
8732            // Since panel_1 was visible on the left, it should now be visible now that it's been moved to the right.
8733            // Since it was the only panel on the left, the left dock should now be closed.
8734            assert!(!workspace.left_dock().read(cx).is_open());
8735            assert!(workspace.left_dock().read(cx).visible_panel().is_none());
8736            let right_dock = workspace.right_dock();
8737            assert_eq!(
8738                right_dock.read(cx).visible_panel().unwrap().panel_id(),
8739                panel_1.panel_id()
8740            );
8741            assert_eq!(
8742                right_dock.read(cx).active_panel_size(window, cx).unwrap(),
8743                px(1337.)
8744            );
8745
8746            // Now we move panel_2 to the left
8747            panel_2.set_position(DockPosition::Left, window, cx);
8748        });
8749
8750        workspace.update(cx, |workspace, cx| {
8751            // Since panel_2 was not visible on the right, we don't open the left dock.
8752            assert!(!workspace.left_dock().read(cx).is_open());
8753            // And the right dock is unaffected in its displaying of panel_1
8754            assert!(workspace.right_dock().read(cx).is_open());
8755            assert_eq!(
8756                workspace
8757                    .right_dock()
8758                    .read(cx)
8759                    .visible_panel()
8760                    .unwrap()
8761                    .panel_id(),
8762                panel_1.panel_id(),
8763            );
8764        });
8765
8766        // Move panel_1 back to the left
8767        panel_1.update_in(cx, |panel_1, window, cx| {
8768            panel_1.set_position(DockPosition::Left, window, cx)
8769        });
8770
8771        workspace.update_in(cx, |workspace, window, cx| {
8772            // Since panel_1 was visible on the right, we open the left dock and make panel_1 active.
8773            let left_dock = workspace.left_dock();
8774            assert!(left_dock.read(cx).is_open());
8775            assert_eq!(
8776                left_dock.read(cx).visible_panel().unwrap().panel_id(),
8777                panel_1.panel_id()
8778            );
8779            assert_eq!(
8780                left_dock.read(cx).active_panel_size(window, cx).unwrap(),
8781                px(1337.)
8782            );
8783            // And the right dock should be closed as it no longer has any panels.
8784            assert!(!workspace.right_dock().read(cx).is_open());
8785
8786            // Now we move panel_1 to the bottom
8787            panel_1.set_position(DockPosition::Bottom, window, cx);
8788        });
8789
8790        workspace.update_in(cx, |workspace, window, cx| {
8791            // Since panel_1 was visible on the left, we close the left dock.
8792            assert!(!workspace.left_dock().read(cx).is_open());
8793            // The bottom dock is sized based on the panel's default size,
8794            // since the panel orientation changed from vertical to horizontal.
8795            let bottom_dock = workspace.bottom_dock();
8796            assert_eq!(
8797                bottom_dock.read(cx).active_panel_size(window, cx).unwrap(),
8798                panel_1.size(window, cx),
8799            );
8800            // Close bottom dock and move panel_1 back to the left.
8801            bottom_dock.update(cx, |bottom_dock, cx| {
8802                bottom_dock.set_open(false, window, cx)
8803            });
8804            panel_1.set_position(DockPosition::Left, window, cx);
8805        });
8806
8807        // Emit activated event on panel 1
8808        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::Activate));
8809
8810        // Now the left dock is open and panel_1 is active and focused.
8811        workspace.update_in(cx, |workspace, window, cx| {
8812            let left_dock = workspace.left_dock();
8813            assert!(left_dock.read(cx).is_open());
8814            assert_eq!(
8815                left_dock.read(cx).visible_panel().unwrap().panel_id(),
8816                panel_1.panel_id(),
8817            );
8818            assert!(panel_1.focus_handle(cx).is_focused(window));
8819        });
8820
8821        // Emit closed event on panel 2, which is not active
8822        panel_2.update(cx, |_, cx| cx.emit(PanelEvent::Close));
8823
8824        // Wo don't close the left dock, because panel_2 wasn't the active panel
8825        workspace.update(cx, |workspace, cx| {
8826            let left_dock = workspace.left_dock();
8827            assert!(left_dock.read(cx).is_open());
8828            assert_eq!(
8829                left_dock.read(cx).visible_panel().unwrap().panel_id(),
8830                panel_1.panel_id(),
8831            );
8832        });
8833
8834        // Emitting a ZoomIn event shows the panel as zoomed.
8835        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::ZoomIn));
8836        workspace.update(cx, |workspace, _| {
8837            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
8838            assert_eq!(workspace.zoomed_position, Some(DockPosition::Left));
8839        });
8840
8841        // Move panel to another dock while it is zoomed
8842        panel_1.update_in(cx, |panel, window, cx| {
8843            panel.set_position(DockPosition::Right, window, cx)
8844        });
8845        workspace.update(cx, |workspace, _| {
8846            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
8847
8848            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
8849        });
8850
8851        // This is a helper for getting a:
8852        // - valid focus on an element,
8853        // - that isn't a part of the panes and panels system of the Workspace,
8854        // - and doesn't trigger the 'on_focus_lost' API.
8855        let focus_other_view = {
8856            let workspace = workspace.clone();
8857            move |cx: &mut VisualTestContext| {
8858                workspace.update_in(cx, |workspace, window, cx| {
8859                    if let Some(_) = workspace.active_modal::<TestModal>(cx) {
8860                        workspace.toggle_modal(window, cx, TestModal::new);
8861                        workspace.toggle_modal(window, cx, TestModal::new);
8862                    } else {
8863                        workspace.toggle_modal(window, cx, TestModal::new);
8864                    }
8865                })
8866            }
8867        };
8868
8869        // If focus is transferred to another view that's not a panel or another pane, we still show
8870        // the panel as zoomed.
8871        focus_other_view(cx);
8872        workspace.update(cx, |workspace, _| {
8873            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
8874            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
8875        });
8876
8877        // If focus is transferred elsewhere in the workspace, the panel is no longer zoomed.
8878        workspace.update_in(cx, |_workspace, window, cx| {
8879            cx.focus_self(window);
8880        });
8881        workspace.update(cx, |workspace, _| {
8882            assert_eq!(workspace.zoomed, None);
8883            assert_eq!(workspace.zoomed_position, None);
8884        });
8885
8886        // If focus is transferred again to another view that's not a panel or a pane, we won't
8887        // show the panel as zoomed because it wasn't zoomed before.
8888        focus_other_view(cx);
8889        workspace.update(cx, |workspace, _| {
8890            assert_eq!(workspace.zoomed, None);
8891            assert_eq!(workspace.zoomed_position, None);
8892        });
8893
8894        // When the panel is activated, it is zoomed again.
8895        cx.dispatch_action(ToggleRightDock);
8896        workspace.update(cx, |workspace, _| {
8897            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
8898            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
8899        });
8900
8901        // Emitting a ZoomOut event unzooms the panel.
8902        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::ZoomOut));
8903        workspace.update(cx, |workspace, _| {
8904            assert_eq!(workspace.zoomed, None);
8905            assert_eq!(workspace.zoomed_position, None);
8906        });
8907
8908        // Emit closed event on panel 1, which is active
8909        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::Close));
8910
8911        // Now the left dock is closed, because panel_1 was the active panel
8912        workspace.update(cx, |workspace, cx| {
8913            let right_dock = workspace.right_dock();
8914            assert!(!right_dock.read(cx).is_open());
8915        });
8916    }
8917
8918    #[gpui::test]
8919    async fn test_no_save_prompt_when_multi_buffer_dirty_items_closed(cx: &mut TestAppContext) {
8920        init_test(cx);
8921
8922        let fs = FakeFs::new(cx.background_executor.clone());
8923        let project = Project::test(fs, [], cx).await;
8924        let (workspace, cx) =
8925            cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
8926        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
8927
8928        let dirty_regular_buffer = cx.new(|cx| {
8929            TestItem::new(cx)
8930                .with_dirty(true)
8931                .with_label("1.txt")
8932                .with_project_items(&[dirty_project_item(1, "1.txt", cx)])
8933        });
8934        let dirty_regular_buffer_2 = cx.new(|cx| {
8935            TestItem::new(cx)
8936                .with_dirty(true)
8937                .with_label("2.txt")
8938                .with_project_items(&[dirty_project_item(2, "2.txt", cx)])
8939        });
8940        let dirty_multi_buffer_with_both = cx.new(|cx| {
8941            TestItem::new(cx)
8942                .with_dirty(true)
8943                .with_singleton(false)
8944                .with_label("Fake Project Search")
8945                .with_project_items(&[
8946                    dirty_regular_buffer.read(cx).project_items[0].clone(),
8947                    dirty_regular_buffer_2.read(cx).project_items[0].clone(),
8948                ])
8949        });
8950        let multi_buffer_with_both_files_id = dirty_multi_buffer_with_both.item_id();
8951        workspace.update_in(cx, |workspace, window, cx| {
8952            workspace.add_item(
8953                pane.clone(),
8954                Box::new(dirty_regular_buffer.clone()),
8955                None,
8956                false,
8957                false,
8958                window,
8959                cx,
8960            );
8961            workspace.add_item(
8962                pane.clone(),
8963                Box::new(dirty_regular_buffer_2.clone()),
8964                None,
8965                false,
8966                false,
8967                window,
8968                cx,
8969            );
8970            workspace.add_item(
8971                pane.clone(),
8972                Box::new(dirty_multi_buffer_with_both.clone()),
8973                None,
8974                false,
8975                false,
8976                window,
8977                cx,
8978            );
8979        });
8980
8981        pane.update_in(cx, |pane, window, cx| {
8982            pane.activate_item(2, true, true, window, cx);
8983            assert_eq!(
8984                pane.active_item().unwrap().item_id(),
8985                multi_buffer_with_both_files_id,
8986                "Should select the multi buffer in the pane"
8987            );
8988        });
8989        let close_all_but_multi_buffer_task = pane
8990            .update_in(cx, |pane, window, cx| {
8991                pane.close_inactive_items(
8992                    &CloseInactiveItems {
8993                        save_intent: Some(SaveIntent::Save),
8994                        close_pinned: true,
8995                    },
8996                    window,
8997                    cx,
8998                )
8999            })
9000            .expect("should have inactive files to close");
9001        cx.background_executor.run_until_parked();
9002        assert!(!cx.has_pending_prompt());
9003        close_all_but_multi_buffer_task
9004            .await
9005            .expect("Closing all buffers but the multi buffer failed");
9006        pane.update(cx, |pane, cx| {
9007            assert_eq!(dirty_regular_buffer.read(cx).save_count, 1);
9008            assert_eq!(dirty_multi_buffer_with_both.read(cx).save_count, 0);
9009            assert_eq!(dirty_regular_buffer_2.read(cx).save_count, 1);
9010            assert_eq!(pane.items_len(), 1);
9011            assert_eq!(
9012                pane.active_item().unwrap().item_id(),
9013                multi_buffer_with_both_files_id,
9014                "Should have only the multi buffer left in the pane"
9015            );
9016            assert!(
9017                dirty_multi_buffer_with_both.read(cx).is_dirty,
9018                "The multi buffer containing the unsaved buffer should still be dirty"
9019            );
9020        });
9021
9022        dirty_regular_buffer.update(cx, |buffer, cx| {
9023            buffer.project_items[0].update(cx, |pi, _| pi.is_dirty = true)
9024        });
9025
9026        let close_multi_buffer_task = pane
9027            .update_in(cx, |pane, window, cx| {
9028                pane.close_active_item(
9029                    &CloseActiveItem {
9030                        save_intent: Some(SaveIntent::Close),
9031                        close_pinned: false,
9032                    },
9033                    window,
9034                    cx,
9035                )
9036            })
9037            .expect("should have the multi buffer to close");
9038        cx.background_executor.run_until_parked();
9039        assert!(
9040            cx.has_pending_prompt(),
9041            "Dirty multi buffer should prompt a save dialog"
9042        );
9043        cx.simulate_prompt_answer("Save");
9044        cx.background_executor.run_until_parked();
9045        close_multi_buffer_task
9046            .await
9047            .expect("Closing the multi buffer failed");
9048        pane.update(cx, |pane, cx| {
9049            assert_eq!(
9050                dirty_multi_buffer_with_both.read(cx).save_count,
9051                1,
9052                "Multi buffer item should get be saved"
9053            );
9054            // Test impl does not save inner items, so we do not assert them
9055            assert_eq!(
9056                pane.items_len(),
9057                0,
9058                "No more items should be left in the pane"
9059            );
9060            assert!(pane.active_item().is_none());
9061        });
9062    }
9063
9064    #[gpui::test]
9065    async fn test_save_prompt_when_dirty_multi_buffer_closed_with_some_of_its_dirty_items_not_present_in_the_pane(
9066        cx: &mut TestAppContext,
9067    ) {
9068        init_test(cx);
9069
9070        let fs = FakeFs::new(cx.background_executor.clone());
9071        let project = Project::test(fs, [], cx).await;
9072        let (workspace, cx) =
9073            cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
9074        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
9075
9076        let dirty_regular_buffer = cx.new(|cx| {
9077            TestItem::new(cx)
9078                .with_dirty(true)
9079                .with_label("1.txt")
9080                .with_project_items(&[dirty_project_item(1, "1.txt", cx)])
9081        });
9082        let dirty_regular_buffer_2 = cx.new(|cx| {
9083            TestItem::new(cx)
9084                .with_dirty(true)
9085                .with_label("2.txt")
9086                .with_project_items(&[dirty_project_item(2, "2.txt", cx)])
9087        });
9088        let clear_regular_buffer = cx.new(|cx| {
9089            TestItem::new(cx)
9090                .with_label("3.txt")
9091                .with_project_items(&[TestProjectItem::new(3, "3.txt", cx)])
9092        });
9093
9094        let dirty_multi_buffer_with_both = cx.new(|cx| {
9095            TestItem::new(cx)
9096                .with_dirty(true)
9097                .with_singleton(false)
9098                .with_label("Fake Project Search")
9099                .with_project_items(&[
9100                    dirty_regular_buffer.read(cx).project_items[0].clone(),
9101                    dirty_regular_buffer_2.read(cx).project_items[0].clone(),
9102                    clear_regular_buffer.read(cx).project_items[0].clone(),
9103                ])
9104        });
9105        let multi_buffer_with_both_files_id = dirty_multi_buffer_with_both.item_id();
9106        workspace.update_in(cx, |workspace, window, cx| {
9107            workspace.add_item(
9108                pane.clone(),
9109                Box::new(dirty_regular_buffer.clone()),
9110                None,
9111                false,
9112                false,
9113                window,
9114                cx,
9115            );
9116            workspace.add_item(
9117                pane.clone(),
9118                Box::new(dirty_multi_buffer_with_both.clone()),
9119                None,
9120                false,
9121                false,
9122                window,
9123                cx,
9124            );
9125        });
9126
9127        pane.update_in(cx, |pane, window, cx| {
9128            pane.activate_item(1, true, true, window, cx);
9129            assert_eq!(
9130                pane.active_item().unwrap().item_id(),
9131                multi_buffer_with_both_files_id,
9132                "Should select the multi buffer in the pane"
9133            );
9134        });
9135        let _close_multi_buffer_task = pane
9136            .update_in(cx, |pane, window, cx| {
9137                pane.close_active_item(
9138                    &CloseActiveItem {
9139                        save_intent: None,
9140                        close_pinned: false,
9141                    },
9142                    window,
9143                    cx,
9144                )
9145            })
9146            .expect("should have active multi buffer to close");
9147        cx.background_executor.run_until_parked();
9148        assert!(
9149            cx.has_pending_prompt(),
9150            "With one dirty item from the multi buffer not being in the pane, a save prompt should be shown"
9151        );
9152    }
9153
9154    #[gpui::test]
9155    async fn test_no_save_prompt_when_dirty_multi_buffer_closed_with_all_of_its_dirty_items_present_in_the_pane(
9156        cx: &mut TestAppContext,
9157    ) {
9158        init_test(cx);
9159
9160        let fs = FakeFs::new(cx.background_executor.clone());
9161        let project = Project::test(fs, [], cx).await;
9162        let (workspace, cx) =
9163            cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
9164        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
9165
9166        let dirty_regular_buffer = cx.new(|cx| {
9167            TestItem::new(cx)
9168                .with_dirty(true)
9169                .with_label("1.txt")
9170                .with_project_items(&[dirty_project_item(1, "1.txt", cx)])
9171        });
9172        let dirty_regular_buffer_2 = cx.new(|cx| {
9173            TestItem::new(cx)
9174                .with_dirty(true)
9175                .with_label("2.txt")
9176                .with_project_items(&[dirty_project_item(2, "2.txt", cx)])
9177        });
9178        let clear_regular_buffer = cx.new(|cx| {
9179            TestItem::new(cx)
9180                .with_label("3.txt")
9181                .with_project_items(&[TestProjectItem::new(3, "3.txt", cx)])
9182        });
9183
9184        let dirty_multi_buffer = cx.new(|cx| {
9185            TestItem::new(cx)
9186                .with_dirty(true)
9187                .with_singleton(false)
9188                .with_label("Fake Project Search")
9189                .with_project_items(&[
9190                    dirty_regular_buffer.read(cx).project_items[0].clone(),
9191                    dirty_regular_buffer_2.read(cx).project_items[0].clone(),
9192                    clear_regular_buffer.read(cx).project_items[0].clone(),
9193                ])
9194        });
9195        workspace.update_in(cx, |workspace, window, cx| {
9196            workspace.add_item(
9197                pane.clone(),
9198                Box::new(dirty_regular_buffer.clone()),
9199                None,
9200                false,
9201                false,
9202                window,
9203                cx,
9204            );
9205            workspace.add_item(
9206                pane.clone(),
9207                Box::new(dirty_regular_buffer_2.clone()),
9208                None,
9209                false,
9210                false,
9211                window,
9212                cx,
9213            );
9214            workspace.add_item(
9215                pane.clone(),
9216                Box::new(dirty_multi_buffer.clone()),
9217                None,
9218                false,
9219                false,
9220                window,
9221                cx,
9222            );
9223        });
9224
9225        pane.update_in(cx, |pane, window, cx| {
9226            pane.activate_item(2, true, true, window, cx);
9227            assert_eq!(
9228                pane.active_item().unwrap().item_id(),
9229                dirty_multi_buffer.item_id(),
9230                "Should select the multi buffer in the pane"
9231            );
9232        });
9233        let close_multi_buffer_task = pane
9234            .update_in(cx, |pane, window, cx| {
9235                pane.close_active_item(
9236                    &CloseActiveItem {
9237                        save_intent: None,
9238                        close_pinned: false,
9239                    },
9240                    window,
9241                    cx,
9242                )
9243            })
9244            .expect("should have active multi buffer to close");
9245        cx.background_executor.run_until_parked();
9246        assert!(
9247            !cx.has_pending_prompt(),
9248            "All dirty items from the multi buffer are in the pane still, no save prompts should be shown"
9249        );
9250        close_multi_buffer_task
9251            .await
9252            .expect("Closing multi buffer failed");
9253        pane.update(cx, |pane, cx| {
9254            assert_eq!(dirty_regular_buffer.read(cx).save_count, 0);
9255            assert_eq!(dirty_multi_buffer.read(cx).save_count, 0);
9256            assert_eq!(dirty_regular_buffer_2.read(cx).save_count, 0);
9257            assert_eq!(
9258                pane.items()
9259                    .map(|item| item.item_id())
9260                    .sorted()
9261                    .collect::<Vec<_>>(),
9262                vec![
9263                    dirty_regular_buffer.item_id(),
9264                    dirty_regular_buffer_2.item_id(),
9265                ],
9266                "Should have no multi buffer left in the pane"
9267            );
9268            assert!(dirty_regular_buffer.read(cx).is_dirty);
9269            assert!(dirty_regular_buffer_2.read(cx).is_dirty);
9270        });
9271    }
9272
9273    #[gpui::test]
9274    async fn test_move_focused_panel_to_next_position(cx: &mut gpui::TestAppContext) {
9275        init_test(cx);
9276        let fs = FakeFs::new(cx.executor());
9277        let project = Project::test(fs, [], cx).await;
9278        let (workspace, cx) =
9279            cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
9280
9281        // Add a new panel to the right dock, opening the dock and setting the
9282        // focus to the new panel.
9283        let panel = workspace.update_in(cx, |workspace, window, cx| {
9284            let panel = cx.new(|cx| TestPanel::new(DockPosition::Right, cx));
9285            workspace.add_panel(panel.clone(), window, cx);
9286
9287            workspace
9288                .right_dock()
9289                .update(cx, |right_dock, cx| right_dock.set_open(true, window, cx));
9290
9291            workspace.toggle_panel_focus::<TestPanel>(window, cx);
9292
9293            panel
9294        });
9295
9296        // Dispatch the `MoveFocusedPanelToNextPosition` action, moving the
9297        // panel to the next valid position which, in this case, is the left
9298        // dock.
9299        cx.dispatch_action(MoveFocusedPanelToNextPosition);
9300        workspace.update(cx, |workspace, cx| {
9301            assert!(workspace.left_dock().read(cx).is_open());
9302            assert_eq!(panel.read(cx).position, DockPosition::Left);
9303        });
9304
9305        // Dispatch the `MoveFocusedPanelToNextPosition` action, moving the
9306        // panel to the next valid position which, in this case, is the bottom
9307        // dock.
9308        cx.dispatch_action(MoveFocusedPanelToNextPosition);
9309        workspace.update(cx, |workspace, cx| {
9310            assert!(workspace.bottom_dock().read(cx).is_open());
9311            assert_eq!(panel.read(cx).position, DockPosition::Bottom);
9312        });
9313
9314        // Dispatch the `MoveFocusedPanelToNextPosition` action again, this time
9315        // around moving the panel to its initial position, the right dock.
9316        cx.dispatch_action(MoveFocusedPanelToNextPosition);
9317        workspace.update(cx, |workspace, cx| {
9318            assert!(workspace.right_dock().read(cx).is_open());
9319            assert_eq!(panel.read(cx).position, DockPosition::Right);
9320        });
9321
9322        // Remove focus from the panel, ensuring that, if the panel is not
9323        // focused, the `MoveFocusedPanelToNextPosition` action does not update
9324        // the panel's position, so the panel is still in the right dock.
9325        workspace.update_in(cx, |workspace, window, cx| {
9326            workspace.toggle_panel_focus::<TestPanel>(window, cx);
9327        });
9328
9329        cx.dispatch_action(MoveFocusedPanelToNextPosition);
9330        workspace.update(cx, |workspace, cx| {
9331            assert!(workspace.right_dock().read(cx).is_open());
9332            assert_eq!(panel.read(cx).position, DockPosition::Right);
9333        });
9334    }
9335
9336    mod register_project_item_tests {
9337
9338        use super::*;
9339
9340        // View
9341        struct TestPngItemView {
9342            focus_handle: FocusHandle,
9343        }
9344        // Model
9345        struct TestPngItem {}
9346
9347        impl project::ProjectItem for TestPngItem {
9348            fn try_open(
9349                _project: &Entity<Project>,
9350                path: &ProjectPath,
9351                cx: &mut App,
9352            ) -> Option<Task<anyhow::Result<Entity<Self>>>> {
9353                if path.path.extension().unwrap() == "png" {
9354                    Some(cx.spawn(async move |cx| cx.new(|_| TestPngItem {})))
9355                } else {
9356                    None
9357                }
9358            }
9359
9360            fn entry_id(&self, _: &App) -> Option<ProjectEntryId> {
9361                None
9362            }
9363
9364            fn project_path(&self, _: &App) -> Option<ProjectPath> {
9365                None
9366            }
9367
9368            fn is_dirty(&self) -> bool {
9369                false
9370            }
9371        }
9372
9373        impl Item for TestPngItemView {
9374            type Event = ();
9375            fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString {
9376                "".into()
9377            }
9378        }
9379        impl EventEmitter<()> for TestPngItemView {}
9380        impl Focusable for TestPngItemView {
9381            fn focus_handle(&self, _cx: &App) -> FocusHandle {
9382                self.focus_handle.clone()
9383            }
9384        }
9385
9386        impl Render for TestPngItemView {
9387            fn render(
9388                &mut self,
9389                _window: &mut Window,
9390                _cx: &mut Context<Self>,
9391            ) -> impl IntoElement {
9392                Empty
9393            }
9394        }
9395
9396        impl ProjectItem for TestPngItemView {
9397            type Item = TestPngItem;
9398
9399            fn for_project_item(
9400                _project: Entity<Project>,
9401                _pane: Option<&Pane>,
9402                _item: Entity<Self::Item>,
9403                _: &mut Window,
9404                cx: &mut Context<Self>,
9405            ) -> Self
9406            where
9407                Self: Sized,
9408            {
9409                Self {
9410                    focus_handle: cx.focus_handle(),
9411                }
9412            }
9413        }
9414
9415        // View
9416        struct TestIpynbItemView {
9417            focus_handle: FocusHandle,
9418        }
9419        // Model
9420        struct TestIpynbItem {}
9421
9422        impl project::ProjectItem for TestIpynbItem {
9423            fn try_open(
9424                _project: &Entity<Project>,
9425                path: &ProjectPath,
9426                cx: &mut App,
9427            ) -> Option<Task<anyhow::Result<Entity<Self>>>> {
9428                if path.path.extension().unwrap() == "ipynb" {
9429                    Some(cx.spawn(async move |cx| cx.new(|_| TestIpynbItem {})))
9430                } else {
9431                    None
9432                }
9433            }
9434
9435            fn entry_id(&self, _: &App) -> Option<ProjectEntryId> {
9436                None
9437            }
9438
9439            fn project_path(&self, _: &App) -> Option<ProjectPath> {
9440                None
9441            }
9442
9443            fn is_dirty(&self) -> bool {
9444                false
9445            }
9446        }
9447
9448        impl Item for TestIpynbItemView {
9449            type Event = ();
9450            fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString {
9451                "".into()
9452            }
9453        }
9454        impl EventEmitter<()> for TestIpynbItemView {}
9455        impl Focusable for TestIpynbItemView {
9456            fn focus_handle(&self, _cx: &App) -> FocusHandle {
9457                self.focus_handle.clone()
9458            }
9459        }
9460
9461        impl Render for TestIpynbItemView {
9462            fn render(
9463                &mut self,
9464                _window: &mut Window,
9465                _cx: &mut Context<Self>,
9466            ) -> impl IntoElement {
9467                Empty
9468            }
9469        }
9470
9471        impl ProjectItem for TestIpynbItemView {
9472            type Item = TestIpynbItem;
9473
9474            fn for_project_item(
9475                _project: Entity<Project>,
9476                _pane: Option<&Pane>,
9477                _item: Entity<Self::Item>,
9478                _: &mut Window,
9479                cx: &mut Context<Self>,
9480            ) -> Self
9481            where
9482                Self: Sized,
9483            {
9484                Self {
9485                    focus_handle: cx.focus_handle(),
9486                }
9487            }
9488        }
9489
9490        struct TestAlternatePngItemView {
9491            focus_handle: FocusHandle,
9492        }
9493
9494        impl Item for TestAlternatePngItemView {
9495            type Event = ();
9496            fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString {
9497                "".into()
9498            }
9499        }
9500
9501        impl EventEmitter<()> for TestAlternatePngItemView {}
9502        impl Focusable for TestAlternatePngItemView {
9503            fn focus_handle(&self, _cx: &App) -> FocusHandle {
9504                self.focus_handle.clone()
9505            }
9506        }
9507
9508        impl Render for TestAlternatePngItemView {
9509            fn render(
9510                &mut self,
9511                _window: &mut Window,
9512                _cx: &mut Context<Self>,
9513            ) -> impl IntoElement {
9514                Empty
9515            }
9516        }
9517
9518        impl ProjectItem for TestAlternatePngItemView {
9519            type Item = TestPngItem;
9520
9521            fn for_project_item(
9522                _project: Entity<Project>,
9523                _pane: Option<&Pane>,
9524                _item: Entity<Self::Item>,
9525                _: &mut Window,
9526                cx: &mut Context<Self>,
9527            ) -> Self
9528            where
9529                Self: Sized,
9530            {
9531                Self {
9532                    focus_handle: cx.focus_handle(),
9533                }
9534            }
9535        }
9536
9537        #[gpui::test]
9538        async fn test_register_project_item(cx: &mut TestAppContext) {
9539            init_test(cx);
9540
9541            cx.update(|cx| {
9542                register_project_item::<TestPngItemView>(cx);
9543                register_project_item::<TestIpynbItemView>(cx);
9544            });
9545
9546            let fs = FakeFs::new(cx.executor());
9547            fs.insert_tree(
9548                "/root1",
9549                json!({
9550                    "one.png": "BINARYDATAHERE",
9551                    "two.ipynb": "{ totally a notebook }",
9552                    "three.txt": "editing text, sure why not?"
9553                }),
9554            )
9555            .await;
9556
9557            let project = Project::test(fs, ["root1".as_ref()], cx).await;
9558            let (workspace, cx) =
9559                cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
9560
9561            let worktree_id = project.update(cx, |project, cx| {
9562                project.worktrees(cx).next().unwrap().read(cx).id()
9563            });
9564
9565            let handle = workspace
9566                .update_in(cx, |workspace, window, cx| {
9567                    let project_path = (worktree_id, "one.png");
9568                    workspace.open_path(project_path, None, true, window, cx)
9569                })
9570                .await
9571                .unwrap();
9572
9573            // Now we can check if the handle we got back errored or not
9574            assert_eq!(
9575                handle.to_any().entity_type(),
9576                TypeId::of::<TestPngItemView>()
9577            );
9578
9579            let handle = workspace
9580                .update_in(cx, |workspace, window, cx| {
9581                    let project_path = (worktree_id, "two.ipynb");
9582                    workspace.open_path(project_path, None, true, window, cx)
9583                })
9584                .await
9585                .unwrap();
9586
9587            assert_eq!(
9588                handle.to_any().entity_type(),
9589                TypeId::of::<TestIpynbItemView>()
9590            );
9591
9592            let handle = workspace
9593                .update_in(cx, |workspace, window, cx| {
9594                    let project_path = (worktree_id, "three.txt");
9595                    workspace.open_path(project_path, None, true, window, cx)
9596                })
9597                .await;
9598            assert!(handle.is_err());
9599        }
9600
9601        #[gpui::test]
9602        async fn test_register_project_item_two_enter_one_leaves(cx: &mut TestAppContext) {
9603            init_test(cx);
9604
9605            cx.update(|cx| {
9606                register_project_item::<TestPngItemView>(cx);
9607                register_project_item::<TestAlternatePngItemView>(cx);
9608            });
9609
9610            let fs = FakeFs::new(cx.executor());
9611            fs.insert_tree(
9612                "/root1",
9613                json!({
9614                    "one.png": "BINARYDATAHERE",
9615                    "two.ipynb": "{ totally a notebook }",
9616                    "three.txt": "editing text, sure why not?"
9617                }),
9618            )
9619            .await;
9620            let project = Project::test(fs, ["root1".as_ref()], cx).await;
9621            let (workspace, cx) =
9622                cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
9623            let worktree_id = project.update(cx, |project, cx| {
9624                project.worktrees(cx).next().unwrap().read(cx).id()
9625            });
9626
9627            let handle = workspace
9628                .update_in(cx, |workspace, window, cx| {
9629                    let project_path = (worktree_id, "one.png");
9630                    workspace.open_path(project_path, None, true, window, cx)
9631                })
9632                .await
9633                .unwrap();
9634
9635            // This _must_ be the second item registered
9636            assert_eq!(
9637                handle.to_any().entity_type(),
9638                TypeId::of::<TestAlternatePngItemView>()
9639            );
9640
9641            let handle = workspace
9642                .update_in(cx, |workspace, window, cx| {
9643                    let project_path = (worktree_id, "three.txt");
9644                    workspace.open_path(project_path, None, true, window, cx)
9645                })
9646                .await;
9647            assert!(handle.is_err());
9648        }
9649    }
9650
9651    pub fn init_test(cx: &mut TestAppContext) {
9652        cx.update(|cx| {
9653            let settings_store = SettingsStore::test(cx);
9654            cx.set_global(settings_store);
9655            theme::init(theme::LoadThemes::JustBase, cx);
9656            language::init(cx);
9657            crate::init_settings(cx);
9658            Project::init_settings(cx);
9659        });
9660    }
9661
9662    fn dirty_project_item(id: u64, path: &str, cx: &mut App) -> Entity<TestProjectItem> {
9663        let item = TestProjectItem::new(id, path, cx);
9664        item.update(cx, |item, _| {
9665            item.is_dirty = true;
9666        });
9667        item
9668    }
9669}