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