workspace.rs

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