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