workspace.rs

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