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