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