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    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: Option<SerializedWorkspace> =
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#[cfg(test)]
7449mod tests {
7450    use std::{cell::RefCell, rc::Rc};
7451
7452    use super::*;
7453    use crate::{
7454        dock::{PanelEvent, test::TestPanel},
7455        item::{
7456            ItemEvent,
7457            test::{TestItem, TestProjectItem},
7458        },
7459    };
7460    use fs::FakeFs;
7461    use gpui::{
7462        DismissEvent, Empty, EventEmitter, FocusHandle, Focusable, Render, TestAppContext,
7463        UpdateGlobal, VisualTestContext, px,
7464    };
7465    use project::{Project, ProjectEntryId};
7466    use serde_json::json;
7467    use settings::SettingsStore;
7468
7469    #[gpui::test]
7470    async fn test_tab_disambiguation(cx: &mut TestAppContext) {
7471        init_test(cx);
7472
7473        let fs = FakeFs::new(cx.executor());
7474        let project = Project::test(fs, [], cx).await;
7475        let (workspace, cx) =
7476            cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
7477
7478        // Adding an item with no ambiguity renders the tab without detail.
7479        let item1 = cx.new(|cx| {
7480            let mut item = TestItem::new(cx);
7481            item.tab_descriptions = Some(vec!["c", "b1/c", "a/b1/c"]);
7482            item
7483        });
7484        workspace.update_in(cx, |workspace, window, cx| {
7485            workspace.add_item_to_active_pane(Box::new(item1.clone()), None, true, window, cx);
7486        });
7487        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(0)));
7488
7489        // Adding an item that creates ambiguity increases the level of detail on
7490        // both tabs.
7491        let item2 = cx.new_window_entity(|_window, cx| {
7492            let mut item = TestItem::new(cx);
7493            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
7494            item
7495        });
7496        workspace.update_in(cx, |workspace, window, cx| {
7497            workspace.add_item_to_active_pane(Box::new(item2.clone()), None, true, window, cx);
7498        });
7499        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
7500        item2.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
7501
7502        // Adding an item that creates ambiguity increases the level of detail only
7503        // on the ambiguous tabs. In this case, the ambiguity can't be resolved so
7504        // we stop at the highest detail available.
7505        let item3 = cx.new(|cx| {
7506            let mut item = TestItem::new(cx);
7507            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
7508            item
7509        });
7510        workspace.update_in(cx, |workspace, window, cx| {
7511            workspace.add_item_to_active_pane(Box::new(item3.clone()), None, true, window, cx);
7512        });
7513        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
7514        item2.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
7515        item3.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
7516    }
7517
7518    #[gpui::test]
7519    async fn test_tracking_active_path(cx: &mut TestAppContext) {
7520        init_test(cx);
7521
7522        let fs = FakeFs::new(cx.executor());
7523        fs.insert_tree(
7524            "/root1",
7525            json!({
7526                "one.txt": "",
7527                "two.txt": "",
7528            }),
7529        )
7530        .await;
7531        fs.insert_tree(
7532            "/root2",
7533            json!({
7534                "three.txt": "",
7535            }),
7536        )
7537        .await;
7538
7539        let project = Project::test(fs, ["root1".as_ref()], cx).await;
7540        let (workspace, cx) =
7541            cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
7542        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
7543        let worktree_id = project.update(cx, |project, cx| {
7544            project.worktrees(cx).next().unwrap().read(cx).id()
7545        });
7546
7547        let item1 = cx.new(|cx| {
7548            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "one.txt", cx)])
7549        });
7550        let item2 = cx.new(|cx| {
7551            TestItem::new(cx).with_project_items(&[TestProjectItem::new(2, "two.txt", cx)])
7552        });
7553
7554        // Add an item to an empty pane
7555        workspace.update_in(cx, |workspace, window, cx| {
7556            workspace.add_item_to_active_pane(Box::new(item1), None, true, window, cx)
7557        });
7558        project.update(cx, |project, cx| {
7559            assert_eq!(
7560                project.active_entry(),
7561                project
7562                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
7563                    .map(|e| e.id)
7564            );
7565        });
7566        assert_eq!(cx.window_title().as_deref(), Some("root1 — one.txt"));
7567
7568        // Add a second item to a non-empty pane
7569        workspace.update_in(cx, |workspace, window, cx| {
7570            workspace.add_item_to_active_pane(Box::new(item2), None, true, window, cx)
7571        });
7572        assert_eq!(cx.window_title().as_deref(), Some("root1 — two.txt"));
7573        project.update(cx, |project, cx| {
7574            assert_eq!(
7575                project.active_entry(),
7576                project
7577                    .entry_for_path(&(worktree_id, "two.txt").into(), cx)
7578                    .map(|e| e.id)
7579            );
7580        });
7581
7582        // Close the active item
7583        pane.update_in(cx, |pane, window, cx| {
7584            pane.close_active_item(&Default::default(), window, cx)
7585                .unwrap()
7586        })
7587        .await
7588        .unwrap();
7589        assert_eq!(cx.window_title().as_deref(), Some("root1 — one.txt"));
7590        project.update(cx, |project, cx| {
7591            assert_eq!(
7592                project.active_entry(),
7593                project
7594                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
7595                    .map(|e| e.id)
7596            );
7597        });
7598
7599        // Add a project folder
7600        project
7601            .update(cx, |project, cx| {
7602                project.find_or_create_worktree("root2", true, cx)
7603            })
7604            .await
7605            .unwrap();
7606        assert_eq!(cx.window_title().as_deref(), Some("root1, root2 — one.txt"));
7607
7608        // Remove a project folder
7609        project.update(cx, |project, cx| project.remove_worktree(worktree_id, cx));
7610        assert_eq!(cx.window_title().as_deref(), Some("root2 — one.txt"));
7611    }
7612
7613    #[gpui::test]
7614    async fn test_close_window(cx: &mut TestAppContext) {
7615        init_test(cx);
7616
7617        let fs = FakeFs::new(cx.executor());
7618        fs.insert_tree("/root", json!({ "one": "" })).await;
7619
7620        let project = Project::test(fs, ["root".as_ref()], cx).await;
7621        let (workspace, cx) =
7622            cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
7623
7624        // When there are no dirty items, there's nothing to do.
7625        let item1 = cx.new(TestItem::new);
7626        workspace.update_in(cx, |w, window, cx| {
7627            w.add_item_to_active_pane(Box::new(item1.clone()), None, true, window, cx)
7628        });
7629        let task = workspace.update_in(cx, |w, window, cx| {
7630            w.prepare_to_close(CloseIntent::CloseWindow, window, cx)
7631        });
7632        assert!(task.await.unwrap());
7633
7634        // When there are dirty untitled items, prompt to save each one. If the user
7635        // cancels any prompt, then abort.
7636        let item2 = cx.new(|cx| TestItem::new(cx).with_dirty(true));
7637        let item3 = cx.new(|cx| {
7638            TestItem::new(cx)
7639                .with_dirty(true)
7640                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
7641        });
7642        workspace.update_in(cx, |w, window, cx| {
7643            w.add_item_to_active_pane(Box::new(item2.clone()), None, true, window, cx);
7644            w.add_item_to_active_pane(Box::new(item3.clone()), None, true, window, cx);
7645        });
7646        let task = workspace.update_in(cx, |w, window, cx| {
7647            w.prepare_to_close(CloseIntent::CloseWindow, window, cx)
7648        });
7649        cx.executor().run_until_parked();
7650        cx.simulate_prompt_answer("Cancel"); // cancel save all
7651        cx.executor().run_until_parked();
7652        assert!(!cx.has_pending_prompt());
7653        assert!(!task.await.unwrap());
7654    }
7655
7656    #[gpui::test]
7657    async fn test_close_window_with_serializable_items(cx: &mut TestAppContext) {
7658        init_test(cx);
7659
7660        // Register TestItem as a serializable item
7661        cx.update(|cx| {
7662            register_serializable_item::<TestItem>(cx);
7663        });
7664
7665        let fs = FakeFs::new(cx.executor());
7666        fs.insert_tree("/root", json!({ "one": "" })).await;
7667
7668        let project = Project::test(fs, ["root".as_ref()], cx).await;
7669        let (workspace, cx) =
7670            cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
7671
7672        // When there are dirty untitled items, but they can serialize, then there is no prompt.
7673        let item1 = cx.new(|cx| {
7674            TestItem::new(cx)
7675                .with_dirty(true)
7676                .with_serialize(|| Some(Task::ready(Ok(()))))
7677        });
7678        let item2 = cx.new(|cx| {
7679            TestItem::new(cx)
7680                .with_dirty(true)
7681                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
7682                .with_serialize(|| Some(Task::ready(Ok(()))))
7683        });
7684        workspace.update_in(cx, |w, window, cx| {
7685            w.add_item_to_active_pane(Box::new(item1.clone()), None, true, window, cx);
7686            w.add_item_to_active_pane(Box::new(item2.clone()), None, true, window, cx);
7687        });
7688        let task = workspace.update_in(cx, |w, window, cx| {
7689            w.prepare_to_close(CloseIntent::CloseWindow, window, cx)
7690        });
7691        assert!(task.await.unwrap());
7692    }
7693
7694    #[gpui::test]
7695    async fn test_close_pane_items(cx: &mut TestAppContext) {
7696        init_test(cx);
7697
7698        let fs = FakeFs::new(cx.executor());
7699
7700        let project = Project::test(fs, None, cx).await;
7701        let (workspace, cx) =
7702            cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
7703
7704        let item1 = cx.new(|cx| {
7705            TestItem::new(cx)
7706                .with_dirty(true)
7707                .with_project_items(&[dirty_project_item(1, "1.txt", cx)])
7708        });
7709        let item2 = cx.new(|cx| {
7710            TestItem::new(cx)
7711                .with_dirty(true)
7712                .with_conflict(true)
7713                .with_project_items(&[dirty_project_item(2, "2.txt", cx)])
7714        });
7715        let item3 = cx.new(|cx| {
7716            TestItem::new(cx)
7717                .with_dirty(true)
7718                .with_conflict(true)
7719                .with_project_items(&[dirty_project_item(3, "3.txt", cx)])
7720        });
7721        let item4 = cx.new(|cx| {
7722            TestItem::new(cx).with_dirty(true).with_project_items(&[{
7723                let project_item = TestProjectItem::new_untitled(cx);
7724                project_item.update(cx, |project_item, _| project_item.is_dirty = true);
7725                project_item
7726            }])
7727        });
7728        let pane = workspace.update_in(cx, |workspace, window, cx| {
7729            workspace.add_item_to_active_pane(Box::new(item1.clone()), None, true, window, cx);
7730            workspace.add_item_to_active_pane(Box::new(item2.clone()), None, true, window, cx);
7731            workspace.add_item_to_active_pane(Box::new(item3.clone()), None, true, window, cx);
7732            workspace.add_item_to_active_pane(Box::new(item4.clone()), None, true, window, cx);
7733            workspace.active_pane().clone()
7734        });
7735
7736        let close_items = pane.update_in(cx, |pane, window, cx| {
7737            pane.activate_item(1, true, true, window, cx);
7738            assert_eq!(pane.active_item().unwrap().item_id(), item2.item_id());
7739            let item1_id = item1.item_id();
7740            let item3_id = item3.item_id();
7741            let item4_id = item4.item_id();
7742            pane.close_items(window, cx, SaveIntent::Close, move |id| {
7743                [item1_id, item3_id, item4_id].contains(&id)
7744            })
7745        });
7746        cx.executor().run_until_parked();
7747
7748        assert!(cx.has_pending_prompt());
7749        cx.simulate_prompt_answer("Save all");
7750
7751        cx.executor().run_until_parked();
7752
7753        // Item 1 is saved. There's a prompt to save item 3.
7754        pane.update(cx, |pane, cx| {
7755            assert_eq!(item1.read(cx).save_count, 1);
7756            assert_eq!(item1.read(cx).save_as_count, 0);
7757            assert_eq!(item1.read(cx).reload_count, 0);
7758            assert_eq!(pane.items_len(), 3);
7759            assert_eq!(pane.active_item().unwrap().item_id(), item3.item_id());
7760        });
7761        assert!(cx.has_pending_prompt());
7762
7763        // Cancel saving item 3.
7764        cx.simulate_prompt_answer("Discard");
7765        cx.executor().run_until_parked();
7766
7767        // Item 3 is reloaded. There's a prompt to save item 4.
7768        pane.update(cx, |pane, cx| {
7769            assert_eq!(item3.read(cx).save_count, 0);
7770            assert_eq!(item3.read(cx).save_as_count, 0);
7771            assert_eq!(item3.read(cx).reload_count, 1);
7772            assert_eq!(pane.items_len(), 2);
7773            assert_eq!(pane.active_item().unwrap().item_id(), item4.item_id());
7774        });
7775
7776        // There's a prompt for a path for item 4.
7777        cx.simulate_new_path_selection(|_| Some(Default::default()));
7778        close_items.await.unwrap();
7779
7780        // The requested items are closed.
7781        pane.update(cx, |pane, cx| {
7782            assert_eq!(item4.read(cx).save_count, 0);
7783            assert_eq!(item4.read(cx).save_as_count, 1);
7784            assert_eq!(item4.read(cx).reload_count, 0);
7785            assert_eq!(pane.items_len(), 1);
7786            assert_eq!(pane.active_item().unwrap().item_id(), item2.item_id());
7787        });
7788    }
7789
7790    #[gpui::test]
7791    async fn test_prompting_to_save_only_on_last_item_for_entry(cx: &mut TestAppContext) {
7792        init_test(cx);
7793
7794        let fs = FakeFs::new(cx.executor());
7795        let project = Project::test(fs, [], cx).await;
7796        let (workspace, cx) =
7797            cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
7798
7799        // Create several workspace items with single project entries, and two
7800        // workspace items with multiple project entries.
7801        let single_entry_items = (0..=4)
7802            .map(|project_entry_id| {
7803                cx.new(|cx| {
7804                    TestItem::new(cx)
7805                        .with_dirty(true)
7806                        .with_project_items(&[dirty_project_item(
7807                            project_entry_id,
7808                            &format!("{project_entry_id}.txt"),
7809                            cx,
7810                        )])
7811                })
7812            })
7813            .collect::<Vec<_>>();
7814        let item_2_3 = cx.new(|cx| {
7815            TestItem::new(cx)
7816                .with_dirty(true)
7817                .with_singleton(false)
7818                .with_project_items(&[
7819                    single_entry_items[2].read(cx).project_items[0].clone(),
7820                    single_entry_items[3].read(cx).project_items[0].clone(),
7821                ])
7822        });
7823        let item_3_4 = cx.new(|cx| {
7824            TestItem::new(cx)
7825                .with_dirty(true)
7826                .with_singleton(false)
7827                .with_project_items(&[
7828                    single_entry_items[3].read(cx).project_items[0].clone(),
7829                    single_entry_items[4].read(cx).project_items[0].clone(),
7830                ])
7831        });
7832
7833        // Create two panes that contain the following project entries:
7834        //   left pane:
7835        //     multi-entry items:   (2, 3)
7836        //     single-entry items:  0, 2, 3, 4
7837        //   right pane:
7838        //     single-entry items:  4, 1
7839        //     multi-entry items:   (3, 4)
7840        let (left_pane, right_pane) = workspace.update_in(cx, |workspace, window, cx| {
7841            let left_pane = workspace.active_pane().clone();
7842            workspace.add_item_to_active_pane(Box::new(item_2_3.clone()), None, true, window, cx);
7843            workspace.add_item_to_active_pane(
7844                single_entry_items[0].boxed_clone(),
7845                None,
7846                true,
7847                window,
7848                cx,
7849            );
7850            workspace.add_item_to_active_pane(
7851                single_entry_items[2].boxed_clone(),
7852                None,
7853                true,
7854                window,
7855                cx,
7856            );
7857            workspace.add_item_to_active_pane(
7858                single_entry_items[3].boxed_clone(),
7859                None,
7860                true,
7861                window,
7862                cx,
7863            );
7864            workspace.add_item_to_active_pane(
7865                single_entry_items[4].boxed_clone(),
7866                None,
7867                true,
7868                window,
7869                cx,
7870            );
7871
7872            let right_pane = workspace
7873                .split_and_clone(left_pane.clone(), SplitDirection::Right, window, cx)
7874                .unwrap();
7875
7876            right_pane.update(cx, |pane, cx| {
7877                pane.add_item(
7878                    single_entry_items[1].boxed_clone(),
7879                    true,
7880                    true,
7881                    None,
7882                    window,
7883                    cx,
7884                );
7885                pane.add_item(Box::new(item_3_4.clone()), true, true, None, window, cx);
7886            });
7887
7888            (left_pane, right_pane)
7889        });
7890
7891        cx.focus(&right_pane);
7892
7893        let mut close = right_pane.update_in(cx, |pane, window, cx| {
7894            pane.close_all_items(&CloseAllItems::default(), window, cx)
7895                .unwrap()
7896        });
7897        cx.executor().run_until_parked();
7898
7899        let msg = cx.pending_prompt().unwrap().0;
7900        assert!(msg.contains("1.txt"));
7901        assert!(!msg.contains("2.txt"));
7902        assert!(!msg.contains("3.txt"));
7903        assert!(!msg.contains("4.txt"));
7904
7905        cx.simulate_prompt_answer("Cancel");
7906        close.await.unwrap();
7907
7908        left_pane
7909            .update_in(cx, |left_pane, window, cx| {
7910                left_pane.close_item_by_id(
7911                    single_entry_items[3].entity_id(),
7912                    SaveIntent::Skip,
7913                    window,
7914                    cx,
7915                )
7916            })
7917            .await
7918            .unwrap();
7919
7920        close = right_pane.update_in(cx, |pane, window, cx| {
7921            pane.close_all_items(&CloseAllItems::default(), window, cx)
7922                .unwrap()
7923        });
7924        cx.executor().run_until_parked();
7925
7926        let details = cx.pending_prompt().unwrap().1;
7927        assert!(details.contains("1.txt"));
7928        assert!(!details.contains("2.txt"));
7929        assert!(details.contains("3.txt"));
7930        // ideally this assertion could be made, but today we can only
7931        // save whole items not project items, so the orphaned item 3 causes
7932        // 4 to be saved too.
7933        // assert!(!details.contains("4.txt"));
7934
7935        cx.simulate_prompt_answer("Save all");
7936
7937        cx.executor().run_until_parked();
7938        close.await.unwrap();
7939        right_pane.update(cx, |pane, _| {
7940            assert_eq!(pane.items_len(), 0);
7941        });
7942    }
7943
7944    #[gpui::test]
7945    async fn test_autosave(cx: &mut gpui::TestAppContext) {
7946        init_test(cx);
7947
7948        let fs = FakeFs::new(cx.executor());
7949        let project = Project::test(fs, [], cx).await;
7950        let (workspace, cx) =
7951            cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
7952        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
7953
7954        let item = cx.new(|cx| {
7955            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
7956        });
7957        let item_id = item.entity_id();
7958        workspace.update_in(cx, |workspace, window, cx| {
7959            workspace.add_item_to_active_pane(Box::new(item.clone()), None, true, window, cx);
7960        });
7961
7962        // Autosave on window change.
7963        item.update(cx, |item, cx| {
7964            SettingsStore::update_global(cx, |settings, cx| {
7965                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
7966                    settings.autosave = Some(AutosaveSetting::OnWindowChange);
7967                })
7968            });
7969            item.is_dirty = true;
7970        });
7971
7972        // Deactivating the window saves the file.
7973        cx.deactivate_window();
7974        item.update(cx, |item, _| assert_eq!(item.save_count, 1));
7975
7976        // Re-activating the window doesn't save the file.
7977        cx.update(|window, _| window.activate_window());
7978        cx.executor().run_until_parked();
7979        item.update(cx, |item, _| assert_eq!(item.save_count, 1));
7980
7981        // Autosave on focus change.
7982        item.update_in(cx, |item, window, cx| {
7983            cx.focus_self(window);
7984            SettingsStore::update_global(cx, |settings, cx| {
7985                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
7986                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
7987                })
7988            });
7989            item.is_dirty = true;
7990        });
7991
7992        // Blurring the item saves the file.
7993        item.update_in(cx, |_, window, _| window.blur());
7994        cx.executor().run_until_parked();
7995        item.update(cx, |item, _| assert_eq!(item.save_count, 2));
7996
7997        // Deactivating the window still saves the file.
7998        item.update_in(cx, |item, window, cx| {
7999            cx.focus_self(window);
8000            item.is_dirty = true;
8001        });
8002        cx.deactivate_window();
8003        item.update(cx, |item, _| assert_eq!(item.save_count, 3));
8004
8005        // Autosave after delay.
8006        item.update(cx, |item, cx| {
8007            SettingsStore::update_global(cx, |settings, cx| {
8008                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
8009                    settings.autosave = Some(AutosaveSetting::AfterDelay { milliseconds: 500 });
8010                })
8011            });
8012            item.is_dirty = true;
8013            cx.emit(ItemEvent::Edit);
8014        });
8015
8016        // Delay hasn't fully expired, so the file is still dirty and unsaved.
8017        cx.executor().advance_clock(Duration::from_millis(250));
8018        item.update(cx, |item, _| assert_eq!(item.save_count, 3));
8019
8020        // After delay expires, the file is saved.
8021        cx.executor().advance_clock(Duration::from_millis(250));
8022        item.update(cx, |item, _| assert_eq!(item.save_count, 4));
8023
8024        // Autosave on focus change, ensuring closing the tab counts as such.
8025        item.update(cx, |item, cx| {
8026            SettingsStore::update_global(cx, |settings, cx| {
8027                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
8028                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
8029                })
8030            });
8031            item.is_dirty = true;
8032            for project_item in &mut item.project_items {
8033                project_item.update(cx, |project_item, _| project_item.is_dirty = true);
8034            }
8035        });
8036
8037        pane.update_in(cx, |pane, window, cx| {
8038            pane.close_items(window, cx, SaveIntent::Close, move |id| id == item_id)
8039        })
8040        .await
8041        .unwrap();
8042        assert!(!cx.has_pending_prompt());
8043        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
8044
8045        // Add the item again, ensuring autosave is prevented if the underlying file has been deleted.
8046        workspace.update_in(cx, |workspace, window, cx| {
8047            workspace.add_item_to_active_pane(Box::new(item.clone()), None, true, window, cx);
8048        });
8049        item.update_in(cx, |item, window, cx| {
8050            item.project_items[0].update(cx, |item, _| {
8051                item.entry_id = None;
8052            });
8053            item.is_dirty = true;
8054            window.blur();
8055        });
8056        cx.run_until_parked();
8057        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
8058
8059        // Ensure autosave is prevented for deleted files also when closing the buffer.
8060        let _close_items = pane.update_in(cx, |pane, window, cx| {
8061            pane.close_items(window, cx, SaveIntent::Close, move |id| id == item_id)
8062        });
8063        cx.run_until_parked();
8064        assert!(cx.has_pending_prompt());
8065        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
8066    }
8067
8068    #[gpui::test]
8069    async fn test_pane_navigation(cx: &mut gpui::TestAppContext) {
8070        init_test(cx);
8071
8072        let fs = FakeFs::new(cx.executor());
8073
8074        let project = Project::test(fs, [], cx).await;
8075        let (workspace, cx) =
8076            cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
8077
8078        let item = cx.new(|cx| {
8079            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
8080        });
8081        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
8082        let toolbar = pane.update(cx, |pane, _| pane.toolbar().clone());
8083        let toolbar_notify_count = Rc::new(RefCell::new(0));
8084
8085        workspace.update_in(cx, |workspace, window, cx| {
8086            workspace.add_item_to_active_pane(Box::new(item.clone()), None, true, window, cx);
8087            let toolbar_notification_count = toolbar_notify_count.clone();
8088            cx.observe_in(&toolbar, window, move |_, _, _, _| {
8089                *toolbar_notification_count.borrow_mut() += 1
8090            })
8091            .detach();
8092        });
8093
8094        pane.update(cx, |pane, _| {
8095            assert!(!pane.can_navigate_backward());
8096            assert!(!pane.can_navigate_forward());
8097        });
8098
8099        item.update_in(cx, |item, _, cx| {
8100            item.set_state("one".to_string(), cx);
8101        });
8102
8103        // Toolbar must be notified to re-render the navigation buttons
8104        assert_eq!(*toolbar_notify_count.borrow(), 1);
8105
8106        pane.update(cx, |pane, _| {
8107            assert!(pane.can_navigate_backward());
8108            assert!(!pane.can_navigate_forward());
8109        });
8110
8111        workspace
8112            .update_in(cx, |workspace, window, cx| {
8113                workspace.go_back(pane.downgrade(), window, cx)
8114            })
8115            .await
8116            .unwrap();
8117
8118        assert_eq!(*toolbar_notify_count.borrow(), 2);
8119        pane.update(cx, |pane, _| {
8120            assert!(!pane.can_navigate_backward());
8121            assert!(pane.can_navigate_forward());
8122        });
8123    }
8124
8125    #[gpui::test]
8126    async fn test_toggle_docks_and_panels(cx: &mut gpui::TestAppContext) {
8127        init_test(cx);
8128        let fs = FakeFs::new(cx.executor());
8129
8130        let project = Project::test(fs, [], cx).await;
8131        let (workspace, cx) =
8132            cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
8133
8134        let panel = workspace.update_in(cx, |workspace, window, cx| {
8135            let panel = cx.new(|cx| TestPanel::new(DockPosition::Right, cx));
8136            workspace.add_panel(panel.clone(), window, cx);
8137
8138            workspace
8139                .right_dock()
8140                .update(cx, |right_dock, cx| right_dock.set_open(true, window, cx));
8141
8142            panel
8143        });
8144
8145        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
8146        pane.update_in(cx, |pane, window, cx| {
8147            let item = cx.new(TestItem::new);
8148            pane.add_item(Box::new(item), true, true, None, window, cx);
8149        });
8150
8151        // Transfer focus from center to panel
8152        workspace.update_in(cx, |workspace, window, cx| {
8153            workspace.toggle_panel_focus::<TestPanel>(window, cx);
8154        });
8155
8156        workspace.update_in(cx, |workspace, window, cx| {
8157            assert!(workspace.right_dock().read(cx).is_open());
8158            assert!(!panel.is_zoomed(window, cx));
8159            assert!(panel.read(cx).focus_handle(cx).contains_focused(window, cx));
8160        });
8161
8162        // Transfer focus from panel to center
8163        workspace.update_in(cx, |workspace, window, cx| {
8164            workspace.toggle_panel_focus::<TestPanel>(window, cx);
8165        });
8166
8167        workspace.update_in(cx, |workspace, window, cx| {
8168            assert!(workspace.right_dock().read(cx).is_open());
8169            assert!(!panel.is_zoomed(window, cx));
8170            assert!(!panel.read(cx).focus_handle(cx).contains_focused(window, cx));
8171        });
8172
8173        // Close the dock
8174        workspace.update_in(cx, |workspace, window, cx| {
8175            workspace.toggle_dock(DockPosition::Right, window, cx);
8176        });
8177
8178        workspace.update_in(cx, |workspace, window, cx| {
8179            assert!(!workspace.right_dock().read(cx).is_open());
8180            assert!(!panel.is_zoomed(window, cx));
8181            assert!(!panel.read(cx).focus_handle(cx).contains_focused(window, cx));
8182        });
8183
8184        // Open the dock
8185        workspace.update_in(cx, |workspace, window, cx| {
8186            workspace.toggle_dock(DockPosition::Right, window, cx);
8187        });
8188
8189        workspace.update_in(cx, |workspace, window, cx| {
8190            assert!(workspace.right_dock().read(cx).is_open());
8191            assert!(!panel.is_zoomed(window, cx));
8192            assert!(panel.read(cx).focus_handle(cx).contains_focused(window, cx));
8193        });
8194
8195        // Focus and zoom panel
8196        panel.update_in(cx, |panel, window, cx| {
8197            cx.focus_self(window);
8198            panel.set_zoomed(true, window, cx)
8199        });
8200
8201        workspace.update_in(cx, |workspace, window, cx| {
8202            assert!(workspace.right_dock().read(cx).is_open());
8203            assert!(panel.is_zoomed(window, cx));
8204            assert!(panel.read(cx).focus_handle(cx).contains_focused(window, cx));
8205        });
8206
8207        // Transfer focus to the center closes the dock
8208        workspace.update_in(cx, |workspace, window, cx| {
8209            workspace.toggle_panel_focus::<TestPanel>(window, cx);
8210        });
8211
8212        workspace.update_in(cx, |workspace, window, cx| {
8213            assert!(!workspace.right_dock().read(cx).is_open());
8214            assert!(panel.is_zoomed(window, cx));
8215            assert!(!panel.read(cx).focus_handle(cx).contains_focused(window, cx));
8216        });
8217
8218        // Transferring focus back to the panel keeps it zoomed
8219        workspace.update_in(cx, |workspace, window, cx| {
8220            workspace.toggle_panel_focus::<TestPanel>(window, cx);
8221        });
8222
8223        workspace.update_in(cx, |workspace, window, cx| {
8224            assert!(workspace.right_dock().read(cx).is_open());
8225            assert!(panel.is_zoomed(window, cx));
8226            assert!(panel.read(cx).focus_handle(cx).contains_focused(window, cx));
8227        });
8228
8229        // Close the dock while it is zoomed
8230        workspace.update_in(cx, |workspace, window, cx| {
8231            workspace.toggle_dock(DockPosition::Right, window, cx)
8232        });
8233
8234        workspace.update_in(cx, |workspace, window, cx| {
8235            assert!(!workspace.right_dock().read(cx).is_open());
8236            assert!(panel.is_zoomed(window, cx));
8237            assert!(workspace.zoomed.is_none());
8238            assert!(!panel.read(cx).focus_handle(cx).contains_focused(window, cx));
8239        });
8240
8241        // Opening the dock, when it's zoomed, retains focus
8242        workspace.update_in(cx, |workspace, window, cx| {
8243            workspace.toggle_dock(DockPosition::Right, window, cx)
8244        });
8245
8246        workspace.update_in(cx, |workspace, window, cx| {
8247            assert!(workspace.right_dock().read(cx).is_open());
8248            assert!(panel.is_zoomed(window, cx));
8249            assert!(workspace.zoomed.is_some());
8250            assert!(panel.read(cx).focus_handle(cx).contains_focused(window, cx));
8251        });
8252
8253        // Unzoom and close the panel, zoom the active pane.
8254        panel.update_in(cx, |panel, window, cx| panel.set_zoomed(false, window, cx));
8255        workspace.update_in(cx, |workspace, window, cx| {
8256            workspace.toggle_dock(DockPosition::Right, window, cx)
8257        });
8258        pane.update_in(cx, |pane, window, cx| {
8259            pane.toggle_zoom(&Default::default(), window, cx)
8260        });
8261
8262        // Opening a dock unzooms the pane.
8263        workspace.update_in(cx, |workspace, window, cx| {
8264            workspace.toggle_dock(DockPosition::Right, window, cx)
8265        });
8266        workspace.update_in(cx, |workspace, window, cx| {
8267            let pane = pane.read(cx);
8268            assert!(!pane.is_zoomed());
8269            assert!(!pane.focus_handle(cx).is_focused(window));
8270            assert!(workspace.right_dock().read(cx).is_open());
8271            assert!(workspace.zoomed.is_none());
8272        });
8273    }
8274
8275    #[gpui::test]
8276    async fn test_join_pane_into_next(cx: &mut gpui::TestAppContext) {
8277        init_test(cx);
8278
8279        let fs = FakeFs::new(cx.executor());
8280
8281        let project = Project::test(fs, None, cx).await;
8282        let (workspace, cx) =
8283            cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
8284
8285        // Let's arrange the panes like this:
8286        //
8287        // +-----------------------+
8288        // |         top           |
8289        // +------+--------+-------+
8290        // | left | center | right |
8291        // +------+--------+-------+
8292        // |        bottom         |
8293        // +-----------------------+
8294
8295        let top_item = cx.new(|cx| {
8296            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "top.txt", cx)])
8297        });
8298        let bottom_item = cx.new(|cx| {
8299            TestItem::new(cx).with_project_items(&[TestProjectItem::new(2, "bottom.txt", cx)])
8300        });
8301        let left_item = cx.new(|cx| {
8302            TestItem::new(cx).with_project_items(&[TestProjectItem::new(3, "left.txt", cx)])
8303        });
8304        let right_item = cx.new(|cx| {
8305            TestItem::new(cx).with_project_items(&[TestProjectItem::new(4, "right.txt", cx)])
8306        });
8307        let center_item = cx.new(|cx| {
8308            TestItem::new(cx).with_project_items(&[TestProjectItem::new(5, "center.txt", cx)])
8309        });
8310
8311        let top_pane_id = workspace.update_in(cx, |workspace, window, cx| {
8312            let top_pane_id = workspace.active_pane().entity_id();
8313            workspace.add_item_to_active_pane(Box::new(top_item.clone()), None, false, window, cx);
8314            workspace.split_pane(
8315                workspace.active_pane().clone(),
8316                SplitDirection::Down,
8317                window,
8318                cx,
8319            );
8320            top_pane_id
8321        });
8322        let bottom_pane_id = workspace.update_in(cx, |workspace, window, cx| {
8323            let bottom_pane_id = workspace.active_pane().entity_id();
8324            workspace.add_item_to_active_pane(
8325                Box::new(bottom_item.clone()),
8326                None,
8327                false,
8328                window,
8329                cx,
8330            );
8331            workspace.split_pane(
8332                workspace.active_pane().clone(),
8333                SplitDirection::Up,
8334                window,
8335                cx,
8336            );
8337            bottom_pane_id
8338        });
8339        let left_pane_id = workspace.update_in(cx, |workspace, window, cx| {
8340            let left_pane_id = workspace.active_pane().entity_id();
8341            workspace.add_item_to_active_pane(Box::new(left_item.clone()), None, false, window, cx);
8342            workspace.split_pane(
8343                workspace.active_pane().clone(),
8344                SplitDirection::Right,
8345                window,
8346                cx,
8347            );
8348            left_pane_id
8349        });
8350        let right_pane_id = workspace.update_in(cx, |workspace, window, cx| {
8351            let right_pane_id = workspace.active_pane().entity_id();
8352            workspace.add_item_to_active_pane(
8353                Box::new(right_item.clone()),
8354                None,
8355                false,
8356                window,
8357                cx,
8358            );
8359            workspace.split_pane(
8360                workspace.active_pane().clone(),
8361                SplitDirection::Left,
8362                window,
8363                cx,
8364            );
8365            right_pane_id
8366        });
8367        let center_pane_id = workspace.update_in(cx, |workspace, window, cx| {
8368            let center_pane_id = workspace.active_pane().entity_id();
8369            workspace.add_item_to_active_pane(
8370                Box::new(center_item.clone()),
8371                None,
8372                false,
8373                window,
8374                cx,
8375            );
8376            center_pane_id
8377        });
8378        cx.executor().run_until_parked();
8379
8380        workspace.update_in(cx, |workspace, window, cx| {
8381            assert_eq!(center_pane_id, workspace.active_pane().entity_id());
8382
8383            // Join into next from center pane into right
8384            workspace.join_pane_into_next(workspace.active_pane().clone(), window, cx);
8385        });
8386
8387        workspace.update_in(cx, |workspace, window, cx| {
8388            let active_pane = workspace.active_pane();
8389            assert_eq!(right_pane_id, active_pane.entity_id());
8390            assert_eq!(2, active_pane.read(cx).items_len());
8391            let item_ids_in_pane =
8392                HashSet::from_iter(active_pane.read(cx).items().map(|item| item.item_id()));
8393            assert!(item_ids_in_pane.contains(&center_item.item_id()));
8394            assert!(item_ids_in_pane.contains(&right_item.item_id()));
8395
8396            // Join into next from right pane into bottom
8397            workspace.join_pane_into_next(workspace.active_pane().clone(), window, cx);
8398        });
8399
8400        workspace.update_in(cx, |workspace, window, cx| {
8401            let active_pane = workspace.active_pane();
8402            assert_eq!(bottom_pane_id, active_pane.entity_id());
8403            assert_eq!(3, active_pane.read(cx).items_len());
8404            let item_ids_in_pane =
8405                HashSet::from_iter(active_pane.read(cx).items().map(|item| item.item_id()));
8406            assert!(item_ids_in_pane.contains(&center_item.item_id()));
8407            assert!(item_ids_in_pane.contains(&right_item.item_id()));
8408            assert!(item_ids_in_pane.contains(&bottom_item.item_id()));
8409
8410            // Join into next from bottom pane into left
8411            workspace.join_pane_into_next(workspace.active_pane().clone(), window, cx);
8412        });
8413
8414        workspace.update_in(cx, |workspace, window, cx| {
8415            let active_pane = workspace.active_pane();
8416            assert_eq!(left_pane_id, active_pane.entity_id());
8417            assert_eq!(4, active_pane.read(cx).items_len());
8418            let item_ids_in_pane =
8419                HashSet::from_iter(active_pane.read(cx).items().map(|item| item.item_id()));
8420            assert!(item_ids_in_pane.contains(&center_item.item_id()));
8421            assert!(item_ids_in_pane.contains(&right_item.item_id()));
8422            assert!(item_ids_in_pane.contains(&bottom_item.item_id()));
8423            assert!(item_ids_in_pane.contains(&left_item.item_id()));
8424
8425            // Join into next from left pane into top
8426            workspace.join_pane_into_next(workspace.active_pane().clone(), window, cx);
8427        });
8428
8429        workspace.update_in(cx, |workspace, window, cx| {
8430            let active_pane = workspace.active_pane();
8431            assert_eq!(top_pane_id, active_pane.entity_id());
8432            assert_eq!(5, active_pane.read(cx).items_len());
8433            let item_ids_in_pane =
8434                HashSet::from_iter(active_pane.read(cx).items().map(|item| item.item_id()));
8435            assert!(item_ids_in_pane.contains(&center_item.item_id()));
8436            assert!(item_ids_in_pane.contains(&right_item.item_id()));
8437            assert!(item_ids_in_pane.contains(&bottom_item.item_id()));
8438            assert!(item_ids_in_pane.contains(&left_item.item_id()));
8439            assert!(item_ids_in_pane.contains(&top_item.item_id()));
8440
8441            // Single pane left: no-op
8442            workspace.join_pane_into_next(workspace.active_pane().clone(), window, cx)
8443        });
8444
8445        workspace.update(cx, |workspace, _cx| {
8446            let active_pane = workspace.active_pane();
8447            assert_eq!(top_pane_id, active_pane.entity_id());
8448        });
8449    }
8450
8451    fn add_an_item_to_active_pane(
8452        cx: &mut VisualTestContext,
8453        workspace: &Entity<Workspace>,
8454        item_id: u64,
8455    ) -> Entity<TestItem> {
8456        let item = cx.new(|cx| {
8457            TestItem::new(cx).with_project_items(&[TestProjectItem::new(
8458                item_id,
8459                "item{item_id}.txt",
8460                cx,
8461            )])
8462        });
8463        workspace.update_in(cx, |workspace, window, cx| {
8464            workspace.add_item_to_active_pane(Box::new(item.clone()), None, false, window, cx);
8465        });
8466        return item;
8467    }
8468
8469    fn split_pane(cx: &mut VisualTestContext, workspace: &Entity<Workspace>) -> Entity<Pane> {
8470        return workspace.update_in(cx, |workspace, window, cx| {
8471            let new_pane = workspace.split_pane(
8472                workspace.active_pane().clone(),
8473                SplitDirection::Right,
8474                window,
8475                cx,
8476            );
8477            new_pane
8478        });
8479    }
8480
8481    #[gpui::test]
8482    async fn test_join_all_panes(cx: &mut gpui::TestAppContext) {
8483        init_test(cx);
8484        let fs = FakeFs::new(cx.executor());
8485        let project = Project::test(fs, None, cx).await;
8486        let (workspace, cx) =
8487            cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
8488
8489        add_an_item_to_active_pane(cx, &workspace, 1);
8490        split_pane(cx, &workspace);
8491        add_an_item_to_active_pane(cx, &workspace, 2);
8492        split_pane(cx, &workspace); // empty pane
8493        split_pane(cx, &workspace);
8494        let last_item = add_an_item_to_active_pane(cx, &workspace, 3);
8495
8496        cx.executor().run_until_parked();
8497
8498        workspace.update(cx, |workspace, cx| {
8499            let num_panes = workspace.panes().len();
8500            let num_items_in_current_pane = workspace.active_pane().read(cx).items().count();
8501            let active_item = workspace
8502                .active_pane()
8503                .read(cx)
8504                .active_item()
8505                .expect("item is in focus");
8506
8507            assert_eq!(num_panes, 4);
8508            assert_eq!(num_items_in_current_pane, 1);
8509            assert_eq!(active_item.item_id(), last_item.item_id());
8510        });
8511
8512        workspace.update_in(cx, |workspace, window, cx| {
8513            workspace.join_all_panes(window, cx);
8514        });
8515
8516        workspace.update(cx, |workspace, cx| {
8517            let num_panes = workspace.panes().len();
8518            let num_items_in_current_pane = workspace.active_pane().read(cx).items().count();
8519            let active_item = workspace
8520                .active_pane()
8521                .read(cx)
8522                .active_item()
8523                .expect("item is in focus");
8524
8525            assert_eq!(num_panes, 1);
8526            assert_eq!(num_items_in_current_pane, 3);
8527            assert_eq!(active_item.item_id(), last_item.item_id());
8528        });
8529    }
8530    struct TestModal(FocusHandle);
8531
8532    impl TestModal {
8533        fn new(_: &mut Window, cx: &mut Context<Self>) -> Self {
8534            Self(cx.focus_handle())
8535        }
8536    }
8537
8538    impl EventEmitter<DismissEvent> for TestModal {}
8539
8540    impl Focusable for TestModal {
8541        fn focus_handle(&self, _cx: &App) -> FocusHandle {
8542            self.0.clone()
8543        }
8544    }
8545
8546    impl ModalView for TestModal {}
8547
8548    impl Render for TestModal {
8549        fn render(
8550            &mut self,
8551            _window: &mut Window,
8552            _cx: &mut Context<TestModal>,
8553        ) -> impl IntoElement {
8554            div().track_focus(&self.0)
8555        }
8556    }
8557
8558    #[gpui::test]
8559    async fn test_panels(cx: &mut gpui::TestAppContext) {
8560        init_test(cx);
8561        let fs = FakeFs::new(cx.executor());
8562
8563        let project = Project::test(fs, [], cx).await;
8564        let (workspace, cx) =
8565            cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
8566
8567        let (panel_1, panel_2) = workspace.update_in(cx, |workspace, window, cx| {
8568            let panel_1 = cx.new(|cx| TestPanel::new(DockPosition::Left, cx));
8569            workspace.add_panel(panel_1.clone(), window, cx);
8570            workspace.toggle_dock(DockPosition::Left, window, cx);
8571            let panel_2 = cx.new(|cx| TestPanel::new(DockPosition::Right, cx));
8572            workspace.add_panel(panel_2.clone(), window, cx);
8573            workspace.toggle_dock(DockPosition::Right, window, cx);
8574
8575            let left_dock = workspace.left_dock();
8576            assert_eq!(
8577                left_dock.read(cx).visible_panel().unwrap().panel_id(),
8578                panel_1.panel_id()
8579            );
8580            assert_eq!(
8581                left_dock.read(cx).active_panel_size(window, cx).unwrap(),
8582                panel_1.size(window, cx)
8583            );
8584
8585            left_dock.update(cx, |left_dock, cx| {
8586                left_dock.resize_active_panel(Some(px(1337.)), window, cx)
8587            });
8588            assert_eq!(
8589                workspace
8590                    .right_dock()
8591                    .read(cx)
8592                    .visible_panel()
8593                    .unwrap()
8594                    .panel_id(),
8595                panel_2.panel_id(),
8596            );
8597
8598            (panel_1, panel_2)
8599        });
8600
8601        // Move panel_1 to the right
8602        panel_1.update_in(cx, |panel_1, window, cx| {
8603            panel_1.set_position(DockPosition::Right, window, cx)
8604        });
8605
8606        workspace.update_in(cx, |workspace, window, cx| {
8607            // Since panel_1 was visible on the left, it should now be visible now that it's been moved to the right.
8608            // Since it was the only panel on the left, the left dock should now be closed.
8609            assert!(!workspace.left_dock().read(cx).is_open());
8610            assert!(workspace.left_dock().read(cx).visible_panel().is_none());
8611            let right_dock = workspace.right_dock();
8612            assert_eq!(
8613                right_dock.read(cx).visible_panel().unwrap().panel_id(),
8614                panel_1.panel_id()
8615            );
8616            assert_eq!(
8617                right_dock.read(cx).active_panel_size(window, cx).unwrap(),
8618                px(1337.)
8619            );
8620
8621            // Now we move panel_2 to the left
8622            panel_2.set_position(DockPosition::Left, window, cx);
8623        });
8624
8625        workspace.update(cx, |workspace, cx| {
8626            // Since panel_2 was not visible on the right, we don't open the left dock.
8627            assert!(!workspace.left_dock().read(cx).is_open());
8628            // And the right dock is unaffected in its displaying of panel_1
8629            assert!(workspace.right_dock().read(cx).is_open());
8630            assert_eq!(
8631                workspace
8632                    .right_dock()
8633                    .read(cx)
8634                    .visible_panel()
8635                    .unwrap()
8636                    .panel_id(),
8637                panel_1.panel_id(),
8638            );
8639        });
8640
8641        // Move panel_1 back to the left
8642        panel_1.update_in(cx, |panel_1, window, cx| {
8643            panel_1.set_position(DockPosition::Left, window, cx)
8644        });
8645
8646        workspace.update_in(cx, |workspace, window, cx| {
8647            // Since panel_1 was visible on the right, we open the left dock and make panel_1 active.
8648            let left_dock = workspace.left_dock();
8649            assert!(left_dock.read(cx).is_open());
8650            assert_eq!(
8651                left_dock.read(cx).visible_panel().unwrap().panel_id(),
8652                panel_1.panel_id()
8653            );
8654            assert_eq!(
8655                left_dock.read(cx).active_panel_size(window, cx).unwrap(),
8656                px(1337.)
8657            );
8658            // And the right dock should be closed as it no longer has any panels.
8659            assert!(!workspace.right_dock().read(cx).is_open());
8660
8661            // Now we move panel_1 to the bottom
8662            panel_1.set_position(DockPosition::Bottom, window, cx);
8663        });
8664
8665        workspace.update_in(cx, |workspace, window, cx| {
8666            // Since panel_1 was visible on the left, we close the left dock.
8667            assert!(!workspace.left_dock().read(cx).is_open());
8668            // The bottom dock is sized based on the panel's default size,
8669            // since the panel orientation changed from vertical to horizontal.
8670            let bottom_dock = workspace.bottom_dock();
8671            assert_eq!(
8672                bottom_dock.read(cx).active_panel_size(window, cx).unwrap(),
8673                panel_1.size(window, cx),
8674            );
8675            // Close bottom dock and move panel_1 back to the left.
8676            bottom_dock.update(cx, |bottom_dock, cx| {
8677                bottom_dock.set_open(false, window, cx)
8678            });
8679            panel_1.set_position(DockPosition::Left, window, cx);
8680        });
8681
8682        // Emit activated event on panel 1
8683        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::Activate));
8684
8685        // Now the left dock is open and panel_1 is active and focused.
8686        workspace.update_in(cx, |workspace, window, cx| {
8687            let left_dock = workspace.left_dock();
8688            assert!(left_dock.read(cx).is_open());
8689            assert_eq!(
8690                left_dock.read(cx).visible_panel().unwrap().panel_id(),
8691                panel_1.panel_id(),
8692            );
8693            assert!(panel_1.focus_handle(cx).is_focused(window));
8694        });
8695
8696        // Emit closed event on panel 2, which is not active
8697        panel_2.update(cx, |_, cx| cx.emit(PanelEvent::Close));
8698
8699        // Wo don't close the left dock, because panel_2 wasn't the active panel
8700        workspace.update(cx, |workspace, cx| {
8701            let left_dock = workspace.left_dock();
8702            assert!(left_dock.read(cx).is_open());
8703            assert_eq!(
8704                left_dock.read(cx).visible_panel().unwrap().panel_id(),
8705                panel_1.panel_id(),
8706            );
8707        });
8708
8709        // Emitting a ZoomIn event shows the panel as zoomed.
8710        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::ZoomIn));
8711        workspace.update(cx, |workspace, _| {
8712            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
8713            assert_eq!(workspace.zoomed_position, Some(DockPosition::Left));
8714        });
8715
8716        // Move panel to another dock while it is zoomed
8717        panel_1.update_in(cx, |panel, window, cx| {
8718            panel.set_position(DockPosition::Right, window, cx)
8719        });
8720        workspace.update(cx, |workspace, _| {
8721            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
8722
8723            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
8724        });
8725
8726        // This is a helper for getting a:
8727        // - valid focus on an element,
8728        // - that isn't a part of the panes and panels system of the Workspace,
8729        // - and doesn't trigger the 'on_focus_lost' API.
8730        let focus_other_view = {
8731            let workspace = workspace.clone();
8732            move |cx: &mut VisualTestContext| {
8733                workspace.update_in(cx, |workspace, window, cx| {
8734                    if let Some(_) = workspace.active_modal::<TestModal>(cx) {
8735                        workspace.toggle_modal(window, cx, TestModal::new);
8736                        workspace.toggle_modal(window, cx, TestModal::new);
8737                    } else {
8738                        workspace.toggle_modal(window, cx, TestModal::new);
8739                    }
8740                })
8741            }
8742        };
8743
8744        // If focus is transferred to another view that's not a panel or another pane, we still show
8745        // the panel as zoomed.
8746        focus_other_view(cx);
8747        workspace.update(cx, |workspace, _| {
8748            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
8749            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
8750        });
8751
8752        // If focus is transferred elsewhere in the workspace, the panel is no longer zoomed.
8753        workspace.update_in(cx, |_workspace, window, cx| {
8754            cx.focus_self(window);
8755        });
8756        workspace.update(cx, |workspace, _| {
8757            assert_eq!(workspace.zoomed, None);
8758            assert_eq!(workspace.zoomed_position, None);
8759        });
8760
8761        // If focus is transferred again to another view that's not a panel or a pane, we won't
8762        // show the panel as zoomed because it wasn't zoomed before.
8763        focus_other_view(cx);
8764        workspace.update(cx, |workspace, _| {
8765            assert_eq!(workspace.zoomed, None);
8766            assert_eq!(workspace.zoomed_position, None);
8767        });
8768
8769        // When the panel is activated, it is zoomed again.
8770        cx.dispatch_action(ToggleRightDock);
8771        workspace.update(cx, |workspace, _| {
8772            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
8773            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
8774        });
8775
8776        // Emitting a ZoomOut event unzooms the panel.
8777        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::ZoomOut));
8778        workspace.update(cx, |workspace, _| {
8779            assert_eq!(workspace.zoomed, None);
8780            assert_eq!(workspace.zoomed_position, None);
8781        });
8782
8783        // Emit closed event on panel 1, which is active
8784        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::Close));
8785
8786        // Now the left dock is closed, because panel_1 was the active panel
8787        workspace.update(cx, |workspace, cx| {
8788            let right_dock = workspace.right_dock();
8789            assert!(!right_dock.read(cx).is_open());
8790        });
8791    }
8792
8793    #[gpui::test]
8794    async fn test_no_save_prompt_when_multi_buffer_dirty_items_closed(cx: &mut TestAppContext) {
8795        init_test(cx);
8796
8797        let fs = FakeFs::new(cx.background_executor.clone());
8798        let project = Project::test(fs, [], cx).await;
8799        let (workspace, cx) =
8800            cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
8801        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
8802
8803        let dirty_regular_buffer = cx.new(|cx| {
8804            TestItem::new(cx)
8805                .with_dirty(true)
8806                .with_label("1.txt")
8807                .with_project_items(&[dirty_project_item(1, "1.txt", cx)])
8808        });
8809        let dirty_regular_buffer_2 = cx.new(|cx| {
8810            TestItem::new(cx)
8811                .with_dirty(true)
8812                .with_label("2.txt")
8813                .with_project_items(&[dirty_project_item(2, "2.txt", cx)])
8814        });
8815        let dirty_multi_buffer_with_both = cx.new(|cx| {
8816            TestItem::new(cx)
8817                .with_dirty(true)
8818                .with_singleton(false)
8819                .with_label("Fake Project Search")
8820                .with_project_items(&[
8821                    dirty_regular_buffer.read(cx).project_items[0].clone(),
8822                    dirty_regular_buffer_2.read(cx).project_items[0].clone(),
8823                ])
8824        });
8825        let multi_buffer_with_both_files_id = dirty_multi_buffer_with_both.item_id();
8826        workspace.update_in(cx, |workspace, window, cx| {
8827            workspace.add_item(
8828                pane.clone(),
8829                Box::new(dirty_regular_buffer.clone()),
8830                None,
8831                false,
8832                false,
8833                window,
8834                cx,
8835            );
8836            workspace.add_item(
8837                pane.clone(),
8838                Box::new(dirty_regular_buffer_2.clone()),
8839                None,
8840                false,
8841                false,
8842                window,
8843                cx,
8844            );
8845            workspace.add_item(
8846                pane.clone(),
8847                Box::new(dirty_multi_buffer_with_both.clone()),
8848                None,
8849                false,
8850                false,
8851                window,
8852                cx,
8853            );
8854        });
8855
8856        pane.update_in(cx, |pane, window, cx| {
8857            pane.activate_item(2, true, true, window, cx);
8858            assert_eq!(
8859                pane.active_item().unwrap().item_id(),
8860                multi_buffer_with_both_files_id,
8861                "Should select the multi buffer in the pane"
8862            );
8863        });
8864        let close_all_but_multi_buffer_task = pane
8865            .update_in(cx, |pane, window, cx| {
8866                pane.close_inactive_items(
8867                    &CloseInactiveItems {
8868                        save_intent: Some(SaveIntent::Save),
8869                        close_pinned: true,
8870                    },
8871                    window,
8872                    cx,
8873                )
8874            })
8875            .expect("should have inactive files to close");
8876        cx.background_executor.run_until_parked();
8877        assert!(!cx.has_pending_prompt());
8878        close_all_but_multi_buffer_task
8879            .await
8880            .expect("Closing all buffers but the multi buffer failed");
8881        pane.update(cx, |pane, cx| {
8882            assert_eq!(dirty_regular_buffer.read(cx).save_count, 1);
8883            assert_eq!(dirty_multi_buffer_with_both.read(cx).save_count, 0);
8884            assert_eq!(dirty_regular_buffer_2.read(cx).save_count, 1);
8885            assert_eq!(pane.items_len(), 1);
8886            assert_eq!(
8887                pane.active_item().unwrap().item_id(),
8888                multi_buffer_with_both_files_id,
8889                "Should have only the multi buffer left in the pane"
8890            );
8891            assert!(
8892                dirty_multi_buffer_with_both.read(cx).is_dirty,
8893                "The multi buffer containing the unsaved buffer should still be dirty"
8894            );
8895        });
8896
8897        dirty_regular_buffer.update(cx, |buffer, cx| {
8898            buffer.project_items[0].update(cx, |pi, _| pi.is_dirty = true)
8899        });
8900
8901        let close_multi_buffer_task = pane
8902            .update_in(cx, |pane, window, cx| {
8903                pane.close_active_item(
8904                    &CloseActiveItem {
8905                        save_intent: Some(SaveIntent::Close),
8906                        close_pinned: false,
8907                    },
8908                    window,
8909                    cx,
8910                )
8911            })
8912            .expect("should have the multi buffer to close");
8913        cx.background_executor.run_until_parked();
8914        assert!(
8915            cx.has_pending_prompt(),
8916            "Dirty multi buffer should prompt a save dialog"
8917        );
8918        cx.simulate_prompt_answer("Save");
8919        cx.background_executor.run_until_parked();
8920        close_multi_buffer_task
8921            .await
8922            .expect("Closing the multi buffer failed");
8923        pane.update(cx, |pane, cx| {
8924            assert_eq!(
8925                dirty_multi_buffer_with_both.read(cx).save_count,
8926                1,
8927                "Multi buffer item should get be saved"
8928            );
8929            // Test impl does not save inner items, so we do not assert them
8930            assert_eq!(
8931                pane.items_len(),
8932                0,
8933                "No more items should be left in the pane"
8934            );
8935            assert!(pane.active_item().is_none());
8936        });
8937    }
8938
8939    #[gpui::test]
8940    async fn test_save_prompt_when_dirty_multi_buffer_closed_with_some_of_its_dirty_items_not_present_in_the_pane(
8941        cx: &mut TestAppContext,
8942    ) {
8943        init_test(cx);
8944
8945        let fs = FakeFs::new(cx.background_executor.clone());
8946        let project = Project::test(fs, [], cx).await;
8947        let (workspace, cx) =
8948            cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
8949        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
8950
8951        let dirty_regular_buffer = cx.new(|cx| {
8952            TestItem::new(cx)
8953                .with_dirty(true)
8954                .with_label("1.txt")
8955                .with_project_items(&[dirty_project_item(1, "1.txt", cx)])
8956        });
8957        let dirty_regular_buffer_2 = cx.new(|cx| {
8958            TestItem::new(cx)
8959                .with_dirty(true)
8960                .with_label("2.txt")
8961                .with_project_items(&[dirty_project_item(2, "2.txt", cx)])
8962        });
8963        let clear_regular_buffer = cx.new(|cx| {
8964            TestItem::new(cx)
8965                .with_label("3.txt")
8966                .with_project_items(&[TestProjectItem::new(3, "3.txt", cx)])
8967        });
8968
8969        let dirty_multi_buffer_with_both = cx.new(|cx| {
8970            TestItem::new(cx)
8971                .with_dirty(true)
8972                .with_singleton(false)
8973                .with_label("Fake Project Search")
8974                .with_project_items(&[
8975                    dirty_regular_buffer.read(cx).project_items[0].clone(),
8976                    dirty_regular_buffer_2.read(cx).project_items[0].clone(),
8977                    clear_regular_buffer.read(cx).project_items[0].clone(),
8978                ])
8979        });
8980        let multi_buffer_with_both_files_id = dirty_multi_buffer_with_both.item_id();
8981        workspace.update_in(cx, |workspace, window, cx| {
8982            workspace.add_item(
8983                pane.clone(),
8984                Box::new(dirty_regular_buffer.clone()),
8985                None,
8986                false,
8987                false,
8988                window,
8989                cx,
8990            );
8991            workspace.add_item(
8992                pane.clone(),
8993                Box::new(dirty_multi_buffer_with_both.clone()),
8994                None,
8995                false,
8996                false,
8997                window,
8998                cx,
8999            );
9000        });
9001
9002        pane.update_in(cx, |pane, window, cx| {
9003            pane.activate_item(1, true, true, window, cx);
9004            assert_eq!(
9005                pane.active_item().unwrap().item_id(),
9006                multi_buffer_with_both_files_id,
9007                "Should select the multi buffer in the pane"
9008            );
9009        });
9010        let _close_multi_buffer_task = pane
9011            .update_in(cx, |pane, window, cx| {
9012                pane.close_active_item(
9013                    &CloseActiveItem {
9014                        save_intent: None,
9015                        close_pinned: false,
9016                    },
9017                    window,
9018                    cx,
9019                )
9020            })
9021            .expect("should have active multi buffer to close");
9022        cx.background_executor.run_until_parked();
9023        assert!(
9024            cx.has_pending_prompt(),
9025            "With one dirty item from the multi buffer not being in the pane, a save prompt should be shown"
9026        );
9027    }
9028
9029    #[gpui::test]
9030    async fn test_no_save_prompt_when_dirty_multi_buffer_closed_with_all_of_its_dirty_items_present_in_the_pane(
9031        cx: &mut TestAppContext,
9032    ) {
9033        init_test(cx);
9034
9035        let fs = FakeFs::new(cx.background_executor.clone());
9036        let project = Project::test(fs, [], cx).await;
9037        let (workspace, cx) =
9038            cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
9039        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
9040
9041        let dirty_regular_buffer = cx.new(|cx| {
9042            TestItem::new(cx)
9043                .with_dirty(true)
9044                .with_label("1.txt")
9045                .with_project_items(&[dirty_project_item(1, "1.txt", cx)])
9046        });
9047        let dirty_regular_buffer_2 = cx.new(|cx| {
9048            TestItem::new(cx)
9049                .with_dirty(true)
9050                .with_label("2.txt")
9051                .with_project_items(&[dirty_project_item(2, "2.txt", cx)])
9052        });
9053        let clear_regular_buffer = cx.new(|cx| {
9054            TestItem::new(cx)
9055                .with_label("3.txt")
9056                .with_project_items(&[TestProjectItem::new(3, "3.txt", cx)])
9057        });
9058
9059        let dirty_multi_buffer = cx.new(|cx| {
9060            TestItem::new(cx)
9061                .with_dirty(true)
9062                .with_singleton(false)
9063                .with_label("Fake Project Search")
9064                .with_project_items(&[
9065                    dirty_regular_buffer.read(cx).project_items[0].clone(),
9066                    dirty_regular_buffer_2.read(cx).project_items[0].clone(),
9067                    clear_regular_buffer.read(cx).project_items[0].clone(),
9068                ])
9069        });
9070        workspace.update_in(cx, |workspace, window, cx| {
9071            workspace.add_item(
9072                pane.clone(),
9073                Box::new(dirty_regular_buffer.clone()),
9074                None,
9075                false,
9076                false,
9077                window,
9078                cx,
9079            );
9080            workspace.add_item(
9081                pane.clone(),
9082                Box::new(dirty_regular_buffer_2.clone()),
9083                None,
9084                false,
9085                false,
9086                window,
9087                cx,
9088            );
9089            workspace.add_item(
9090                pane.clone(),
9091                Box::new(dirty_multi_buffer.clone()),
9092                None,
9093                false,
9094                false,
9095                window,
9096                cx,
9097            );
9098        });
9099
9100        pane.update_in(cx, |pane, window, cx| {
9101            pane.activate_item(2, true, true, window, cx);
9102            assert_eq!(
9103                pane.active_item().unwrap().item_id(),
9104                dirty_multi_buffer.item_id(),
9105                "Should select the multi buffer in the pane"
9106            );
9107        });
9108        let close_multi_buffer_task = pane
9109            .update_in(cx, |pane, window, cx| {
9110                pane.close_active_item(
9111                    &CloseActiveItem {
9112                        save_intent: None,
9113                        close_pinned: false,
9114                    },
9115                    window,
9116                    cx,
9117                )
9118            })
9119            .expect("should have active multi buffer to close");
9120        cx.background_executor.run_until_parked();
9121        assert!(
9122            !cx.has_pending_prompt(),
9123            "All dirty items from the multi buffer are in the pane still, no save prompts should be shown"
9124        );
9125        close_multi_buffer_task
9126            .await
9127            .expect("Closing multi buffer failed");
9128        pane.update(cx, |pane, cx| {
9129            assert_eq!(dirty_regular_buffer.read(cx).save_count, 0);
9130            assert_eq!(dirty_multi_buffer.read(cx).save_count, 0);
9131            assert_eq!(dirty_regular_buffer_2.read(cx).save_count, 0);
9132            assert_eq!(
9133                pane.items()
9134                    .map(|item| item.item_id())
9135                    .sorted()
9136                    .collect::<Vec<_>>(),
9137                vec![
9138                    dirty_regular_buffer.item_id(),
9139                    dirty_regular_buffer_2.item_id(),
9140                ],
9141                "Should have no multi buffer left in the pane"
9142            );
9143            assert!(dirty_regular_buffer.read(cx).is_dirty);
9144            assert!(dirty_regular_buffer_2.read(cx).is_dirty);
9145        });
9146    }
9147
9148    #[gpui::test]
9149    async fn test_move_focused_panel_to_next_position(cx: &mut gpui::TestAppContext) {
9150        init_test(cx);
9151        let fs = FakeFs::new(cx.executor());
9152        let project = Project::test(fs, [], cx).await;
9153        let (workspace, cx) =
9154            cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
9155
9156        // Add a new panel to the right dock, opening the dock and setting the
9157        // focus to the new panel.
9158        let panel = workspace.update_in(cx, |workspace, window, cx| {
9159            let panel = cx.new(|cx| TestPanel::new(DockPosition::Right, cx));
9160            workspace.add_panel(panel.clone(), window, cx);
9161
9162            workspace
9163                .right_dock()
9164                .update(cx, |right_dock, cx| right_dock.set_open(true, window, cx));
9165
9166            workspace.toggle_panel_focus::<TestPanel>(window, cx);
9167
9168            panel
9169        });
9170
9171        // Dispatch the `MoveFocusedPanelToNextPosition` action, moving the
9172        // panel to the next valid position which, in this case, is the left
9173        // dock.
9174        cx.dispatch_action(MoveFocusedPanelToNextPosition);
9175        workspace.update(cx, |workspace, cx| {
9176            assert!(workspace.left_dock().read(cx).is_open());
9177            assert_eq!(panel.read(cx).position, DockPosition::Left);
9178        });
9179
9180        // Dispatch the `MoveFocusedPanelToNextPosition` action, moving the
9181        // panel to the next valid position which, in this case, is the bottom
9182        // dock.
9183        cx.dispatch_action(MoveFocusedPanelToNextPosition);
9184        workspace.update(cx, |workspace, cx| {
9185            assert!(workspace.bottom_dock().read(cx).is_open());
9186            assert_eq!(panel.read(cx).position, DockPosition::Bottom);
9187        });
9188
9189        // Dispatch the `MoveFocusedPanelToNextPosition` action again, this time
9190        // around moving the panel to its initial position, the right dock.
9191        cx.dispatch_action(MoveFocusedPanelToNextPosition);
9192        workspace.update(cx, |workspace, cx| {
9193            assert!(workspace.right_dock().read(cx).is_open());
9194            assert_eq!(panel.read(cx).position, DockPosition::Right);
9195        });
9196
9197        // Remove focus from the panel, ensuring that, if the panel is not
9198        // focused, the `MoveFocusedPanelToNextPosition` action does not update
9199        // the panel's position, so the panel is still in the right dock.
9200        workspace.update_in(cx, |workspace, window, cx| {
9201            workspace.toggle_panel_focus::<TestPanel>(window, cx);
9202        });
9203
9204        cx.dispatch_action(MoveFocusedPanelToNextPosition);
9205        workspace.update(cx, |workspace, cx| {
9206            assert!(workspace.right_dock().read(cx).is_open());
9207            assert_eq!(panel.read(cx).position, DockPosition::Right);
9208        });
9209    }
9210
9211    mod register_project_item_tests {
9212
9213        use super::*;
9214
9215        // View
9216        struct TestPngItemView {
9217            focus_handle: FocusHandle,
9218        }
9219        // Model
9220        struct TestPngItem {}
9221
9222        impl project::ProjectItem for TestPngItem {
9223            fn try_open(
9224                _project: &Entity<Project>,
9225                path: &ProjectPath,
9226                cx: &mut App,
9227            ) -> Option<Task<gpui::Result<Entity<Self>>>> {
9228                if path.path.extension().unwrap() == "png" {
9229                    Some(cx.spawn(async move |cx| cx.new(|_| TestPngItem {})))
9230                } else {
9231                    None
9232                }
9233            }
9234
9235            fn entry_id(&self, _: &App) -> Option<ProjectEntryId> {
9236                None
9237            }
9238
9239            fn project_path(&self, _: &App) -> Option<ProjectPath> {
9240                None
9241            }
9242
9243            fn is_dirty(&self) -> bool {
9244                false
9245            }
9246        }
9247
9248        impl Item for TestPngItemView {
9249            type Event = ();
9250            fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString {
9251                "".into()
9252            }
9253        }
9254        impl EventEmitter<()> for TestPngItemView {}
9255        impl Focusable for TestPngItemView {
9256            fn focus_handle(&self, _cx: &App) -> FocusHandle {
9257                self.focus_handle.clone()
9258            }
9259        }
9260
9261        impl Render for TestPngItemView {
9262            fn render(
9263                &mut self,
9264                _window: &mut Window,
9265                _cx: &mut Context<Self>,
9266            ) -> impl IntoElement {
9267                Empty
9268            }
9269        }
9270
9271        impl ProjectItem for TestPngItemView {
9272            type Item = TestPngItem;
9273
9274            fn for_project_item(
9275                _project: Entity<Project>,
9276                _pane: Option<&Pane>,
9277                _item: Entity<Self::Item>,
9278                _: &mut Window,
9279                cx: &mut Context<Self>,
9280            ) -> Self
9281            where
9282                Self: Sized,
9283            {
9284                Self {
9285                    focus_handle: cx.focus_handle(),
9286                }
9287            }
9288        }
9289
9290        // View
9291        struct TestIpynbItemView {
9292            focus_handle: FocusHandle,
9293        }
9294        // Model
9295        struct TestIpynbItem {}
9296
9297        impl project::ProjectItem for TestIpynbItem {
9298            fn try_open(
9299                _project: &Entity<Project>,
9300                path: &ProjectPath,
9301                cx: &mut App,
9302            ) -> Option<Task<gpui::Result<Entity<Self>>>> {
9303                if path.path.extension().unwrap() == "ipynb" {
9304                    Some(cx.spawn(async move |cx| cx.new(|_| TestIpynbItem {})))
9305                } else {
9306                    None
9307                }
9308            }
9309
9310            fn entry_id(&self, _: &App) -> Option<ProjectEntryId> {
9311                None
9312            }
9313
9314            fn project_path(&self, _: &App) -> Option<ProjectPath> {
9315                None
9316            }
9317
9318            fn is_dirty(&self) -> bool {
9319                false
9320            }
9321        }
9322
9323        impl Item for TestIpynbItemView {
9324            type Event = ();
9325            fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString {
9326                "".into()
9327            }
9328        }
9329        impl EventEmitter<()> for TestIpynbItemView {}
9330        impl Focusable for TestIpynbItemView {
9331            fn focus_handle(&self, _cx: &App) -> FocusHandle {
9332                self.focus_handle.clone()
9333            }
9334        }
9335
9336        impl Render for TestIpynbItemView {
9337            fn render(
9338                &mut self,
9339                _window: &mut Window,
9340                _cx: &mut Context<Self>,
9341            ) -> impl IntoElement {
9342                Empty
9343            }
9344        }
9345
9346        impl ProjectItem for TestIpynbItemView {
9347            type Item = TestIpynbItem;
9348
9349            fn for_project_item(
9350                _project: Entity<Project>,
9351                _pane: Option<&Pane>,
9352                _item: Entity<Self::Item>,
9353                _: &mut Window,
9354                cx: &mut Context<Self>,
9355            ) -> Self
9356            where
9357                Self: Sized,
9358            {
9359                Self {
9360                    focus_handle: cx.focus_handle(),
9361                }
9362            }
9363        }
9364
9365        struct TestAlternatePngItemView {
9366            focus_handle: FocusHandle,
9367        }
9368
9369        impl Item for TestAlternatePngItemView {
9370            type Event = ();
9371            fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString {
9372                "".into()
9373            }
9374        }
9375
9376        impl EventEmitter<()> for TestAlternatePngItemView {}
9377        impl Focusable for TestAlternatePngItemView {
9378            fn focus_handle(&self, _cx: &App) -> FocusHandle {
9379                self.focus_handle.clone()
9380            }
9381        }
9382
9383        impl Render for TestAlternatePngItemView {
9384            fn render(
9385                &mut self,
9386                _window: &mut Window,
9387                _cx: &mut Context<Self>,
9388            ) -> impl IntoElement {
9389                Empty
9390            }
9391        }
9392
9393        impl ProjectItem for TestAlternatePngItemView {
9394            type Item = TestPngItem;
9395
9396            fn for_project_item(
9397                _project: Entity<Project>,
9398                _pane: Option<&Pane>,
9399                _item: Entity<Self::Item>,
9400                _: &mut Window,
9401                cx: &mut Context<Self>,
9402            ) -> Self
9403            where
9404                Self: Sized,
9405            {
9406                Self {
9407                    focus_handle: cx.focus_handle(),
9408                }
9409            }
9410        }
9411
9412        #[gpui::test]
9413        async fn test_register_project_item(cx: &mut TestAppContext) {
9414            init_test(cx);
9415
9416            cx.update(|cx| {
9417                register_project_item::<TestPngItemView>(cx);
9418                register_project_item::<TestIpynbItemView>(cx);
9419            });
9420
9421            let fs = FakeFs::new(cx.executor());
9422            fs.insert_tree(
9423                "/root1",
9424                json!({
9425                    "one.png": "BINARYDATAHERE",
9426                    "two.ipynb": "{ totally a notebook }",
9427                    "three.txt": "editing text, sure why not?"
9428                }),
9429            )
9430            .await;
9431
9432            let project = Project::test(fs, ["root1".as_ref()], cx).await;
9433            let (workspace, cx) =
9434                cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
9435
9436            let worktree_id = project.update(cx, |project, cx| {
9437                project.worktrees(cx).next().unwrap().read(cx).id()
9438            });
9439
9440            let handle = workspace
9441                .update_in(cx, |workspace, window, cx| {
9442                    let project_path = (worktree_id, "one.png");
9443                    workspace.open_path(project_path, None, true, window, cx)
9444                })
9445                .await
9446                .unwrap();
9447
9448            // Now we can check if the handle we got back errored or not
9449            assert_eq!(
9450                handle.to_any().entity_type(),
9451                TypeId::of::<TestPngItemView>()
9452            );
9453
9454            let handle = workspace
9455                .update_in(cx, |workspace, window, cx| {
9456                    let project_path = (worktree_id, "two.ipynb");
9457                    workspace.open_path(project_path, None, true, window, cx)
9458                })
9459                .await
9460                .unwrap();
9461
9462            assert_eq!(
9463                handle.to_any().entity_type(),
9464                TypeId::of::<TestIpynbItemView>()
9465            );
9466
9467            let handle = workspace
9468                .update_in(cx, |workspace, window, cx| {
9469                    let project_path = (worktree_id, "three.txt");
9470                    workspace.open_path(project_path, None, true, window, cx)
9471                })
9472                .await;
9473            assert!(handle.is_err());
9474        }
9475
9476        #[gpui::test]
9477        async fn test_register_project_item_two_enter_one_leaves(cx: &mut TestAppContext) {
9478            init_test(cx);
9479
9480            cx.update(|cx| {
9481                register_project_item::<TestPngItemView>(cx);
9482                register_project_item::<TestAlternatePngItemView>(cx);
9483            });
9484
9485            let fs = FakeFs::new(cx.executor());
9486            fs.insert_tree(
9487                "/root1",
9488                json!({
9489                    "one.png": "BINARYDATAHERE",
9490                    "two.ipynb": "{ totally a notebook }",
9491                    "three.txt": "editing text, sure why not?"
9492                }),
9493            )
9494            .await;
9495            let project = Project::test(fs, ["root1".as_ref()], cx).await;
9496            let (workspace, cx) =
9497                cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
9498            let worktree_id = project.update(cx, |project, cx| {
9499                project.worktrees(cx).next().unwrap().read(cx).id()
9500            });
9501
9502            let handle = workspace
9503                .update_in(cx, |workspace, window, cx| {
9504                    let project_path = (worktree_id, "one.png");
9505                    workspace.open_path(project_path, None, true, window, cx)
9506                })
9507                .await
9508                .unwrap();
9509
9510            // This _must_ be the second item registered
9511            assert_eq!(
9512                handle.to_any().entity_type(),
9513                TypeId::of::<TestAlternatePngItemView>()
9514            );
9515
9516            let handle = workspace
9517                .update_in(cx, |workspace, window, cx| {
9518                    let project_path = (worktree_id, "three.txt");
9519                    workspace.open_path(project_path, None, true, window, cx)
9520                })
9521                .await;
9522            assert!(handle.is_err());
9523        }
9524    }
9525
9526    pub fn init_test(cx: &mut TestAppContext) {
9527        cx.update(|cx| {
9528            let settings_store = SettingsStore::test(cx);
9529            cx.set_global(settings_store);
9530            theme::init(theme::LoadThemes::JustBase, cx);
9531            language::init(cx);
9532            crate::init_settings(cx);
9533            Project::init_settings(cx);
9534        });
9535    }
9536
9537    fn dirty_project_item(id: u64, path: &str, cx: &mut App) -> Entity<TestProjectItem> {
9538        let item = TestProjectItem::new(id, path, cx);
9539        item.update(cx, |item, _| {
9540            item.is_dirty = true;
9541        });
9542        item
9543    }
9544}