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 = new_size.min(workspace.bounds.bottom() - RESIZE_HANDLE_SIZE);
5883    workspace.bottom_dock.update(cx, |bottom_dock, cx| {
5884        bottom_dock.resize_active_panel(Some(size), window, cx);
5885    });
5886}
5887
5888fn resize_right_dock(
5889    new_size: Pixels,
5890    workspace: &mut Workspace,
5891    window: &mut Window,
5892    cx: &mut App,
5893) {
5894    let size = new_size.max(workspace.bounds.left() - RESIZE_HANDLE_SIZE);
5895    workspace.right_dock.update(cx, |right_dock, cx| {
5896        right_dock.resize_active_panel(Some(size), window, cx);
5897    });
5898}
5899
5900fn resize_left_dock(
5901    new_size: Pixels,
5902    workspace: &mut Workspace,
5903    window: &mut Window,
5904    cx: &mut App,
5905) {
5906    let size = new_size.min(workspace.bounds.right() - RESIZE_HANDLE_SIZE);
5907
5908    workspace.left_dock.update(cx, |left_dock, cx| {
5909        left_dock.resize_active_panel(Some(size), window, cx);
5910    });
5911}
5912
5913impl WorkspaceStore {
5914    pub fn new(client: Arc<Client>, cx: &mut Context<Self>) -> Self {
5915        Self {
5916            workspaces: Default::default(),
5917            _subscriptions: vec![
5918                client.add_request_handler(cx.weak_entity(), Self::handle_follow),
5919                client.add_message_handler(cx.weak_entity(), Self::handle_update_followers),
5920            ],
5921            client,
5922        }
5923    }
5924
5925    pub fn update_followers(
5926        &self,
5927        project_id: Option<u64>,
5928        update: proto::update_followers::Variant,
5929        cx: &App,
5930    ) -> Option<()> {
5931        let active_call = ActiveCall::try_global(cx)?;
5932        let room_id = active_call.read(cx).room()?.read(cx).id();
5933        self.client
5934            .send(proto::UpdateFollowers {
5935                room_id,
5936                project_id,
5937                variant: Some(update),
5938            })
5939            .log_err()
5940    }
5941
5942    pub async fn handle_follow(
5943        this: Entity<Self>,
5944        envelope: TypedEnvelope<proto::Follow>,
5945        mut cx: AsyncApp,
5946    ) -> Result<proto::FollowResponse> {
5947        this.update(&mut cx, |this, cx| {
5948            let follower = Follower {
5949                project_id: envelope.payload.project_id,
5950                peer_id: envelope.original_sender_id()?,
5951            };
5952
5953            let mut response = proto::FollowResponse::default();
5954            this.workspaces.retain(|workspace| {
5955                workspace
5956                    .update(cx, |workspace, window, cx| {
5957                        let handler_response =
5958                            workspace.handle_follow(follower.project_id, window, cx);
5959                        if let Some(active_view) = handler_response.active_view.clone() {
5960                            if workspace.project.read(cx).remote_id() == follower.project_id {
5961                                response.active_view = Some(active_view)
5962                            }
5963                        }
5964                    })
5965                    .is_ok()
5966            });
5967
5968            Ok(response)
5969        })?
5970    }
5971
5972    async fn handle_update_followers(
5973        this: Entity<Self>,
5974        envelope: TypedEnvelope<proto::UpdateFollowers>,
5975        mut cx: AsyncApp,
5976    ) -> Result<()> {
5977        let leader_id = envelope.original_sender_id()?;
5978        let update = envelope.payload;
5979
5980        this.update(&mut cx, |this, cx| {
5981            this.workspaces.retain(|workspace| {
5982                workspace
5983                    .update(cx, |workspace, window, cx| {
5984                        let project_id = workspace.project.read(cx).remote_id();
5985                        if update.project_id != project_id && update.project_id.is_some() {
5986                            return;
5987                        }
5988                        workspace.handle_update_followers(leader_id, update.clone(), window, cx);
5989                    })
5990                    .is_ok()
5991            });
5992            Ok(())
5993        })?
5994    }
5995}
5996
5997impl ViewId {
5998    pub(crate) fn from_proto(message: proto::ViewId) -> Result<Self> {
5999        Ok(Self {
6000            creator: message
6001                .creator
6002                .ok_or_else(|| anyhow!("creator is missing"))?,
6003            id: message.id,
6004        })
6005    }
6006
6007    pub(crate) fn to_proto(self) -> proto::ViewId {
6008        proto::ViewId {
6009            creator: Some(self.creator),
6010            id: self.id,
6011        }
6012    }
6013}
6014
6015impl FollowerState {
6016    fn pane(&self) -> &Entity<Pane> {
6017        self.dock_pane.as_ref().unwrap_or(&self.center_pane)
6018    }
6019}
6020
6021pub trait WorkspaceHandle {
6022    fn file_project_paths(&self, cx: &App) -> Vec<ProjectPath>;
6023}
6024
6025impl WorkspaceHandle for Entity<Workspace> {
6026    fn file_project_paths(&self, cx: &App) -> Vec<ProjectPath> {
6027        self.read(cx)
6028            .worktrees(cx)
6029            .flat_map(|worktree| {
6030                let worktree_id = worktree.read(cx).id();
6031                worktree.read(cx).files(true, 0).map(move |f| ProjectPath {
6032                    worktree_id,
6033                    path: f.path.clone(),
6034                })
6035            })
6036            .collect::<Vec<_>>()
6037    }
6038}
6039
6040impl std::fmt::Debug for OpenPaths {
6041    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
6042        f.debug_struct("OpenPaths")
6043            .field("paths", &self.paths)
6044            .finish()
6045    }
6046}
6047
6048pub async fn last_opened_workspace_location() -> Option<SerializedWorkspaceLocation> {
6049    DB.last_workspace().await.log_err().flatten()
6050}
6051
6052pub fn last_session_workspace_locations(
6053    last_session_id: &str,
6054    last_session_window_stack: Option<Vec<WindowId>>,
6055) -> Option<Vec<SerializedWorkspaceLocation>> {
6056    DB.last_session_workspace_locations(last_session_id, last_session_window_stack)
6057        .log_err()
6058}
6059
6060actions!(
6061    collab,
6062    [
6063        OpenChannelNotes,
6064        Mute,
6065        Deafen,
6066        LeaveCall,
6067        ShareProject,
6068        ScreenShare
6069    ]
6070);
6071actions!(zed, [OpenLog]);
6072
6073async fn join_channel_internal(
6074    channel_id: ChannelId,
6075    app_state: &Arc<AppState>,
6076    requesting_window: Option<WindowHandle<Workspace>>,
6077    active_call: &Entity<ActiveCall>,
6078    cx: &mut AsyncApp,
6079) -> Result<bool> {
6080    let (should_prompt, open_room) = active_call.update(cx, |active_call, cx| {
6081        let Some(room) = active_call.room().map(|room| room.read(cx)) else {
6082            return (false, None);
6083        };
6084
6085        let already_in_channel = room.channel_id() == Some(channel_id);
6086        let should_prompt = room.is_sharing_project()
6087            && !room.remote_participants().is_empty()
6088            && !already_in_channel;
6089        let open_room = if already_in_channel {
6090            active_call.room().cloned()
6091        } else {
6092            None
6093        };
6094        (should_prompt, open_room)
6095    })?;
6096
6097    if let Some(room) = open_room {
6098        let task = room.update(cx, |room, cx| {
6099            if let Some((project, host)) = room.most_active_project(cx) {
6100                return Some(join_in_room_project(project, host, app_state.clone(), cx));
6101            }
6102
6103            None
6104        })?;
6105        if let Some(task) = task {
6106            task.await?;
6107        }
6108        return anyhow::Ok(true);
6109    }
6110
6111    if should_prompt {
6112        if let Some(workspace) = requesting_window {
6113            let answer = workspace
6114                .update(cx, |_, window, cx| {
6115                    window.prompt(
6116                        PromptLevel::Warning,
6117                        "Do you want to switch channels?",
6118                        Some("Leaving this call will unshare your current project."),
6119                        &["Yes, Join Channel", "Cancel"],
6120                        cx,
6121                    )
6122                })?
6123                .await;
6124
6125            if answer == Ok(1) {
6126                return Ok(false);
6127            }
6128        } else {
6129            return Ok(false); // unreachable!() hopefully
6130        }
6131    }
6132
6133    let client = cx.update(|cx| active_call.read(cx).client())?;
6134
6135    let mut client_status = client.status();
6136
6137    // this loop will terminate within client::CONNECTION_TIMEOUT seconds.
6138    'outer: loop {
6139        let Some(status) = client_status.recv().await else {
6140            return Err(anyhow!("error connecting"));
6141        };
6142
6143        match status {
6144            Status::Connecting
6145            | Status::Authenticating
6146            | Status::Reconnecting
6147            | Status::Reauthenticating => continue,
6148            Status::Connected { .. } => break 'outer,
6149            Status::SignedOut => return Err(ErrorCode::SignedOut.into()),
6150            Status::UpgradeRequired => return Err(ErrorCode::UpgradeRequired.into()),
6151            Status::ConnectionError | Status::ConnectionLost | Status::ReconnectionError { .. } => {
6152                return Err(ErrorCode::Disconnected.into());
6153            }
6154        }
6155    }
6156
6157    let room = active_call
6158        .update(cx, |active_call, cx| {
6159            active_call.join_channel(channel_id, cx)
6160        })?
6161        .await?;
6162
6163    let Some(room) = room else {
6164        return anyhow::Ok(true);
6165    };
6166
6167    room.update(cx, |room, _| room.room_update_completed())?
6168        .await;
6169
6170    let task = room.update(cx, |room, cx| {
6171        if let Some((project, host)) = room.most_active_project(cx) {
6172            return Some(join_in_room_project(project, host, app_state.clone(), cx));
6173        }
6174
6175        // If you are the first to join a channel, see if you should share your project.
6176        if room.remote_participants().is_empty() && !room.local_participant_is_guest() {
6177            if let Some(workspace) = requesting_window {
6178                let project = workspace.update(cx, |workspace, _, cx| {
6179                    let project = workspace.project.read(cx);
6180
6181                    if !CallSettings::get_global(cx).share_on_join {
6182                        return None;
6183                    }
6184
6185                    if (project.is_local() || project.is_via_ssh())
6186                        && project.visible_worktrees(cx).any(|tree| {
6187                            tree.read(cx)
6188                                .root_entry()
6189                                .map_or(false, |entry| entry.is_dir())
6190                        })
6191                    {
6192                        Some(workspace.project.clone())
6193                    } else {
6194                        None
6195                    }
6196                });
6197                if let Ok(Some(project)) = project {
6198                    return Some(cx.spawn(async move |room, cx| {
6199                        room.update(cx, |room, cx| room.share_project(project, cx))?
6200                            .await?;
6201                        Ok(())
6202                    }));
6203                }
6204            }
6205        }
6206
6207        None
6208    })?;
6209    if let Some(task) = task {
6210        task.await?;
6211        return anyhow::Ok(true);
6212    }
6213    anyhow::Ok(false)
6214}
6215
6216pub fn join_channel(
6217    channel_id: ChannelId,
6218    app_state: Arc<AppState>,
6219    requesting_window: Option<WindowHandle<Workspace>>,
6220    cx: &mut App,
6221) -> Task<Result<()>> {
6222    let active_call = ActiveCall::global(cx);
6223    cx.spawn(async move |cx| {
6224        let result = join_channel_internal(
6225            channel_id,
6226            &app_state,
6227            requesting_window,
6228            &active_call,
6229             cx,
6230        )
6231            .await;
6232
6233        // join channel succeeded, and opened a window
6234        if matches!(result, Ok(true)) {
6235            return anyhow::Ok(());
6236        }
6237
6238        // find an existing workspace to focus and show call controls
6239        let mut active_window =
6240            requesting_window.or_else(|| activate_any_workspace_window( cx));
6241        if active_window.is_none() {
6242            // no open workspaces, make one to show the error in (blergh)
6243            let (window_handle, _) = cx
6244                .update(|cx| {
6245                    Workspace::new_local(vec![], app_state.clone(), requesting_window, None, cx)
6246                })?
6247                .await?;
6248
6249            if result.is_ok() {
6250                cx.update(|cx| {
6251                    cx.dispatch_action(&OpenChannelNotes);
6252                }).log_err();
6253            }
6254
6255            active_window = Some(window_handle);
6256        }
6257
6258        if let Err(err) = result {
6259            log::error!("failed to join channel: {}", err);
6260            if let Some(active_window) = active_window {
6261                active_window
6262                    .update(cx, |_, window, cx| {
6263                        let detail: SharedString = match err.error_code() {
6264                            ErrorCode::SignedOut => {
6265                                "Please sign in to continue.".into()
6266                            }
6267                            ErrorCode::UpgradeRequired => {
6268                                "Your are running an unsupported version of Zed. Please update to continue.".into()
6269                            }
6270                            ErrorCode::NoSuchChannel => {
6271                                "No matching channel was found. Please check the link and try again.".into()
6272                            }
6273                            ErrorCode::Forbidden => {
6274                                "This channel is private, and you do not have access. Please ask someone to add you and try again.".into()
6275                            }
6276                            ErrorCode::Disconnected => "Please check your internet connection and try again.".into(),
6277                            _ => format!("{}\n\nPlease try again.", err).into(),
6278                        };
6279                        window.prompt(
6280                            PromptLevel::Critical,
6281                            "Failed to join channel",
6282                            Some(&detail),
6283                            &["Ok"],
6284                        cx)
6285                    })?
6286                    .await
6287                    .ok();
6288            }
6289        }
6290
6291        // return ok, we showed the error to the user.
6292        anyhow::Ok(())
6293    })
6294}
6295
6296pub async fn get_any_active_workspace(
6297    app_state: Arc<AppState>,
6298    mut cx: AsyncApp,
6299) -> anyhow::Result<WindowHandle<Workspace>> {
6300    // find an existing workspace to focus and show call controls
6301    let active_window = activate_any_workspace_window(&mut cx);
6302    if active_window.is_none() {
6303        cx.update(|cx| Workspace::new_local(vec![], app_state.clone(), None, None, cx))?
6304            .await?;
6305    }
6306    activate_any_workspace_window(&mut cx).context("could not open zed")
6307}
6308
6309fn activate_any_workspace_window(cx: &mut AsyncApp) -> Option<WindowHandle<Workspace>> {
6310    cx.update(|cx| {
6311        if let Some(workspace_window) = cx
6312            .active_window()
6313            .and_then(|window| window.downcast::<Workspace>())
6314        {
6315            return Some(workspace_window);
6316        }
6317
6318        for window in cx.windows() {
6319            if let Some(workspace_window) = window.downcast::<Workspace>() {
6320                workspace_window
6321                    .update(cx, |_, window, _| window.activate_window())
6322                    .ok();
6323                return Some(workspace_window);
6324            }
6325        }
6326        None
6327    })
6328    .ok()
6329    .flatten()
6330}
6331
6332pub fn local_workspace_windows(cx: &App) -> Vec<WindowHandle<Workspace>> {
6333    cx.windows()
6334        .into_iter()
6335        .filter_map(|window| window.downcast::<Workspace>())
6336        .filter(|workspace| {
6337            workspace
6338                .read(cx)
6339                .is_ok_and(|workspace| workspace.project.read(cx).is_local())
6340        })
6341        .collect()
6342}
6343
6344#[derive(Default)]
6345pub struct OpenOptions {
6346    pub visible: Option<OpenVisible>,
6347    pub focus: Option<bool>,
6348    pub open_new_workspace: Option<bool>,
6349    pub replace_window: Option<WindowHandle<Workspace>>,
6350    pub env: Option<HashMap<String, String>>,
6351}
6352
6353#[allow(clippy::type_complexity)]
6354pub fn open_paths(
6355    abs_paths: &[PathBuf],
6356    app_state: Arc<AppState>,
6357    open_options: OpenOptions,
6358    cx: &mut App,
6359) -> Task<
6360    anyhow::Result<(
6361        WindowHandle<Workspace>,
6362        Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
6363    )>,
6364> {
6365    let abs_paths = abs_paths.to_vec();
6366    let mut existing = None;
6367    let mut best_match = None;
6368    let mut open_visible = OpenVisible::All;
6369
6370    cx.spawn(async move |cx| {
6371        if open_options.open_new_workspace != Some(true) {
6372            let all_paths = abs_paths.iter().map(|path| app_state.fs.metadata(path));
6373            let all_metadatas = futures::future::join_all(all_paths)
6374                .await
6375                .into_iter()
6376                .filter_map(|result| result.ok().flatten())
6377                .collect::<Vec<_>>();
6378
6379            cx.update(|cx| {
6380                for window in local_workspace_windows(&cx) {
6381                    if let Ok(workspace) = window.read(&cx) {
6382                        let m = workspace.project.read(&cx).visibility_for_paths(
6383                            &abs_paths,
6384                            &all_metadatas,
6385                            open_options.open_new_workspace == None,
6386                            cx,
6387                        );
6388                        if m > best_match {
6389                            existing = Some(window);
6390                            best_match = m;
6391                        } else if best_match.is_none()
6392                            && open_options.open_new_workspace == Some(false)
6393                        {
6394                            existing = Some(window)
6395                        }
6396                    }
6397                }
6398            })?;
6399
6400            if open_options.open_new_workspace.is_none() && existing.is_none() {
6401                if all_metadatas.iter().all(|file| !file.is_dir) {
6402                    cx.update(|cx| {
6403                        if let Some(window) = cx
6404                            .active_window()
6405                            .and_then(|window| window.downcast::<Workspace>())
6406                        {
6407                            if let Ok(workspace) = window.read(cx) {
6408                                let project = workspace.project().read(cx);
6409                                if project.is_local() && !project.is_via_collab() {
6410                                    existing = Some(window);
6411                                    open_visible = OpenVisible::None;
6412                                    return;
6413                                }
6414                            }
6415                        }
6416                        for window in local_workspace_windows(cx) {
6417                            if let Ok(workspace) = window.read(cx) {
6418                                let project = workspace.project().read(cx);
6419                                if project.is_via_collab() {
6420                                    continue;
6421                                }
6422                                existing = Some(window);
6423                                open_visible = OpenVisible::None;
6424                                break;
6425                            }
6426                        }
6427                    })?;
6428                }
6429            }
6430        }
6431
6432        if let Some(existing) = existing {
6433            let open_task = existing
6434                .update(cx, |workspace, window, cx| {
6435                    window.activate_window();
6436                    workspace.open_paths(
6437                        abs_paths,
6438                        OpenOptions {
6439                            visible: Some(open_visible),
6440                            ..Default::default()
6441                        },
6442                        None,
6443                        window,
6444                        cx,
6445                    )
6446                })?
6447                .await;
6448
6449            _ = existing.update(cx, |workspace, _, cx| {
6450                for item in open_task.iter().flatten() {
6451                    if let Err(e) = item {
6452                        workspace.show_error(&e, cx);
6453                    }
6454                }
6455            });
6456
6457            Ok((existing, open_task))
6458        } else {
6459            cx.update(move |cx| {
6460                Workspace::new_local(
6461                    abs_paths,
6462                    app_state.clone(),
6463                    open_options.replace_window,
6464                    open_options.env,
6465                    cx,
6466                )
6467            })?
6468            .await
6469        }
6470    })
6471}
6472
6473pub fn open_new(
6474    open_options: OpenOptions,
6475    app_state: Arc<AppState>,
6476    cx: &mut App,
6477    init: impl FnOnce(&mut Workspace, &mut Window, &mut Context<Workspace>) + 'static + Send,
6478) -> Task<anyhow::Result<()>> {
6479    let task = Workspace::new_local(Vec::new(), app_state, None, open_options.env, cx);
6480    cx.spawn(async move |cx| {
6481        let (workspace, opened_paths) = task.await?;
6482        workspace.update(cx, |workspace, window, cx| {
6483            if opened_paths.is_empty() {
6484                init(workspace, window, cx)
6485            }
6486        })?;
6487        Ok(())
6488    })
6489}
6490
6491pub fn create_and_open_local_file(
6492    path: &'static Path,
6493    window: &mut Window,
6494    cx: &mut Context<Workspace>,
6495    default_content: impl 'static + Send + FnOnce() -> Rope,
6496) -> Task<Result<Box<dyn ItemHandle>>> {
6497    cx.spawn_in(window, async move |workspace, cx| {
6498        let fs = workspace.update(cx, |workspace, _| workspace.app_state().fs.clone())?;
6499        if !fs.is_file(path).await {
6500            fs.create_file(path, Default::default()).await?;
6501            fs.save(path, &default_content(), Default::default())
6502                .await?;
6503        }
6504
6505        let mut items = workspace
6506            .update_in(cx, |workspace, window, cx| {
6507                workspace.with_local_workspace(window, cx, |workspace, window, cx| {
6508                    workspace.open_paths(
6509                        vec![path.to_path_buf()],
6510                        OpenOptions {
6511                            visible: Some(OpenVisible::None),
6512                            ..Default::default()
6513                        },
6514                        None,
6515                        window,
6516                        cx,
6517                    )
6518                })
6519            })?
6520            .await?
6521            .await;
6522
6523        let item = items.pop().flatten();
6524        item.ok_or_else(|| anyhow!("path {path:?} is not a file"))?
6525    })
6526}
6527
6528pub fn open_ssh_project_with_new_connection(
6529    window: WindowHandle<Workspace>,
6530    connection_options: SshConnectionOptions,
6531    cancel_rx: oneshot::Receiver<()>,
6532    delegate: Arc<dyn SshClientDelegate>,
6533    app_state: Arc<AppState>,
6534    paths: Vec<PathBuf>,
6535    cx: &mut App,
6536) -> Task<Result<()>> {
6537    cx.spawn(async move |cx| {
6538        let (serialized_ssh_project, workspace_id, serialized_workspace) =
6539            serialize_ssh_project(connection_options.clone(), paths.clone(), &cx).await?;
6540
6541        let session = match cx
6542            .update(|cx| {
6543                remote::SshRemoteClient::new(
6544                    ConnectionIdentifier::Workspace(workspace_id.0),
6545                    connection_options,
6546                    cancel_rx,
6547                    delegate,
6548                    cx,
6549                )
6550            })?
6551            .await?
6552        {
6553            Some(result) => result,
6554            None => return Ok(()),
6555        };
6556
6557        let project = cx.update(|cx| {
6558            project::Project::ssh(
6559                session,
6560                app_state.client.clone(),
6561                app_state.node_runtime.clone(),
6562                app_state.user_store.clone(),
6563                app_state.languages.clone(),
6564                app_state.fs.clone(),
6565                cx,
6566            )
6567        })?;
6568
6569        open_ssh_project_inner(
6570            project,
6571            paths,
6572            serialized_ssh_project,
6573            workspace_id,
6574            serialized_workspace,
6575            app_state,
6576            window,
6577            cx,
6578        )
6579        .await
6580    })
6581}
6582
6583pub fn open_ssh_project_with_existing_connection(
6584    connection_options: SshConnectionOptions,
6585    project: Entity<Project>,
6586    paths: Vec<PathBuf>,
6587    app_state: Arc<AppState>,
6588    window: WindowHandle<Workspace>,
6589    cx: &mut AsyncApp,
6590) -> Task<Result<()>> {
6591    cx.spawn(async move |cx| {
6592        let (serialized_ssh_project, workspace_id, serialized_workspace) =
6593            serialize_ssh_project(connection_options.clone(), paths.clone(), &cx).await?;
6594
6595        open_ssh_project_inner(
6596            project,
6597            paths,
6598            serialized_ssh_project,
6599            workspace_id,
6600            serialized_workspace,
6601            app_state,
6602            window,
6603            cx,
6604        )
6605        .await
6606    })
6607}
6608
6609async fn open_ssh_project_inner(
6610    project: Entity<Project>,
6611    paths: Vec<PathBuf>,
6612    serialized_ssh_project: SerializedSshProject,
6613    workspace_id: WorkspaceId,
6614    serialized_workspace: Option<SerializedWorkspace>,
6615    app_state: Arc<AppState>,
6616    window: WindowHandle<Workspace>,
6617    cx: &mut AsyncApp,
6618) -> Result<()> {
6619    let toolchains = DB.toolchains(workspace_id).await?;
6620    for (toolchain, worktree_id, path) in toolchains {
6621        project
6622            .update(cx, |this, cx| {
6623                this.activate_toolchain(ProjectPath { worktree_id, path }, toolchain, cx)
6624            })?
6625            .await;
6626    }
6627    let mut project_paths_to_open = vec![];
6628    let mut project_path_errors = vec![];
6629
6630    for path in paths {
6631        let result = cx
6632            .update(|cx| Workspace::project_path_for_path(project.clone(), &path, true, cx))?
6633            .await;
6634        match result {
6635            Ok((_, project_path)) => {
6636                project_paths_to_open.push((path.clone(), Some(project_path)));
6637            }
6638            Err(error) => {
6639                project_path_errors.push(error);
6640            }
6641        };
6642    }
6643
6644    if project_paths_to_open.is_empty() {
6645        return Err(project_path_errors
6646            .pop()
6647            .unwrap_or_else(|| anyhow!("no paths given")));
6648    }
6649
6650    cx.update_window(window.into(), |_, window, cx| {
6651        window.replace_root(cx, |window, cx| {
6652            telemetry::event!("SSH Project Opened");
6653
6654            let mut workspace =
6655                Workspace::new(Some(workspace_id), project, app_state.clone(), window, cx);
6656            workspace.set_serialized_ssh_project(serialized_ssh_project);
6657            workspace.update_history(cx);
6658            workspace
6659        });
6660    })?;
6661
6662    window
6663        .update(cx, |_, window, cx| {
6664            window.activate_window();
6665            open_items(serialized_workspace, project_paths_to_open, window, cx)
6666        })?
6667        .await?;
6668
6669    window.update(cx, |workspace, _, cx| {
6670        for error in project_path_errors {
6671            if error.error_code() == proto::ErrorCode::DevServerProjectPathDoesNotExist {
6672                if let Some(path) = error.error_tag("path") {
6673                    workspace.show_error(&anyhow!("'{path}' does not exist"), cx)
6674                }
6675            } else {
6676                workspace.show_error(&error, cx)
6677            }
6678        }
6679    })?;
6680
6681    Ok(())
6682}
6683
6684fn serialize_ssh_project(
6685    connection_options: SshConnectionOptions,
6686    paths: Vec<PathBuf>,
6687    cx: &AsyncApp,
6688) -> Task<
6689    Result<(
6690        SerializedSshProject,
6691        WorkspaceId,
6692        Option<SerializedWorkspace>,
6693    )>,
6694> {
6695    cx.background_spawn(async move {
6696        let serialized_ssh_project = persistence::DB
6697            .get_or_create_ssh_project(
6698                connection_options.host.clone(),
6699                connection_options.port,
6700                paths
6701                    .iter()
6702                    .map(|path| path.to_string_lossy().to_string())
6703                    .collect::<Vec<_>>(),
6704                connection_options.username.clone(),
6705            )
6706            .await?;
6707
6708        let serialized_workspace =
6709            persistence::DB.workspace_for_ssh_project(&serialized_ssh_project);
6710
6711        let workspace_id = if let Some(workspace_id) =
6712            serialized_workspace.as_ref().map(|workspace| workspace.id)
6713        {
6714            workspace_id
6715        } else {
6716            persistence::DB.next_id().await?
6717        };
6718
6719        Ok((serialized_ssh_project, workspace_id, serialized_workspace))
6720    })
6721}
6722
6723pub fn join_in_room_project(
6724    project_id: u64,
6725    follow_user_id: u64,
6726    app_state: Arc<AppState>,
6727    cx: &mut App,
6728) -> Task<Result<()>> {
6729    let windows = cx.windows();
6730    cx.spawn(async move |cx| {
6731        let existing_workspace = windows.into_iter().find_map(|window_handle| {
6732            window_handle
6733                .downcast::<Workspace>()
6734                .and_then(|window_handle| {
6735                    window_handle
6736                        .update(cx, |workspace, _window, cx| {
6737                            if workspace.project().read(cx).remote_id() == Some(project_id) {
6738                                Some(window_handle)
6739                            } else {
6740                                None
6741                            }
6742                        })
6743                        .unwrap_or(None)
6744                })
6745        });
6746
6747        let workspace = if let Some(existing_workspace) = existing_workspace {
6748            existing_workspace
6749        } else {
6750            let active_call = cx.update(|cx| ActiveCall::global(cx))?;
6751            let room = active_call
6752                .read_with(cx, |call, _| call.room().cloned())?
6753                .ok_or_else(|| anyhow!("not in a call"))?;
6754            let project = room
6755                .update(cx, |room, cx| {
6756                    room.join_project(
6757                        project_id,
6758                        app_state.languages.clone(),
6759                        app_state.fs.clone(),
6760                        cx,
6761                    )
6762                })?
6763                .await?;
6764
6765            let window_bounds_override = window_bounds_env_override();
6766            cx.update(|cx| {
6767                let mut options = (app_state.build_window_options)(None, cx);
6768                options.window_bounds = window_bounds_override.map(WindowBounds::Windowed);
6769                cx.open_window(options, |window, cx| {
6770                    cx.new(|cx| {
6771                        Workspace::new(Default::default(), project, app_state.clone(), window, cx)
6772                    })
6773                })
6774            })??
6775        };
6776
6777        workspace.update(cx, |workspace, window, cx| {
6778            cx.activate(true);
6779            window.activate_window();
6780
6781            if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
6782                let follow_peer_id = room
6783                    .read(cx)
6784                    .remote_participants()
6785                    .iter()
6786                    .find(|(_, participant)| participant.user.id == follow_user_id)
6787                    .map(|(_, p)| p.peer_id)
6788                    .or_else(|| {
6789                        // If we couldn't follow the given user, follow the host instead.
6790                        let collaborator = workspace
6791                            .project()
6792                            .read(cx)
6793                            .collaborators()
6794                            .values()
6795                            .find(|collaborator| collaborator.is_host)?;
6796                        Some(collaborator.peer_id)
6797                    });
6798
6799                if let Some(follow_peer_id) = follow_peer_id {
6800                    workspace.follow(follow_peer_id, window, cx);
6801                }
6802            }
6803        })?;
6804
6805        anyhow::Ok(())
6806    })
6807}
6808
6809pub fn reload(reload: &Reload, cx: &mut App) {
6810    let should_confirm = WorkspaceSettings::get_global(cx).confirm_quit;
6811    let mut workspace_windows = cx
6812        .windows()
6813        .into_iter()
6814        .filter_map(|window| window.downcast::<Workspace>())
6815        .collect::<Vec<_>>();
6816
6817    // If multiple windows have unsaved changes, and need a save prompt,
6818    // prompt in the active window before switching to a different window.
6819    workspace_windows.sort_by_key(|window| window.is_active(cx) == Some(false));
6820
6821    let mut prompt = None;
6822    if let (true, Some(window)) = (should_confirm, workspace_windows.first()) {
6823        prompt = window
6824            .update(cx, |_, window, cx| {
6825                window.prompt(
6826                    PromptLevel::Info,
6827                    "Are you sure you want to restart?",
6828                    None,
6829                    &["Restart", "Cancel"],
6830                    cx,
6831                )
6832            })
6833            .ok();
6834    }
6835
6836    let binary_path = reload.binary_path.clone();
6837    cx.spawn(async move |cx| {
6838        if let Some(prompt) = prompt {
6839            let answer = prompt.await?;
6840            if answer != 0 {
6841                return Ok(());
6842            }
6843        }
6844
6845        // If the user cancels any save prompt, then keep the app open.
6846        for window in workspace_windows {
6847            if let Ok(should_close) = window.update(cx, |workspace, window, cx| {
6848                workspace.prepare_to_close(CloseIntent::Quit, window, cx)
6849            }) {
6850                if !should_close.await? {
6851                    return Ok(());
6852                }
6853            }
6854        }
6855
6856        cx.update(|cx| cx.restart(binary_path))
6857    })
6858    .detach_and_log_err(cx);
6859}
6860
6861fn parse_pixel_position_env_var(value: &str) -> Option<Point<Pixels>> {
6862    let mut parts = value.split(',');
6863    let x: usize = parts.next()?.parse().ok()?;
6864    let y: usize = parts.next()?.parse().ok()?;
6865    Some(point(px(x as f32), px(y as f32)))
6866}
6867
6868fn parse_pixel_size_env_var(value: &str) -> Option<Size<Pixels>> {
6869    let mut parts = value.split(',');
6870    let width: usize = parts.next()?.parse().ok()?;
6871    let height: usize = parts.next()?.parse().ok()?;
6872    Some(size(px(width as f32), px(height as f32)))
6873}
6874
6875pub fn client_side_decorations(
6876    element: impl IntoElement,
6877    window: &mut Window,
6878    cx: &mut App,
6879) -> Stateful<Div> {
6880    const BORDER_SIZE: Pixels = px(1.0);
6881    let decorations = window.window_decorations();
6882
6883    if matches!(decorations, Decorations::Client { .. }) {
6884        window.set_client_inset(theme::CLIENT_SIDE_DECORATION_SHADOW);
6885    }
6886
6887    struct GlobalResizeEdge(ResizeEdge);
6888    impl Global for GlobalResizeEdge {}
6889
6890    div()
6891        .id("window-backdrop")
6892        .bg(transparent_black())
6893        .map(|div| match decorations {
6894            Decorations::Server => div,
6895            Decorations::Client { tiling, .. } => div
6896                .when(!(tiling.top || tiling.right), |div| {
6897                    div.rounded_tr(theme::CLIENT_SIDE_DECORATION_ROUNDING)
6898                })
6899                .when(!(tiling.top || tiling.left), |div| {
6900                    div.rounded_tl(theme::CLIENT_SIDE_DECORATION_ROUNDING)
6901                })
6902                .when(!(tiling.bottom || tiling.right), |div| {
6903                    div.rounded_br(theme::CLIENT_SIDE_DECORATION_ROUNDING)
6904                })
6905                .when(!(tiling.bottom || tiling.left), |div| {
6906                    div.rounded_bl(theme::CLIENT_SIDE_DECORATION_ROUNDING)
6907                })
6908                .when(!tiling.top, |div| {
6909                    div.pt(theme::CLIENT_SIDE_DECORATION_SHADOW)
6910                })
6911                .when(!tiling.bottom, |div| {
6912                    div.pb(theme::CLIENT_SIDE_DECORATION_SHADOW)
6913                })
6914                .when(!tiling.left, |div| {
6915                    div.pl(theme::CLIENT_SIDE_DECORATION_SHADOW)
6916                })
6917                .when(!tiling.right, |div| {
6918                    div.pr(theme::CLIENT_SIDE_DECORATION_SHADOW)
6919                })
6920                .on_mouse_move(move |e, window, cx| {
6921                    let size = window.window_bounds().get_bounds().size;
6922                    let pos = e.position;
6923
6924                    let new_edge =
6925                        resize_edge(pos, theme::CLIENT_SIDE_DECORATION_SHADOW, size, tiling);
6926
6927                    let edge = cx.try_global::<GlobalResizeEdge>();
6928                    if new_edge != edge.map(|edge| edge.0) {
6929                        window
6930                            .window_handle()
6931                            .update(cx, |workspace, _, cx| {
6932                                cx.notify(workspace.entity_id());
6933                            })
6934                            .ok();
6935                    }
6936                })
6937                .on_mouse_down(MouseButton::Left, move |e, window, _| {
6938                    let size = window.window_bounds().get_bounds().size;
6939                    let pos = e.position;
6940
6941                    let edge = match resize_edge(
6942                        pos,
6943                        theme::CLIENT_SIDE_DECORATION_SHADOW,
6944                        size,
6945                        tiling,
6946                    ) {
6947                        Some(value) => value,
6948                        None => return,
6949                    };
6950
6951                    window.start_window_resize(edge);
6952                }),
6953        })
6954        .size_full()
6955        .child(
6956            div()
6957                .cursor(CursorStyle::Arrow)
6958                .map(|div| match decorations {
6959                    Decorations::Server => div,
6960                    Decorations::Client { tiling } => div
6961                        .border_color(cx.theme().colors().border)
6962                        .when(!(tiling.top || tiling.right), |div| {
6963                            div.rounded_tr(theme::CLIENT_SIDE_DECORATION_ROUNDING)
6964                        })
6965                        .when(!(tiling.top || tiling.left), |div| {
6966                            div.rounded_tl(theme::CLIENT_SIDE_DECORATION_ROUNDING)
6967                        })
6968                        .when(!(tiling.bottom || tiling.right), |div| {
6969                            div.rounded_br(theme::CLIENT_SIDE_DECORATION_ROUNDING)
6970                        })
6971                        .when(!(tiling.bottom || tiling.left), |div| {
6972                            div.rounded_bl(theme::CLIENT_SIDE_DECORATION_ROUNDING)
6973                        })
6974                        .when(!tiling.top, |div| div.border_t(BORDER_SIZE))
6975                        .when(!tiling.bottom, |div| div.border_b(BORDER_SIZE))
6976                        .when(!tiling.left, |div| div.border_l(BORDER_SIZE))
6977                        .when(!tiling.right, |div| div.border_r(BORDER_SIZE))
6978                        .when(!tiling.is_tiled(), |div| {
6979                            div.shadow(smallvec::smallvec![gpui::BoxShadow {
6980                                color: Hsla {
6981                                    h: 0.,
6982                                    s: 0.,
6983                                    l: 0.,
6984                                    a: 0.4,
6985                                },
6986                                blur_radius: theme::CLIENT_SIDE_DECORATION_SHADOW / 2.,
6987                                spread_radius: px(0.),
6988                                offset: point(px(0.0), px(0.0)),
6989                            }])
6990                        }),
6991                })
6992                .on_mouse_move(|_e, _, cx| {
6993                    cx.stop_propagation();
6994                })
6995                .size_full()
6996                .child(element),
6997        )
6998        .map(|div| match decorations {
6999            Decorations::Server => div,
7000            Decorations::Client { tiling, .. } => div.child(
7001                canvas(
7002                    |_bounds, window, _| {
7003                        window.insert_hitbox(
7004                            Bounds::new(
7005                                point(px(0.0), px(0.0)),
7006                                window.window_bounds().get_bounds().size,
7007                            ),
7008                            false,
7009                        )
7010                    },
7011                    move |_bounds, hitbox, window, cx| {
7012                        let mouse = window.mouse_position();
7013                        let size = window.window_bounds().get_bounds().size;
7014                        let Some(edge) =
7015                            resize_edge(mouse, theme::CLIENT_SIDE_DECORATION_SHADOW, size, tiling)
7016                        else {
7017                            return;
7018                        };
7019                        cx.set_global(GlobalResizeEdge(edge));
7020                        window.set_cursor_style(
7021                            match edge {
7022                                ResizeEdge::Top | ResizeEdge::Bottom => CursorStyle::ResizeUpDown,
7023                                ResizeEdge::Left | ResizeEdge::Right => {
7024                                    CursorStyle::ResizeLeftRight
7025                                }
7026                                ResizeEdge::TopLeft | ResizeEdge::BottomRight => {
7027                                    CursorStyle::ResizeUpLeftDownRight
7028                                }
7029                                ResizeEdge::TopRight | ResizeEdge::BottomLeft => {
7030                                    CursorStyle::ResizeUpRightDownLeft
7031                                }
7032                            },
7033                            Some(&hitbox),
7034                        );
7035                    },
7036                )
7037                .size_full()
7038                .absolute(),
7039            ),
7040        })
7041}
7042
7043fn resize_edge(
7044    pos: Point<Pixels>,
7045    shadow_size: Pixels,
7046    window_size: Size<Pixels>,
7047    tiling: Tiling,
7048) -> Option<ResizeEdge> {
7049    let bounds = Bounds::new(Point::default(), window_size).inset(shadow_size * 1.5);
7050    if bounds.contains(&pos) {
7051        return None;
7052    }
7053
7054    let corner_size = size(shadow_size * 1.5, shadow_size * 1.5);
7055    let top_left_bounds = Bounds::new(Point::new(px(0.), px(0.)), corner_size);
7056    if !tiling.top && top_left_bounds.contains(&pos) {
7057        return Some(ResizeEdge::TopLeft);
7058    }
7059
7060    let top_right_bounds = Bounds::new(
7061        Point::new(window_size.width - corner_size.width, px(0.)),
7062        corner_size,
7063    );
7064    if !tiling.top && top_right_bounds.contains(&pos) {
7065        return Some(ResizeEdge::TopRight);
7066    }
7067
7068    let bottom_left_bounds = Bounds::new(
7069        Point::new(px(0.), window_size.height - corner_size.height),
7070        corner_size,
7071    );
7072    if !tiling.bottom && bottom_left_bounds.contains(&pos) {
7073        return Some(ResizeEdge::BottomLeft);
7074    }
7075
7076    let bottom_right_bounds = Bounds::new(
7077        Point::new(
7078            window_size.width - corner_size.width,
7079            window_size.height - corner_size.height,
7080        ),
7081        corner_size,
7082    );
7083    if !tiling.bottom && bottom_right_bounds.contains(&pos) {
7084        return Some(ResizeEdge::BottomRight);
7085    }
7086
7087    if !tiling.top && pos.y < shadow_size {
7088        Some(ResizeEdge::Top)
7089    } else if !tiling.bottom && pos.y > window_size.height - shadow_size {
7090        Some(ResizeEdge::Bottom)
7091    } else if !tiling.left && pos.x < shadow_size {
7092        Some(ResizeEdge::Left)
7093    } else if !tiling.right && pos.x > window_size.width - shadow_size {
7094        Some(ResizeEdge::Right)
7095    } else {
7096        None
7097    }
7098}
7099
7100fn join_pane_into_active(
7101    active_pane: &Entity<Pane>,
7102    pane: &Entity<Pane>,
7103    window: &mut Window,
7104    cx: &mut App,
7105) {
7106    if pane == active_pane {
7107        return;
7108    } else if pane.read(cx).items_len() == 0 {
7109        pane.update(cx, |_, cx| {
7110            cx.emit(pane::Event::Remove {
7111                focus_on_pane: None,
7112            });
7113        })
7114    } else {
7115        move_all_items(pane, active_pane, window, cx);
7116    }
7117}
7118
7119fn move_all_items(
7120    from_pane: &Entity<Pane>,
7121    to_pane: &Entity<Pane>,
7122    window: &mut Window,
7123    cx: &mut App,
7124) {
7125    let destination_is_different = from_pane != to_pane;
7126    let mut moved_items = 0;
7127    for (item_ix, item_handle) in from_pane
7128        .read(cx)
7129        .items()
7130        .enumerate()
7131        .map(|(ix, item)| (ix, item.clone()))
7132        .collect::<Vec<_>>()
7133    {
7134        let ix = item_ix - moved_items;
7135        if destination_is_different {
7136            // Close item from previous pane
7137            from_pane.update(cx, |source, cx| {
7138                source.remove_item_and_focus_on_pane(ix, false, to_pane.clone(), window, cx);
7139            });
7140            moved_items += 1;
7141        }
7142
7143        // This automatically removes duplicate items in the pane
7144        to_pane.update(cx, |destination, cx| {
7145            destination.add_item(item_handle, true, true, None, window, cx);
7146            window.focus(&destination.focus_handle(cx))
7147        });
7148    }
7149}
7150
7151pub fn move_item(
7152    source: &Entity<Pane>,
7153    destination: &Entity<Pane>,
7154    item_id_to_move: EntityId,
7155    destination_index: usize,
7156    window: &mut Window,
7157    cx: &mut App,
7158) {
7159    let Some((item_ix, item_handle)) = source
7160        .read(cx)
7161        .items()
7162        .enumerate()
7163        .find(|(_, item_handle)| item_handle.item_id() == item_id_to_move)
7164        .map(|(ix, item)| (ix, item.clone()))
7165    else {
7166        // Tab was closed during drag
7167        return;
7168    };
7169
7170    if source != destination {
7171        // Close item from previous pane
7172        source.update(cx, |source, cx| {
7173            source.remove_item_and_focus_on_pane(item_ix, false, destination.clone(), window, cx);
7174        });
7175    }
7176
7177    // This automatically removes duplicate items in the pane
7178    destination.update(cx, |destination, cx| {
7179        destination.add_item(item_handle, true, true, Some(destination_index), window, cx);
7180        window.focus(&destination.focus_handle(cx))
7181    });
7182}
7183
7184pub fn move_active_item(
7185    source: &Entity<Pane>,
7186    destination: &Entity<Pane>,
7187    focus_destination: bool,
7188    close_if_empty: bool,
7189    window: &mut Window,
7190    cx: &mut App,
7191) {
7192    if source == destination {
7193        return;
7194    }
7195    let Some(active_item) = source.read(cx).active_item() else {
7196        return;
7197    };
7198    source.update(cx, |source_pane, cx| {
7199        let item_id = active_item.item_id();
7200        source_pane.remove_item(item_id, false, close_if_empty, window, cx);
7201        destination.update(cx, |target_pane, cx| {
7202            target_pane.add_item(
7203                active_item,
7204                focus_destination,
7205                focus_destination,
7206                Some(target_pane.items_len()),
7207                window,
7208                cx,
7209            );
7210        });
7211    });
7212}
7213
7214#[cfg(test)]
7215mod tests {
7216    use std::{cell::RefCell, rc::Rc};
7217
7218    use super::*;
7219    use crate::{
7220        dock::{PanelEvent, test::TestPanel},
7221        item::{
7222            ItemEvent,
7223            test::{TestItem, TestProjectItem},
7224        },
7225    };
7226    use fs::FakeFs;
7227    use gpui::{
7228        DismissEvent, Empty, EventEmitter, FocusHandle, Focusable, Render, TestAppContext,
7229        UpdateGlobal, VisualTestContext, px,
7230    };
7231    use project::{Project, ProjectEntryId};
7232    use serde_json::json;
7233    use settings::SettingsStore;
7234
7235    #[gpui::test]
7236    async fn test_tab_disambiguation(cx: &mut TestAppContext) {
7237        init_test(cx);
7238
7239        let fs = FakeFs::new(cx.executor());
7240        let project = Project::test(fs, [], cx).await;
7241        let (workspace, cx) =
7242            cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
7243
7244        // Adding an item with no ambiguity renders the tab without detail.
7245        let item1 = cx.new(|cx| {
7246            let mut item = TestItem::new(cx);
7247            item.tab_descriptions = Some(vec!["c", "b1/c", "a/b1/c"]);
7248            item
7249        });
7250        workspace.update_in(cx, |workspace, window, cx| {
7251            workspace.add_item_to_active_pane(Box::new(item1.clone()), None, true, window, cx);
7252        });
7253        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(0)));
7254
7255        // Adding an item that creates ambiguity increases the level of detail on
7256        // both tabs.
7257        let item2 = cx.new_window_entity(|_window, cx| {
7258            let mut item = TestItem::new(cx);
7259            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
7260            item
7261        });
7262        workspace.update_in(cx, |workspace, window, cx| {
7263            workspace.add_item_to_active_pane(Box::new(item2.clone()), None, true, window, cx);
7264        });
7265        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
7266        item2.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
7267
7268        // Adding an item that creates ambiguity increases the level of detail only
7269        // on the ambiguous tabs. In this case, the ambiguity can't be resolved so
7270        // we stop at the highest detail available.
7271        let item3 = cx.new(|cx| {
7272            let mut item = TestItem::new(cx);
7273            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
7274            item
7275        });
7276        workspace.update_in(cx, |workspace, window, cx| {
7277            workspace.add_item_to_active_pane(Box::new(item3.clone()), None, true, window, cx);
7278        });
7279        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
7280        item2.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
7281        item3.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
7282    }
7283
7284    #[gpui::test]
7285    async fn test_tracking_active_path(cx: &mut TestAppContext) {
7286        init_test(cx);
7287
7288        let fs = FakeFs::new(cx.executor());
7289        fs.insert_tree(
7290            "/root1",
7291            json!({
7292                "one.txt": "",
7293                "two.txt": "",
7294            }),
7295        )
7296        .await;
7297        fs.insert_tree(
7298            "/root2",
7299            json!({
7300                "three.txt": "",
7301            }),
7302        )
7303        .await;
7304
7305        let project = Project::test(fs, ["root1".as_ref()], cx).await;
7306        let (workspace, cx) =
7307            cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
7308        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
7309        let worktree_id = project.update(cx, |project, cx| {
7310            project.worktrees(cx).next().unwrap().read(cx).id()
7311        });
7312
7313        let item1 = cx.new(|cx| {
7314            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "one.txt", cx)])
7315        });
7316        let item2 = cx.new(|cx| {
7317            TestItem::new(cx).with_project_items(&[TestProjectItem::new(2, "two.txt", cx)])
7318        });
7319
7320        // Add an item to an empty pane
7321        workspace.update_in(cx, |workspace, window, cx| {
7322            workspace.add_item_to_active_pane(Box::new(item1), None, true, window, cx)
7323        });
7324        project.update(cx, |project, cx| {
7325            assert_eq!(
7326                project.active_entry(),
7327                project
7328                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
7329                    .map(|e| e.id)
7330            );
7331        });
7332        assert_eq!(cx.window_title().as_deref(), Some("root1 — one.txt"));
7333
7334        // Add a second item to a non-empty pane
7335        workspace.update_in(cx, |workspace, window, cx| {
7336            workspace.add_item_to_active_pane(Box::new(item2), None, true, window, cx)
7337        });
7338        assert_eq!(cx.window_title().as_deref(), Some("root1 — two.txt"));
7339        project.update(cx, |project, cx| {
7340            assert_eq!(
7341                project.active_entry(),
7342                project
7343                    .entry_for_path(&(worktree_id, "two.txt").into(), cx)
7344                    .map(|e| e.id)
7345            );
7346        });
7347
7348        // Close the active item
7349        pane.update_in(cx, |pane, window, cx| {
7350            pane.close_active_item(&Default::default(), window, cx)
7351                .unwrap()
7352        })
7353        .await
7354        .unwrap();
7355        assert_eq!(cx.window_title().as_deref(), Some("root1 — one.txt"));
7356        project.update(cx, |project, cx| {
7357            assert_eq!(
7358                project.active_entry(),
7359                project
7360                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
7361                    .map(|e| e.id)
7362            );
7363        });
7364
7365        // Add a project folder
7366        project
7367            .update(cx, |project, cx| {
7368                project.find_or_create_worktree("root2", true, cx)
7369            })
7370            .await
7371            .unwrap();
7372        assert_eq!(cx.window_title().as_deref(), Some("root1, root2 — one.txt"));
7373
7374        // Remove a project folder
7375        project.update(cx, |project, cx| project.remove_worktree(worktree_id, cx));
7376        assert_eq!(cx.window_title().as_deref(), Some("root2 — one.txt"));
7377    }
7378
7379    #[gpui::test]
7380    async fn test_close_window(cx: &mut TestAppContext) {
7381        init_test(cx);
7382
7383        let fs = FakeFs::new(cx.executor());
7384        fs.insert_tree("/root", json!({ "one": "" })).await;
7385
7386        let project = Project::test(fs, ["root".as_ref()], cx).await;
7387        let (workspace, cx) =
7388            cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
7389
7390        // When there are no dirty items, there's nothing to do.
7391        let item1 = cx.new(TestItem::new);
7392        workspace.update_in(cx, |w, window, cx| {
7393            w.add_item_to_active_pane(Box::new(item1.clone()), None, true, window, cx)
7394        });
7395        let task = workspace.update_in(cx, |w, window, cx| {
7396            w.prepare_to_close(CloseIntent::CloseWindow, window, cx)
7397        });
7398        assert!(task.await.unwrap());
7399
7400        // When there are dirty untitled items, prompt to save each one. If the user
7401        // cancels any prompt, then abort.
7402        let item2 = cx.new(|cx| TestItem::new(cx).with_dirty(true));
7403        let item3 = cx.new(|cx| {
7404            TestItem::new(cx)
7405                .with_dirty(true)
7406                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
7407        });
7408        workspace.update_in(cx, |w, window, cx| {
7409            w.add_item_to_active_pane(Box::new(item2.clone()), None, true, window, cx);
7410            w.add_item_to_active_pane(Box::new(item3.clone()), None, true, window, cx);
7411        });
7412        let task = workspace.update_in(cx, |w, window, cx| {
7413            w.prepare_to_close(CloseIntent::CloseWindow, window, cx)
7414        });
7415        cx.executor().run_until_parked();
7416        cx.simulate_prompt_answer("Cancel"); // cancel save all
7417        cx.executor().run_until_parked();
7418        assert!(!cx.has_pending_prompt());
7419        assert!(!task.await.unwrap());
7420    }
7421
7422    #[gpui::test]
7423    async fn test_close_window_with_serializable_items(cx: &mut TestAppContext) {
7424        init_test(cx);
7425
7426        // Register TestItem as a serializable item
7427        cx.update(|cx| {
7428            register_serializable_item::<TestItem>(cx);
7429        });
7430
7431        let fs = FakeFs::new(cx.executor());
7432        fs.insert_tree("/root", json!({ "one": "" })).await;
7433
7434        let project = Project::test(fs, ["root".as_ref()], cx).await;
7435        let (workspace, cx) =
7436            cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
7437
7438        // When there are dirty untitled items, but they can serialize, then there is no prompt.
7439        let item1 = cx.new(|cx| {
7440            TestItem::new(cx)
7441                .with_dirty(true)
7442                .with_serialize(|| Some(Task::ready(Ok(()))))
7443        });
7444        let item2 = cx.new(|cx| {
7445            TestItem::new(cx)
7446                .with_dirty(true)
7447                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
7448                .with_serialize(|| Some(Task::ready(Ok(()))))
7449        });
7450        workspace.update_in(cx, |w, window, cx| {
7451            w.add_item_to_active_pane(Box::new(item1.clone()), None, true, window, cx);
7452            w.add_item_to_active_pane(Box::new(item2.clone()), None, true, window, cx);
7453        });
7454        let task = workspace.update_in(cx, |w, window, cx| {
7455            w.prepare_to_close(CloseIntent::CloseWindow, window, cx)
7456        });
7457        assert!(task.await.unwrap());
7458    }
7459
7460    #[gpui::test]
7461    async fn test_close_pane_items(cx: &mut TestAppContext) {
7462        init_test(cx);
7463
7464        let fs = FakeFs::new(cx.executor());
7465
7466        let project = Project::test(fs, None, cx).await;
7467        let (workspace, cx) =
7468            cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
7469
7470        let item1 = cx.new(|cx| {
7471            TestItem::new(cx)
7472                .with_dirty(true)
7473                .with_project_items(&[dirty_project_item(1, "1.txt", cx)])
7474        });
7475        let item2 = cx.new(|cx| {
7476            TestItem::new(cx)
7477                .with_dirty(true)
7478                .with_conflict(true)
7479                .with_project_items(&[dirty_project_item(2, "2.txt", cx)])
7480        });
7481        let item3 = cx.new(|cx| {
7482            TestItem::new(cx)
7483                .with_dirty(true)
7484                .with_conflict(true)
7485                .with_project_items(&[dirty_project_item(3, "3.txt", cx)])
7486        });
7487        let item4 = cx.new(|cx| {
7488            TestItem::new(cx).with_dirty(true).with_project_items(&[{
7489                let project_item = TestProjectItem::new_untitled(cx);
7490                project_item.update(cx, |project_item, _| project_item.is_dirty = true);
7491                project_item
7492            }])
7493        });
7494        let pane = workspace.update_in(cx, |workspace, window, cx| {
7495            workspace.add_item_to_active_pane(Box::new(item1.clone()), None, true, window, cx);
7496            workspace.add_item_to_active_pane(Box::new(item2.clone()), None, true, window, cx);
7497            workspace.add_item_to_active_pane(Box::new(item3.clone()), None, true, window, cx);
7498            workspace.add_item_to_active_pane(Box::new(item4.clone()), None, true, window, cx);
7499            workspace.active_pane().clone()
7500        });
7501
7502        let close_items = pane.update_in(cx, |pane, window, cx| {
7503            pane.activate_item(1, true, true, window, cx);
7504            assert_eq!(pane.active_item().unwrap().item_id(), item2.item_id());
7505            let item1_id = item1.item_id();
7506            let item3_id = item3.item_id();
7507            let item4_id = item4.item_id();
7508            pane.close_items(window, cx, SaveIntent::Close, move |id| {
7509                [item1_id, item3_id, item4_id].contains(&id)
7510            })
7511        });
7512        cx.executor().run_until_parked();
7513
7514        assert!(cx.has_pending_prompt());
7515        cx.simulate_prompt_answer("Save all");
7516
7517        cx.executor().run_until_parked();
7518
7519        // Item 1 is saved. There's a prompt to save item 3.
7520        pane.update(cx, |pane, cx| {
7521            assert_eq!(item1.read(cx).save_count, 1);
7522            assert_eq!(item1.read(cx).save_as_count, 0);
7523            assert_eq!(item1.read(cx).reload_count, 0);
7524            assert_eq!(pane.items_len(), 3);
7525            assert_eq!(pane.active_item().unwrap().item_id(), item3.item_id());
7526        });
7527        assert!(cx.has_pending_prompt());
7528
7529        // Cancel saving item 3.
7530        cx.simulate_prompt_answer("Discard");
7531        cx.executor().run_until_parked();
7532
7533        // Item 3 is reloaded. There's a prompt to save item 4.
7534        pane.update(cx, |pane, cx| {
7535            assert_eq!(item3.read(cx).save_count, 0);
7536            assert_eq!(item3.read(cx).save_as_count, 0);
7537            assert_eq!(item3.read(cx).reload_count, 1);
7538            assert_eq!(pane.items_len(), 2);
7539            assert_eq!(pane.active_item().unwrap().item_id(), item4.item_id());
7540        });
7541
7542        // There's a prompt for a path for item 4.
7543        cx.simulate_new_path_selection(|_| Some(Default::default()));
7544        close_items.await.unwrap();
7545
7546        // The requested items are closed.
7547        pane.update(cx, |pane, cx| {
7548            assert_eq!(item4.read(cx).save_count, 0);
7549            assert_eq!(item4.read(cx).save_as_count, 1);
7550            assert_eq!(item4.read(cx).reload_count, 0);
7551            assert_eq!(pane.items_len(), 1);
7552            assert_eq!(pane.active_item().unwrap().item_id(), item2.item_id());
7553        });
7554    }
7555
7556    #[gpui::test]
7557    async fn test_prompting_to_save_only_on_last_item_for_entry(cx: &mut TestAppContext) {
7558        init_test(cx);
7559
7560        let fs = FakeFs::new(cx.executor());
7561        let project = Project::test(fs, [], cx).await;
7562        let (workspace, cx) =
7563            cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
7564
7565        // Create several workspace items with single project entries, and two
7566        // workspace items with multiple project entries.
7567        let single_entry_items = (0..=4)
7568            .map(|project_entry_id| {
7569                cx.new(|cx| {
7570                    TestItem::new(cx)
7571                        .with_dirty(true)
7572                        .with_project_items(&[dirty_project_item(
7573                            project_entry_id,
7574                            &format!("{project_entry_id}.txt"),
7575                            cx,
7576                        )])
7577                })
7578            })
7579            .collect::<Vec<_>>();
7580        let item_2_3 = cx.new(|cx| {
7581            TestItem::new(cx)
7582                .with_dirty(true)
7583                .with_singleton(false)
7584                .with_project_items(&[
7585                    single_entry_items[2].read(cx).project_items[0].clone(),
7586                    single_entry_items[3].read(cx).project_items[0].clone(),
7587                ])
7588        });
7589        let item_3_4 = cx.new(|cx| {
7590            TestItem::new(cx)
7591                .with_dirty(true)
7592                .with_singleton(false)
7593                .with_project_items(&[
7594                    single_entry_items[3].read(cx).project_items[0].clone(),
7595                    single_entry_items[4].read(cx).project_items[0].clone(),
7596                ])
7597        });
7598
7599        // Create two panes that contain the following project entries:
7600        //   left pane:
7601        //     multi-entry items:   (2, 3)
7602        //     single-entry items:  0, 2, 3, 4
7603        //   right pane:
7604        //     single-entry items:  4, 1
7605        //     multi-entry items:   (3, 4)
7606        let (left_pane, right_pane) = workspace.update_in(cx, |workspace, window, cx| {
7607            let left_pane = workspace.active_pane().clone();
7608            workspace.add_item_to_active_pane(Box::new(item_2_3.clone()), None, true, window, cx);
7609            workspace.add_item_to_active_pane(
7610                single_entry_items[0].boxed_clone(),
7611                None,
7612                true,
7613                window,
7614                cx,
7615            );
7616            workspace.add_item_to_active_pane(
7617                single_entry_items[2].boxed_clone(),
7618                None,
7619                true,
7620                window,
7621                cx,
7622            );
7623            workspace.add_item_to_active_pane(
7624                single_entry_items[3].boxed_clone(),
7625                None,
7626                true,
7627                window,
7628                cx,
7629            );
7630            workspace.add_item_to_active_pane(
7631                single_entry_items[4].boxed_clone(),
7632                None,
7633                true,
7634                window,
7635                cx,
7636            );
7637
7638            let right_pane = workspace
7639                .split_and_clone(left_pane.clone(), SplitDirection::Right, window, cx)
7640                .unwrap();
7641
7642            right_pane.update(cx, |pane, cx| {
7643                pane.add_item(
7644                    single_entry_items[1].boxed_clone(),
7645                    true,
7646                    true,
7647                    None,
7648                    window,
7649                    cx,
7650                );
7651                pane.add_item(Box::new(item_3_4.clone()), true, true, None, window, cx);
7652            });
7653
7654            (left_pane, right_pane)
7655        });
7656
7657        cx.focus(&right_pane);
7658
7659        let mut close = right_pane.update_in(cx, |pane, window, cx| {
7660            pane.close_all_items(&CloseAllItems::default(), window, cx)
7661                .unwrap()
7662        });
7663        cx.executor().run_until_parked();
7664
7665        let msg = cx.pending_prompt().unwrap().0;
7666        assert!(msg.contains("1.txt"));
7667        assert!(!msg.contains("2.txt"));
7668        assert!(!msg.contains("3.txt"));
7669        assert!(!msg.contains("4.txt"));
7670
7671        cx.simulate_prompt_answer("Cancel");
7672        close.await.unwrap();
7673
7674        left_pane
7675            .update_in(cx, |left_pane, window, cx| {
7676                left_pane.close_item_by_id(
7677                    single_entry_items[3].entity_id(),
7678                    SaveIntent::Skip,
7679                    window,
7680                    cx,
7681                )
7682            })
7683            .await
7684            .unwrap();
7685
7686        close = right_pane.update_in(cx, |pane, window, cx| {
7687            pane.close_all_items(&CloseAllItems::default(), window, cx)
7688                .unwrap()
7689        });
7690        cx.executor().run_until_parked();
7691
7692        let details = cx.pending_prompt().unwrap().1;
7693        assert!(details.contains("1.txt"));
7694        assert!(!details.contains("2.txt"));
7695        assert!(details.contains("3.txt"));
7696        // ideally this assertion could be made, but today we can only
7697        // save whole items not project items, so the orphaned item 3 causes
7698        // 4 to be saved too.
7699        // assert!(!details.contains("4.txt"));
7700
7701        cx.simulate_prompt_answer("Save all");
7702
7703        cx.executor().run_until_parked();
7704        close.await.unwrap();
7705        right_pane.update(cx, |pane, _| {
7706            assert_eq!(pane.items_len(), 0);
7707        });
7708    }
7709
7710    #[gpui::test]
7711    async fn test_autosave(cx: &mut gpui::TestAppContext) {
7712        init_test(cx);
7713
7714        let fs = FakeFs::new(cx.executor());
7715        let project = Project::test(fs, [], cx).await;
7716        let (workspace, cx) =
7717            cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
7718        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
7719
7720        let item = cx.new(|cx| {
7721            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
7722        });
7723        let item_id = item.entity_id();
7724        workspace.update_in(cx, |workspace, window, cx| {
7725            workspace.add_item_to_active_pane(Box::new(item.clone()), None, true, window, cx);
7726        });
7727
7728        // Autosave on window change.
7729        item.update(cx, |item, cx| {
7730            SettingsStore::update_global(cx, |settings, cx| {
7731                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
7732                    settings.autosave = Some(AutosaveSetting::OnWindowChange);
7733                })
7734            });
7735            item.is_dirty = true;
7736        });
7737
7738        // Deactivating the window saves the file.
7739        cx.deactivate_window();
7740        item.update(cx, |item, _| assert_eq!(item.save_count, 1));
7741
7742        // Re-activating the window doesn't save the file.
7743        cx.update(|window, _| window.activate_window());
7744        cx.executor().run_until_parked();
7745        item.update(cx, |item, _| assert_eq!(item.save_count, 1));
7746
7747        // Autosave on focus change.
7748        item.update_in(cx, |item, window, cx| {
7749            cx.focus_self(window);
7750            SettingsStore::update_global(cx, |settings, cx| {
7751                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
7752                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
7753                })
7754            });
7755            item.is_dirty = true;
7756        });
7757
7758        // Blurring the item saves the file.
7759        item.update_in(cx, |_, window, _| window.blur());
7760        cx.executor().run_until_parked();
7761        item.update(cx, |item, _| assert_eq!(item.save_count, 2));
7762
7763        // Deactivating the window still saves the file.
7764        item.update_in(cx, |item, window, cx| {
7765            cx.focus_self(window);
7766            item.is_dirty = true;
7767        });
7768        cx.deactivate_window();
7769        item.update(cx, |item, _| assert_eq!(item.save_count, 3));
7770
7771        // Autosave after delay.
7772        item.update(cx, |item, cx| {
7773            SettingsStore::update_global(cx, |settings, cx| {
7774                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
7775                    settings.autosave = Some(AutosaveSetting::AfterDelay { milliseconds: 500 });
7776                })
7777            });
7778            item.is_dirty = true;
7779            cx.emit(ItemEvent::Edit);
7780        });
7781
7782        // Delay hasn't fully expired, so the file is still dirty and unsaved.
7783        cx.executor().advance_clock(Duration::from_millis(250));
7784        item.update(cx, |item, _| assert_eq!(item.save_count, 3));
7785
7786        // After delay expires, the file is saved.
7787        cx.executor().advance_clock(Duration::from_millis(250));
7788        item.update(cx, |item, _| assert_eq!(item.save_count, 4));
7789
7790        // Autosave on focus change, ensuring closing the tab counts as such.
7791        item.update(cx, |item, cx| {
7792            SettingsStore::update_global(cx, |settings, cx| {
7793                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
7794                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
7795                })
7796            });
7797            item.is_dirty = true;
7798            for project_item in &mut item.project_items {
7799                project_item.update(cx, |project_item, _| project_item.is_dirty = true);
7800            }
7801        });
7802
7803        pane.update_in(cx, |pane, window, cx| {
7804            pane.close_items(window, cx, SaveIntent::Close, move |id| id == item_id)
7805        })
7806        .await
7807        .unwrap();
7808        assert!(!cx.has_pending_prompt());
7809        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
7810
7811        // Add the item again, ensuring autosave is prevented if the underlying file has been deleted.
7812        workspace.update_in(cx, |workspace, window, cx| {
7813            workspace.add_item_to_active_pane(Box::new(item.clone()), None, true, window, cx);
7814        });
7815        item.update_in(cx, |item, window, cx| {
7816            item.project_items[0].update(cx, |item, _| {
7817                item.entry_id = None;
7818            });
7819            item.is_dirty = true;
7820            window.blur();
7821        });
7822        cx.run_until_parked();
7823        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
7824
7825        // Ensure autosave is prevented for deleted files also when closing the buffer.
7826        let _close_items = pane.update_in(cx, |pane, window, cx| {
7827            pane.close_items(window, cx, SaveIntent::Close, move |id| id == item_id)
7828        });
7829        cx.run_until_parked();
7830        assert!(cx.has_pending_prompt());
7831        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
7832    }
7833
7834    #[gpui::test]
7835    async fn test_pane_navigation(cx: &mut gpui::TestAppContext) {
7836        init_test(cx);
7837
7838        let fs = FakeFs::new(cx.executor());
7839
7840        let project = Project::test(fs, [], cx).await;
7841        let (workspace, cx) =
7842            cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
7843
7844        let item = cx.new(|cx| {
7845            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
7846        });
7847        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
7848        let toolbar = pane.update(cx, |pane, _| pane.toolbar().clone());
7849        let toolbar_notify_count = Rc::new(RefCell::new(0));
7850
7851        workspace.update_in(cx, |workspace, window, cx| {
7852            workspace.add_item_to_active_pane(Box::new(item.clone()), None, true, window, cx);
7853            let toolbar_notification_count = toolbar_notify_count.clone();
7854            cx.observe_in(&toolbar, window, move |_, _, _, _| {
7855                *toolbar_notification_count.borrow_mut() += 1
7856            })
7857            .detach();
7858        });
7859
7860        pane.update(cx, |pane, _| {
7861            assert!(!pane.can_navigate_backward());
7862            assert!(!pane.can_navigate_forward());
7863        });
7864
7865        item.update_in(cx, |item, _, cx| {
7866            item.set_state("one".to_string(), cx);
7867        });
7868
7869        // Toolbar must be notified to re-render the navigation buttons
7870        assert_eq!(*toolbar_notify_count.borrow(), 1);
7871
7872        pane.update(cx, |pane, _| {
7873            assert!(pane.can_navigate_backward());
7874            assert!(!pane.can_navigate_forward());
7875        });
7876
7877        workspace
7878            .update_in(cx, |workspace, window, cx| {
7879                workspace.go_back(pane.downgrade(), window, cx)
7880            })
7881            .await
7882            .unwrap();
7883
7884        assert_eq!(*toolbar_notify_count.borrow(), 2);
7885        pane.update(cx, |pane, _| {
7886            assert!(!pane.can_navigate_backward());
7887            assert!(pane.can_navigate_forward());
7888        });
7889    }
7890
7891    #[gpui::test]
7892    async fn test_toggle_docks_and_panels(cx: &mut gpui::TestAppContext) {
7893        init_test(cx);
7894        let fs = FakeFs::new(cx.executor());
7895
7896        let project = Project::test(fs, [], cx).await;
7897        let (workspace, cx) =
7898            cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
7899
7900        let panel = workspace.update_in(cx, |workspace, window, cx| {
7901            let panel = cx.new(|cx| TestPanel::new(DockPosition::Right, cx));
7902            workspace.add_panel(panel.clone(), window, cx);
7903
7904            workspace
7905                .right_dock()
7906                .update(cx, |right_dock, cx| right_dock.set_open(true, window, cx));
7907
7908            panel
7909        });
7910
7911        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
7912        pane.update_in(cx, |pane, window, cx| {
7913            let item = cx.new(TestItem::new);
7914            pane.add_item(Box::new(item), true, true, None, window, cx);
7915        });
7916
7917        // Transfer focus from center to panel
7918        workspace.update_in(cx, |workspace, window, cx| {
7919            workspace.toggle_panel_focus::<TestPanel>(window, cx);
7920        });
7921
7922        workspace.update_in(cx, |workspace, window, cx| {
7923            assert!(workspace.right_dock().read(cx).is_open());
7924            assert!(!panel.is_zoomed(window, cx));
7925            assert!(panel.read(cx).focus_handle(cx).contains_focused(window, cx));
7926        });
7927
7928        // Transfer focus from panel to center
7929        workspace.update_in(cx, |workspace, window, cx| {
7930            workspace.toggle_panel_focus::<TestPanel>(window, cx);
7931        });
7932
7933        workspace.update_in(cx, |workspace, window, cx| {
7934            assert!(workspace.right_dock().read(cx).is_open());
7935            assert!(!panel.is_zoomed(window, cx));
7936            assert!(!panel.read(cx).focus_handle(cx).contains_focused(window, cx));
7937        });
7938
7939        // Close the dock
7940        workspace.update_in(cx, |workspace, window, cx| {
7941            workspace.toggle_dock(DockPosition::Right, window, cx);
7942        });
7943
7944        workspace.update_in(cx, |workspace, window, cx| {
7945            assert!(!workspace.right_dock().read(cx).is_open());
7946            assert!(!panel.is_zoomed(window, cx));
7947            assert!(!panel.read(cx).focus_handle(cx).contains_focused(window, cx));
7948        });
7949
7950        // Open the dock
7951        workspace.update_in(cx, |workspace, window, cx| {
7952            workspace.toggle_dock(DockPosition::Right, window, cx);
7953        });
7954
7955        workspace.update_in(cx, |workspace, window, cx| {
7956            assert!(workspace.right_dock().read(cx).is_open());
7957            assert!(!panel.is_zoomed(window, cx));
7958            assert!(panel.read(cx).focus_handle(cx).contains_focused(window, cx));
7959        });
7960
7961        // Focus and zoom panel
7962        panel.update_in(cx, |panel, window, cx| {
7963            cx.focus_self(window);
7964            panel.set_zoomed(true, window, cx)
7965        });
7966
7967        workspace.update_in(cx, |workspace, window, cx| {
7968            assert!(workspace.right_dock().read(cx).is_open());
7969            assert!(panel.is_zoomed(window, cx));
7970            assert!(panel.read(cx).focus_handle(cx).contains_focused(window, cx));
7971        });
7972
7973        // Transfer focus to the center closes the dock
7974        workspace.update_in(cx, |workspace, window, cx| {
7975            workspace.toggle_panel_focus::<TestPanel>(window, cx);
7976        });
7977
7978        workspace.update_in(cx, |workspace, window, cx| {
7979            assert!(!workspace.right_dock().read(cx).is_open());
7980            assert!(panel.is_zoomed(window, cx));
7981            assert!(!panel.read(cx).focus_handle(cx).contains_focused(window, cx));
7982        });
7983
7984        // Transferring focus back to the panel keeps it zoomed
7985        workspace.update_in(cx, |workspace, window, cx| {
7986            workspace.toggle_panel_focus::<TestPanel>(window, cx);
7987        });
7988
7989        workspace.update_in(cx, |workspace, window, cx| {
7990            assert!(workspace.right_dock().read(cx).is_open());
7991            assert!(panel.is_zoomed(window, cx));
7992            assert!(panel.read(cx).focus_handle(cx).contains_focused(window, cx));
7993        });
7994
7995        // Close the dock while it is zoomed
7996        workspace.update_in(cx, |workspace, window, cx| {
7997            workspace.toggle_dock(DockPosition::Right, window, cx)
7998        });
7999
8000        workspace.update_in(cx, |workspace, window, cx| {
8001            assert!(!workspace.right_dock().read(cx).is_open());
8002            assert!(panel.is_zoomed(window, cx));
8003            assert!(workspace.zoomed.is_none());
8004            assert!(!panel.read(cx).focus_handle(cx).contains_focused(window, cx));
8005        });
8006
8007        // Opening the dock, when it's zoomed, retains focus
8008        workspace.update_in(cx, |workspace, window, cx| {
8009            workspace.toggle_dock(DockPosition::Right, window, cx)
8010        });
8011
8012        workspace.update_in(cx, |workspace, window, cx| {
8013            assert!(workspace.right_dock().read(cx).is_open());
8014            assert!(panel.is_zoomed(window, cx));
8015            assert!(workspace.zoomed.is_some());
8016            assert!(panel.read(cx).focus_handle(cx).contains_focused(window, cx));
8017        });
8018
8019        // Unzoom and close the panel, zoom the active pane.
8020        panel.update_in(cx, |panel, window, cx| panel.set_zoomed(false, window, cx));
8021        workspace.update_in(cx, |workspace, window, cx| {
8022            workspace.toggle_dock(DockPosition::Right, window, cx)
8023        });
8024        pane.update_in(cx, |pane, window, cx| {
8025            pane.toggle_zoom(&Default::default(), window, cx)
8026        });
8027
8028        // Opening a dock unzooms the pane.
8029        workspace.update_in(cx, |workspace, window, cx| {
8030            workspace.toggle_dock(DockPosition::Right, window, cx)
8031        });
8032        workspace.update_in(cx, |workspace, window, cx| {
8033            let pane = pane.read(cx);
8034            assert!(!pane.is_zoomed());
8035            assert!(!pane.focus_handle(cx).is_focused(window));
8036            assert!(workspace.right_dock().read(cx).is_open());
8037            assert!(workspace.zoomed.is_none());
8038        });
8039    }
8040
8041    #[gpui::test]
8042    async fn test_join_pane_into_next(cx: &mut gpui::TestAppContext) {
8043        init_test(cx);
8044
8045        let fs = FakeFs::new(cx.executor());
8046
8047        let project = Project::test(fs, None, cx).await;
8048        let (workspace, cx) =
8049            cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
8050
8051        // Let's arrange the panes like this:
8052        //
8053        // +-----------------------+
8054        // |         top           |
8055        // +------+--------+-------+
8056        // | left | center | right |
8057        // +------+--------+-------+
8058        // |        bottom         |
8059        // +-----------------------+
8060
8061        let top_item = cx.new(|cx| {
8062            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "top.txt", cx)])
8063        });
8064        let bottom_item = cx.new(|cx| {
8065            TestItem::new(cx).with_project_items(&[TestProjectItem::new(2, "bottom.txt", cx)])
8066        });
8067        let left_item = cx.new(|cx| {
8068            TestItem::new(cx).with_project_items(&[TestProjectItem::new(3, "left.txt", cx)])
8069        });
8070        let right_item = cx.new(|cx| {
8071            TestItem::new(cx).with_project_items(&[TestProjectItem::new(4, "right.txt", cx)])
8072        });
8073        let center_item = cx.new(|cx| {
8074            TestItem::new(cx).with_project_items(&[TestProjectItem::new(5, "center.txt", cx)])
8075        });
8076
8077        let top_pane_id = workspace.update_in(cx, |workspace, window, cx| {
8078            let top_pane_id = workspace.active_pane().entity_id();
8079            workspace.add_item_to_active_pane(Box::new(top_item.clone()), None, false, window, cx);
8080            workspace.split_pane(
8081                workspace.active_pane().clone(),
8082                SplitDirection::Down,
8083                window,
8084                cx,
8085            );
8086            top_pane_id
8087        });
8088        let bottom_pane_id = workspace.update_in(cx, |workspace, window, cx| {
8089            let bottom_pane_id = workspace.active_pane().entity_id();
8090            workspace.add_item_to_active_pane(
8091                Box::new(bottom_item.clone()),
8092                None,
8093                false,
8094                window,
8095                cx,
8096            );
8097            workspace.split_pane(
8098                workspace.active_pane().clone(),
8099                SplitDirection::Up,
8100                window,
8101                cx,
8102            );
8103            bottom_pane_id
8104        });
8105        let left_pane_id = workspace.update_in(cx, |workspace, window, cx| {
8106            let left_pane_id = workspace.active_pane().entity_id();
8107            workspace.add_item_to_active_pane(Box::new(left_item.clone()), None, false, window, cx);
8108            workspace.split_pane(
8109                workspace.active_pane().clone(),
8110                SplitDirection::Right,
8111                window,
8112                cx,
8113            );
8114            left_pane_id
8115        });
8116        let right_pane_id = workspace.update_in(cx, |workspace, window, cx| {
8117            let right_pane_id = workspace.active_pane().entity_id();
8118            workspace.add_item_to_active_pane(
8119                Box::new(right_item.clone()),
8120                None,
8121                false,
8122                window,
8123                cx,
8124            );
8125            workspace.split_pane(
8126                workspace.active_pane().clone(),
8127                SplitDirection::Left,
8128                window,
8129                cx,
8130            );
8131            right_pane_id
8132        });
8133        let center_pane_id = workspace.update_in(cx, |workspace, window, cx| {
8134            let center_pane_id = workspace.active_pane().entity_id();
8135            workspace.add_item_to_active_pane(
8136                Box::new(center_item.clone()),
8137                None,
8138                false,
8139                window,
8140                cx,
8141            );
8142            center_pane_id
8143        });
8144        cx.executor().run_until_parked();
8145
8146        workspace.update_in(cx, |workspace, window, cx| {
8147            assert_eq!(center_pane_id, workspace.active_pane().entity_id());
8148
8149            // Join into next from center pane into right
8150            workspace.join_pane_into_next(workspace.active_pane().clone(), window, cx);
8151        });
8152
8153        workspace.update_in(cx, |workspace, window, cx| {
8154            let active_pane = workspace.active_pane();
8155            assert_eq!(right_pane_id, active_pane.entity_id());
8156            assert_eq!(2, active_pane.read(cx).items_len());
8157            let item_ids_in_pane =
8158                HashSet::from_iter(active_pane.read(cx).items().map(|item| item.item_id()));
8159            assert!(item_ids_in_pane.contains(&center_item.item_id()));
8160            assert!(item_ids_in_pane.contains(&right_item.item_id()));
8161
8162            // Join into next from right pane into bottom
8163            workspace.join_pane_into_next(workspace.active_pane().clone(), window, cx);
8164        });
8165
8166        workspace.update_in(cx, |workspace, window, cx| {
8167            let active_pane = workspace.active_pane();
8168            assert_eq!(bottom_pane_id, active_pane.entity_id());
8169            assert_eq!(3, active_pane.read(cx).items_len());
8170            let item_ids_in_pane =
8171                HashSet::from_iter(active_pane.read(cx).items().map(|item| item.item_id()));
8172            assert!(item_ids_in_pane.contains(&center_item.item_id()));
8173            assert!(item_ids_in_pane.contains(&right_item.item_id()));
8174            assert!(item_ids_in_pane.contains(&bottom_item.item_id()));
8175
8176            // Join into next from bottom pane into left
8177            workspace.join_pane_into_next(workspace.active_pane().clone(), window, cx);
8178        });
8179
8180        workspace.update_in(cx, |workspace, window, cx| {
8181            let active_pane = workspace.active_pane();
8182            assert_eq!(left_pane_id, active_pane.entity_id());
8183            assert_eq!(4, active_pane.read(cx).items_len());
8184            let item_ids_in_pane =
8185                HashSet::from_iter(active_pane.read(cx).items().map(|item| item.item_id()));
8186            assert!(item_ids_in_pane.contains(&center_item.item_id()));
8187            assert!(item_ids_in_pane.contains(&right_item.item_id()));
8188            assert!(item_ids_in_pane.contains(&bottom_item.item_id()));
8189            assert!(item_ids_in_pane.contains(&left_item.item_id()));
8190
8191            // Join into next from left pane into top
8192            workspace.join_pane_into_next(workspace.active_pane().clone(), window, cx);
8193        });
8194
8195        workspace.update_in(cx, |workspace, window, cx| {
8196            let active_pane = workspace.active_pane();
8197            assert_eq!(top_pane_id, active_pane.entity_id());
8198            assert_eq!(5, active_pane.read(cx).items_len());
8199            let item_ids_in_pane =
8200                HashSet::from_iter(active_pane.read(cx).items().map(|item| item.item_id()));
8201            assert!(item_ids_in_pane.contains(&center_item.item_id()));
8202            assert!(item_ids_in_pane.contains(&right_item.item_id()));
8203            assert!(item_ids_in_pane.contains(&bottom_item.item_id()));
8204            assert!(item_ids_in_pane.contains(&left_item.item_id()));
8205            assert!(item_ids_in_pane.contains(&top_item.item_id()));
8206
8207            // Single pane left: no-op
8208            workspace.join_pane_into_next(workspace.active_pane().clone(), window, cx)
8209        });
8210
8211        workspace.update(cx, |workspace, _cx| {
8212            let active_pane = workspace.active_pane();
8213            assert_eq!(top_pane_id, active_pane.entity_id());
8214        });
8215    }
8216
8217    fn add_an_item_to_active_pane(
8218        cx: &mut VisualTestContext,
8219        workspace: &Entity<Workspace>,
8220        item_id: u64,
8221    ) -> Entity<TestItem> {
8222        let item = cx.new(|cx| {
8223            TestItem::new(cx).with_project_items(&[TestProjectItem::new(
8224                item_id,
8225                "item{item_id}.txt",
8226                cx,
8227            )])
8228        });
8229        workspace.update_in(cx, |workspace, window, cx| {
8230            workspace.add_item_to_active_pane(Box::new(item.clone()), None, false, window, cx);
8231        });
8232        return item;
8233    }
8234
8235    fn split_pane(cx: &mut VisualTestContext, workspace: &Entity<Workspace>) -> Entity<Pane> {
8236        return workspace.update_in(cx, |workspace, window, cx| {
8237            let new_pane = workspace.split_pane(
8238                workspace.active_pane().clone(),
8239                SplitDirection::Right,
8240                window,
8241                cx,
8242            );
8243            new_pane
8244        });
8245    }
8246
8247    #[gpui::test]
8248    async fn test_join_all_panes(cx: &mut gpui::TestAppContext) {
8249        init_test(cx);
8250        let fs = FakeFs::new(cx.executor());
8251        let project = Project::test(fs, None, cx).await;
8252        let (workspace, cx) =
8253            cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
8254
8255        add_an_item_to_active_pane(cx, &workspace, 1);
8256        split_pane(cx, &workspace);
8257        add_an_item_to_active_pane(cx, &workspace, 2);
8258        split_pane(cx, &workspace); // empty pane
8259        split_pane(cx, &workspace);
8260        let last_item = add_an_item_to_active_pane(cx, &workspace, 3);
8261
8262        cx.executor().run_until_parked();
8263
8264        workspace.update(cx, |workspace, cx| {
8265            let num_panes = workspace.panes().len();
8266            let num_items_in_current_pane = workspace.active_pane().read(cx).items().count();
8267            let active_item = workspace
8268                .active_pane()
8269                .read(cx)
8270                .active_item()
8271                .expect("item is in focus");
8272
8273            assert_eq!(num_panes, 4);
8274            assert_eq!(num_items_in_current_pane, 1);
8275            assert_eq!(active_item.item_id(), last_item.item_id());
8276        });
8277
8278        workspace.update_in(cx, |workspace, window, cx| {
8279            workspace.join_all_panes(window, cx);
8280        });
8281
8282        workspace.update(cx, |workspace, cx| {
8283            let num_panes = workspace.panes().len();
8284            let num_items_in_current_pane = workspace.active_pane().read(cx).items().count();
8285            let active_item = workspace
8286                .active_pane()
8287                .read(cx)
8288                .active_item()
8289                .expect("item is in focus");
8290
8291            assert_eq!(num_panes, 1);
8292            assert_eq!(num_items_in_current_pane, 3);
8293            assert_eq!(active_item.item_id(), last_item.item_id());
8294        });
8295    }
8296    struct TestModal(FocusHandle);
8297
8298    impl TestModal {
8299        fn new(_: &mut Window, cx: &mut Context<Self>) -> Self {
8300            Self(cx.focus_handle())
8301        }
8302    }
8303
8304    impl EventEmitter<DismissEvent> for TestModal {}
8305
8306    impl Focusable for TestModal {
8307        fn focus_handle(&self, _cx: &App) -> FocusHandle {
8308            self.0.clone()
8309        }
8310    }
8311
8312    impl ModalView for TestModal {}
8313
8314    impl Render for TestModal {
8315        fn render(
8316            &mut self,
8317            _window: &mut Window,
8318            _cx: &mut Context<TestModal>,
8319        ) -> impl IntoElement {
8320            div().track_focus(&self.0)
8321        }
8322    }
8323
8324    #[gpui::test]
8325    async fn test_panels(cx: &mut gpui::TestAppContext) {
8326        init_test(cx);
8327        let fs = FakeFs::new(cx.executor());
8328
8329        let project = Project::test(fs, [], cx).await;
8330        let (workspace, cx) =
8331            cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
8332
8333        let (panel_1, panel_2) = workspace.update_in(cx, |workspace, window, cx| {
8334            let panel_1 = cx.new(|cx| TestPanel::new(DockPosition::Left, cx));
8335            workspace.add_panel(panel_1.clone(), window, cx);
8336            workspace.toggle_dock(DockPosition::Left, window, cx);
8337            let panel_2 = cx.new(|cx| TestPanel::new(DockPosition::Right, cx));
8338            workspace.add_panel(panel_2.clone(), window, cx);
8339            workspace.toggle_dock(DockPosition::Right, window, cx);
8340
8341            let left_dock = workspace.left_dock();
8342            assert_eq!(
8343                left_dock.read(cx).visible_panel().unwrap().panel_id(),
8344                panel_1.panel_id()
8345            );
8346            assert_eq!(
8347                left_dock.read(cx).active_panel_size(window, cx).unwrap(),
8348                panel_1.size(window, cx)
8349            );
8350
8351            left_dock.update(cx, |left_dock, cx| {
8352                left_dock.resize_active_panel(Some(px(1337.)), window, cx)
8353            });
8354            assert_eq!(
8355                workspace
8356                    .right_dock()
8357                    .read(cx)
8358                    .visible_panel()
8359                    .unwrap()
8360                    .panel_id(),
8361                panel_2.panel_id(),
8362            );
8363
8364            (panel_1, panel_2)
8365        });
8366
8367        // Move panel_1 to the right
8368        panel_1.update_in(cx, |panel_1, window, cx| {
8369            panel_1.set_position(DockPosition::Right, window, cx)
8370        });
8371
8372        workspace.update_in(cx, |workspace, window, cx| {
8373            // Since panel_1 was visible on the left, it should now be visible now that it's been moved to the right.
8374            // Since it was the only panel on the left, the left dock should now be closed.
8375            assert!(!workspace.left_dock().read(cx).is_open());
8376            assert!(workspace.left_dock().read(cx).visible_panel().is_none());
8377            let right_dock = workspace.right_dock();
8378            assert_eq!(
8379                right_dock.read(cx).visible_panel().unwrap().panel_id(),
8380                panel_1.panel_id()
8381            );
8382            assert_eq!(
8383                right_dock.read(cx).active_panel_size(window, cx).unwrap(),
8384                px(1337.)
8385            );
8386
8387            // Now we move panel_2 to the left
8388            panel_2.set_position(DockPosition::Left, window, cx);
8389        });
8390
8391        workspace.update(cx, |workspace, cx| {
8392            // Since panel_2 was not visible on the right, we don't open the left dock.
8393            assert!(!workspace.left_dock().read(cx).is_open());
8394            // And the right dock is unaffected in its displaying of panel_1
8395            assert!(workspace.right_dock().read(cx).is_open());
8396            assert_eq!(
8397                workspace
8398                    .right_dock()
8399                    .read(cx)
8400                    .visible_panel()
8401                    .unwrap()
8402                    .panel_id(),
8403                panel_1.panel_id(),
8404            );
8405        });
8406
8407        // Move panel_1 back to the left
8408        panel_1.update_in(cx, |panel_1, window, cx| {
8409            panel_1.set_position(DockPosition::Left, window, cx)
8410        });
8411
8412        workspace.update_in(cx, |workspace, window, cx| {
8413            // Since panel_1 was visible on the right, we open the left dock and make panel_1 active.
8414            let left_dock = workspace.left_dock();
8415            assert!(left_dock.read(cx).is_open());
8416            assert_eq!(
8417                left_dock.read(cx).visible_panel().unwrap().panel_id(),
8418                panel_1.panel_id()
8419            );
8420            assert_eq!(
8421                left_dock.read(cx).active_panel_size(window, cx).unwrap(),
8422                px(1337.)
8423            );
8424            // And the right dock should be closed as it no longer has any panels.
8425            assert!(!workspace.right_dock().read(cx).is_open());
8426
8427            // Now we move panel_1 to the bottom
8428            panel_1.set_position(DockPosition::Bottom, window, cx);
8429        });
8430
8431        workspace.update_in(cx, |workspace, window, cx| {
8432            // Since panel_1 was visible on the left, we close the left dock.
8433            assert!(!workspace.left_dock().read(cx).is_open());
8434            // The bottom dock is sized based on the panel's default size,
8435            // since the panel orientation changed from vertical to horizontal.
8436            let bottom_dock = workspace.bottom_dock();
8437            assert_eq!(
8438                bottom_dock.read(cx).active_panel_size(window, cx).unwrap(),
8439                panel_1.size(window, cx),
8440            );
8441            // Close bottom dock and move panel_1 back to the left.
8442            bottom_dock.update(cx, |bottom_dock, cx| {
8443                bottom_dock.set_open(false, window, cx)
8444            });
8445            panel_1.set_position(DockPosition::Left, window, cx);
8446        });
8447
8448        // Emit activated event on panel 1
8449        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::Activate));
8450
8451        // Now the left dock is open and panel_1 is active and focused.
8452        workspace.update_in(cx, |workspace, window, cx| {
8453            let left_dock = workspace.left_dock();
8454            assert!(left_dock.read(cx).is_open());
8455            assert_eq!(
8456                left_dock.read(cx).visible_panel().unwrap().panel_id(),
8457                panel_1.panel_id(),
8458            );
8459            assert!(panel_1.focus_handle(cx).is_focused(window));
8460        });
8461
8462        // Emit closed event on panel 2, which is not active
8463        panel_2.update(cx, |_, cx| cx.emit(PanelEvent::Close));
8464
8465        // Wo don't close the left dock, because panel_2 wasn't the active panel
8466        workspace.update(cx, |workspace, cx| {
8467            let left_dock = workspace.left_dock();
8468            assert!(left_dock.read(cx).is_open());
8469            assert_eq!(
8470                left_dock.read(cx).visible_panel().unwrap().panel_id(),
8471                panel_1.panel_id(),
8472            );
8473        });
8474
8475        // Emitting a ZoomIn event shows the panel as zoomed.
8476        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::ZoomIn));
8477        workspace.update(cx, |workspace, _| {
8478            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
8479            assert_eq!(workspace.zoomed_position, Some(DockPosition::Left));
8480        });
8481
8482        // Move panel to another dock while it is zoomed
8483        panel_1.update_in(cx, |panel, window, cx| {
8484            panel.set_position(DockPosition::Right, window, cx)
8485        });
8486        workspace.update(cx, |workspace, _| {
8487            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
8488
8489            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
8490        });
8491
8492        // This is a helper for getting a:
8493        // - valid focus on an element,
8494        // - that isn't a part of the panes and panels system of the Workspace,
8495        // - and doesn't trigger the 'on_focus_lost' API.
8496        let focus_other_view = {
8497            let workspace = workspace.clone();
8498            move |cx: &mut VisualTestContext| {
8499                workspace.update_in(cx, |workspace, window, cx| {
8500                    if let Some(_) = workspace.active_modal::<TestModal>(cx) {
8501                        workspace.toggle_modal(window, cx, TestModal::new);
8502                        workspace.toggle_modal(window, cx, TestModal::new);
8503                    } else {
8504                        workspace.toggle_modal(window, cx, TestModal::new);
8505                    }
8506                })
8507            }
8508        };
8509
8510        // If focus is transferred to another view that's not a panel or another pane, we still show
8511        // the panel as zoomed.
8512        focus_other_view(cx);
8513        workspace.update(cx, |workspace, _| {
8514            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
8515            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
8516        });
8517
8518        // If focus is transferred elsewhere in the workspace, the panel is no longer zoomed.
8519        workspace.update_in(cx, |_workspace, window, cx| {
8520            cx.focus_self(window);
8521        });
8522        workspace.update(cx, |workspace, _| {
8523            assert_eq!(workspace.zoomed, None);
8524            assert_eq!(workspace.zoomed_position, None);
8525        });
8526
8527        // If focus is transferred again to another view that's not a panel or a pane, we won't
8528        // show the panel as zoomed because it wasn't zoomed before.
8529        focus_other_view(cx);
8530        workspace.update(cx, |workspace, _| {
8531            assert_eq!(workspace.zoomed, None);
8532            assert_eq!(workspace.zoomed_position, None);
8533        });
8534
8535        // When the panel is activated, it is zoomed again.
8536        cx.dispatch_action(ToggleRightDock);
8537        workspace.update(cx, |workspace, _| {
8538            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
8539            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
8540        });
8541
8542        // Emitting a ZoomOut event unzooms the panel.
8543        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::ZoomOut));
8544        workspace.update(cx, |workspace, _| {
8545            assert_eq!(workspace.zoomed, None);
8546            assert_eq!(workspace.zoomed_position, None);
8547        });
8548
8549        // Emit closed event on panel 1, which is active
8550        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::Close));
8551
8552        // Now the left dock is closed, because panel_1 was the active panel
8553        workspace.update(cx, |workspace, cx| {
8554            let right_dock = workspace.right_dock();
8555            assert!(!right_dock.read(cx).is_open());
8556        });
8557    }
8558
8559    #[gpui::test]
8560    async fn test_no_save_prompt_when_multi_buffer_dirty_items_closed(cx: &mut TestAppContext) {
8561        init_test(cx);
8562
8563        let fs = FakeFs::new(cx.background_executor.clone());
8564        let project = Project::test(fs, [], cx).await;
8565        let (workspace, cx) =
8566            cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
8567        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
8568
8569        let dirty_regular_buffer = cx.new(|cx| {
8570            TestItem::new(cx)
8571                .with_dirty(true)
8572                .with_label("1.txt")
8573                .with_project_items(&[dirty_project_item(1, "1.txt", cx)])
8574        });
8575        let dirty_regular_buffer_2 = cx.new(|cx| {
8576            TestItem::new(cx)
8577                .with_dirty(true)
8578                .with_label("2.txt")
8579                .with_project_items(&[dirty_project_item(2, "2.txt", cx)])
8580        });
8581        let dirty_multi_buffer_with_both = cx.new(|cx| {
8582            TestItem::new(cx)
8583                .with_dirty(true)
8584                .with_singleton(false)
8585                .with_label("Fake Project Search")
8586                .with_project_items(&[
8587                    dirty_regular_buffer.read(cx).project_items[0].clone(),
8588                    dirty_regular_buffer_2.read(cx).project_items[0].clone(),
8589                ])
8590        });
8591        let multi_buffer_with_both_files_id = dirty_multi_buffer_with_both.item_id();
8592        workspace.update_in(cx, |workspace, window, cx| {
8593            workspace.add_item(
8594                pane.clone(),
8595                Box::new(dirty_regular_buffer.clone()),
8596                None,
8597                false,
8598                false,
8599                window,
8600                cx,
8601            );
8602            workspace.add_item(
8603                pane.clone(),
8604                Box::new(dirty_regular_buffer_2.clone()),
8605                None,
8606                false,
8607                false,
8608                window,
8609                cx,
8610            );
8611            workspace.add_item(
8612                pane.clone(),
8613                Box::new(dirty_multi_buffer_with_both.clone()),
8614                None,
8615                false,
8616                false,
8617                window,
8618                cx,
8619            );
8620        });
8621
8622        pane.update_in(cx, |pane, window, cx| {
8623            pane.activate_item(2, true, true, window, cx);
8624            assert_eq!(
8625                pane.active_item().unwrap().item_id(),
8626                multi_buffer_with_both_files_id,
8627                "Should select the multi buffer in the pane"
8628            );
8629        });
8630        let close_all_but_multi_buffer_task = pane
8631            .update_in(cx, |pane, window, cx| {
8632                pane.close_inactive_items(
8633                    &CloseInactiveItems {
8634                        save_intent: Some(SaveIntent::Save),
8635                        close_pinned: true,
8636                    },
8637                    window,
8638                    cx,
8639                )
8640            })
8641            .expect("should have inactive files to close");
8642        cx.background_executor.run_until_parked();
8643        assert!(!cx.has_pending_prompt());
8644        close_all_but_multi_buffer_task
8645            .await
8646            .expect("Closing all buffers but the multi buffer failed");
8647        pane.update(cx, |pane, cx| {
8648            assert_eq!(dirty_regular_buffer.read(cx).save_count, 1);
8649            assert_eq!(dirty_multi_buffer_with_both.read(cx).save_count, 0);
8650            assert_eq!(dirty_regular_buffer_2.read(cx).save_count, 1);
8651            assert_eq!(pane.items_len(), 1);
8652            assert_eq!(
8653                pane.active_item().unwrap().item_id(),
8654                multi_buffer_with_both_files_id,
8655                "Should have only the multi buffer left in the pane"
8656            );
8657            assert!(
8658                dirty_multi_buffer_with_both.read(cx).is_dirty,
8659                "The multi buffer containing the unsaved buffer should still be dirty"
8660            );
8661        });
8662
8663        dirty_regular_buffer.update(cx, |buffer, cx| {
8664            buffer.project_items[0].update(cx, |pi, _| pi.is_dirty = true)
8665        });
8666
8667        let close_multi_buffer_task = pane
8668            .update_in(cx, |pane, window, cx| {
8669                pane.close_active_item(
8670                    &CloseActiveItem {
8671                        save_intent: Some(SaveIntent::Close),
8672                        close_pinned: false,
8673                    },
8674                    window,
8675                    cx,
8676                )
8677            })
8678            .expect("should have the multi buffer to close");
8679        cx.background_executor.run_until_parked();
8680        assert!(
8681            cx.has_pending_prompt(),
8682            "Dirty multi buffer should prompt a save dialog"
8683        );
8684        cx.simulate_prompt_answer("Save");
8685        cx.background_executor.run_until_parked();
8686        close_multi_buffer_task
8687            .await
8688            .expect("Closing the multi buffer failed");
8689        pane.update(cx, |pane, cx| {
8690            assert_eq!(
8691                dirty_multi_buffer_with_both.read(cx).save_count,
8692                1,
8693                "Multi buffer item should get be saved"
8694            );
8695            // Test impl does not save inner items, so we do not assert them
8696            assert_eq!(
8697                pane.items_len(),
8698                0,
8699                "No more items should be left in the pane"
8700            );
8701            assert!(pane.active_item().is_none());
8702        });
8703    }
8704
8705    #[gpui::test]
8706    async fn test_save_prompt_when_dirty_multi_buffer_closed_with_some_of_its_dirty_items_not_present_in_the_pane(
8707        cx: &mut TestAppContext,
8708    ) {
8709        init_test(cx);
8710
8711        let fs = FakeFs::new(cx.background_executor.clone());
8712        let project = Project::test(fs, [], cx).await;
8713        let (workspace, cx) =
8714            cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
8715        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
8716
8717        let dirty_regular_buffer = cx.new(|cx| {
8718            TestItem::new(cx)
8719                .with_dirty(true)
8720                .with_label("1.txt")
8721                .with_project_items(&[dirty_project_item(1, "1.txt", cx)])
8722        });
8723        let dirty_regular_buffer_2 = cx.new(|cx| {
8724            TestItem::new(cx)
8725                .with_dirty(true)
8726                .with_label("2.txt")
8727                .with_project_items(&[dirty_project_item(2, "2.txt", cx)])
8728        });
8729        let clear_regular_buffer = cx.new(|cx| {
8730            TestItem::new(cx)
8731                .with_label("3.txt")
8732                .with_project_items(&[TestProjectItem::new(3, "3.txt", cx)])
8733        });
8734
8735        let dirty_multi_buffer_with_both = cx.new(|cx| {
8736            TestItem::new(cx)
8737                .with_dirty(true)
8738                .with_singleton(false)
8739                .with_label("Fake Project Search")
8740                .with_project_items(&[
8741                    dirty_regular_buffer.read(cx).project_items[0].clone(),
8742                    dirty_regular_buffer_2.read(cx).project_items[0].clone(),
8743                    clear_regular_buffer.read(cx).project_items[0].clone(),
8744                ])
8745        });
8746        let multi_buffer_with_both_files_id = dirty_multi_buffer_with_both.item_id();
8747        workspace.update_in(cx, |workspace, window, cx| {
8748            workspace.add_item(
8749                pane.clone(),
8750                Box::new(dirty_regular_buffer.clone()),
8751                None,
8752                false,
8753                false,
8754                window,
8755                cx,
8756            );
8757            workspace.add_item(
8758                pane.clone(),
8759                Box::new(dirty_multi_buffer_with_both.clone()),
8760                None,
8761                false,
8762                false,
8763                window,
8764                cx,
8765            );
8766        });
8767
8768        pane.update_in(cx, |pane, window, cx| {
8769            pane.activate_item(1, true, true, window, cx);
8770            assert_eq!(
8771                pane.active_item().unwrap().item_id(),
8772                multi_buffer_with_both_files_id,
8773                "Should select the multi buffer in the pane"
8774            );
8775        });
8776        let _close_multi_buffer_task = pane
8777            .update_in(cx, |pane, window, cx| {
8778                pane.close_active_item(
8779                    &CloseActiveItem {
8780                        save_intent: None,
8781                        close_pinned: false,
8782                    },
8783                    window,
8784                    cx,
8785                )
8786            })
8787            .expect("should have active multi buffer to close");
8788        cx.background_executor.run_until_parked();
8789        assert!(
8790            cx.has_pending_prompt(),
8791            "With one dirty item from the multi buffer not being in the pane, a save prompt should be shown"
8792        );
8793    }
8794
8795    #[gpui::test]
8796    async fn test_no_save_prompt_when_dirty_multi_buffer_closed_with_all_of_its_dirty_items_present_in_the_pane(
8797        cx: &mut TestAppContext,
8798    ) {
8799        init_test(cx);
8800
8801        let fs = FakeFs::new(cx.background_executor.clone());
8802        let project = Project::test(fs, [], cx).await;
8803        let (workspace, cx) =
8804            cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
8805        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
8806
8807        let dirty_regular_buffer = cx.new(|cx| {
8808            TestItem::new(cx)
8809                .with_dirty(true)
8810                .with_label("1.txt")
8811                .with_project_items(&[dirty_project_item(1, "1.txt", cx)])
8812        });
8813        let dirty_regular_buffer_2 = cx.new(|cx| {
8814            TestItem::new(cx)
8815                .with_dirty(true)
8816                .with_label("2.txt")
8817                .with_project_items(&[dirty_project_item(2, "2.txt", cx)])
8818        });
8819        let clear_regular_buffer = cx.new(|cx| {
8820            TestItem::new(cx)
8821                .with_label("3.txt")
8822                .with_project_items(&[TestProjectItem::new(3, "3.txt", cx)])
8823        });
8824
8825        let dirty_multi_buffer = cx.new(|cx| {
8826            TestItem::new(cx)
8827                .with_dirty(true)
8828                .with_singleton(false)
8829                .with_label("Fake Project Search")
8830                .with_project_items(&[
8831                    dirty_regular_buffer.read(cx).project_items[0].clone(),
8832                    dirty_regular_buffer_2.read(cx).project_items[0].clone(),
8833                    clear_regular_buffer.read(cx).project_items[0].clone(),
8834                ])
8835        });
8836        workspace.update_in(cx, |workspace, window, cx| {
8837            workspace.add_item(
8838                pane.clone(),
8839                Box::new(dirty_regular_buffer.clone()),
8840                None,
8841                false,
8842                false,
8843                window,
8844                cx,
8845            );
8846            workspace.add_item(
8847                pane.clone(),
8848                Box::new(dirty_regular_buffer_2.clone()),
8849                None,
8850                false,
8851                false,
8852                window,
8853                cx,
8854            );
8855            workspace.add_item(
8856                pane.clone(),
8857                Box::new(dirty_multi_buffer.clone()),
8858                None,
8859                false,
8860                false,
8861                window,
8862                cx,
8863            );
8864        });
8865
8866        pane.update_in(cx, |pane, window, cx| {
8867            pane.activate_item(2, true, true, window, cx);
8868            assert_eq!(
8869                pane.active_item().unwrap().item_id(),
8870                dirty_multi_buffer.item_id(),
8871                "Should select the multi buffer in the pane"
8872            );
8873        });
8874        let close_multi_buffer_task = pane
8875            .update_in(cx, |pane, window, cx| {
8876                pane.close_active_item(
8877                    &CloseActiveItem {
8878                        save_intent: None,
8879                        close_pinned: false,
8880                    },
8881                    window,
8882                    cx,
8883                )
8884            })
8885            .expect("should have active multi buffer to close");
8886        cx.background_executor.run_until_parked();
8887        assert!(
8888            !cx.has_pending_prompt(),
8889            "All dirty items from the multi buffer are in the pane still, no save prompts should be shown"
8890        );
8891        close_multi_buffer_task
8892            .await
8893            .expect("Closing multi buffer failed");
8894        pane.update(cx, |pane, cx| {
8895            assert_eq!(dirty_regular_buffer.read(cx).save_count, 0);
8896            assert_eq!(dirty_multi_buffer.read(cx).save_count, 0);
8897            assert_eq!(dirty_regular_buffer_2.read(cx).save_count, 0);
8898            assert_eq!(
8899                pane.items()
8900                    .map(|item| item.item_id())
8901                    .sorted()
8902                    .collect::<Vec<_>>(),
8903                vec![
8904                    dirty_regular_buffer.item_id(),
8905                    dirty_regular_buffer_2.item_id(),
8906                ],
8907                "Should have no multi buffer left in the pane"
8908            );
8909            assert!(dirty_regular_buffer.read(cx).is_dirty);
8910            assert!(dirty_regular_buffer_2.read(cx).is_dirty);
8911        });
8912    }
8913
8914    #[gpui::test]
8915    async fn test_move_focused_panel_to_next_position(cx: &mut gpui::TestAppContext) {
8916        init_test(cx);
8917        let fs = FakeFs::new(cx.executor());
8918        let project = Project::test(fs, [], cx).await;
8919        let (workspace, cx) =
8920            cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
8921
8922        // Add a new panel to the right dock, opening the dock and setting the
8923        // focus to the new panel.
8924        let panel = workspace.update_in(cx, |workspace, window, cx| {
8925            let panel = cx.new(|cx| TestPanel::new(DockPosition::Right, cx));
8926            workspace.add_panel(panel.clone(), window, cx);
8927
8928            workspace
8929                .right_dock()
8930                .update(cx, |right_dock, cx| right_dock.set_open(true, window, cx));
8931
8932            workspace.toggle_panel_focus::<TestPanel>(window, cx);
8933
8934            panel
8935        });
8936
8937        // Dispatch the `MoveFocusedPanelToNextPosition` action, moving the
8938        // panel to the next valid position which, in this case, is the left
8939        // dock.
8940        cx.dispatch_action(MoveFocusedPanelToNextPosition);
8941        workspace.update(cx, |workspace, cx| {
8942            assert!(workspace.left_dock().read(cx).is_open());
8943            assert_eq!(panel.read(cx).position, DockPosition::Left);
8944        });
8945
8946        // Dispatch the `MoveFocusedPanelToNextPosition` action, moving the
8947        // panel to the next valid position which, in this case, is the bottom
8948        // dock.
8949        cx.dispatch_action(MoveFocusedPanelToNextPosition);
8950        workspace.update(cx, |workspace, cx| {
8951            assert!(workspace.bottom_dock().read(cx).is_open());
8952            assert_eq!(panel.read(cx).position, DockPosition::Bottom);
8953        });
8954
8955        // Dispatch the `MoveFocusedPanelToNextPosition` action again, this time
8956        // around moving the panel to its initial position, the right dock.
8957        cx.dispatch_action(MoveFocusedPanelToNextPosition);
8958        workspace.update(cx, |workspace, cx| {
8959            assert!(workspace.right_dock().read(cx).is_open());
8960            assert_eq!(panel.read(cx).position, DockPosition::Right);
8961        });
8962
8963        // Remove focus from the panel, ensuring that, if the panel is not
8964        // focused, the `MoveFocusedPanelToNextPosition` action does not update
8965        // the panel's position, so the panel is still in the right dock.
8966        workspace.update_in(cx, |workspace, window, cx| {
8967            workspace.toggle_panel_focus::<TestPanel>(window, cx);
8968        });
8969
8970        cx.dispatch_action(MoveFocusedPanelToNextPosition);
8971        workspace.update(cx, |workspace, cx| {
8972            assert!(workspace.right_dock().read(cx).is_open());
8973            assert_eq!(panel.read(cx).position, DockPosition::Right);
8974        });
8975    }
8976
8977    mod register_project_item_tests {
8978
8979        use super::*;
8980
8981        // View
8982        struct TestPngItemView {
8983            focus_handle: FocusHandle,
8984        }
8985        // Model
8986        struct TestPngItem {}
8987
8988        impl project::ProjectItem for TestPngItem {
8989            fn try_open(
8990                _project: &Entity<Project>,
8991                path: &ProjectPath,
8992                cx: &mut App,
8993            ) -> Option<Task<gpui::Result<Entity<Self>>>> {
8994                if path.path.extension().unwrap() == "png" {
8995                    Some(cx.spawn(async move |cx| cx.new(|_| TestPngItem {})))
8996                } else {
8997                    None
8998                }
8999            }
9000
9001            fn entry_id(&self, _: &App) -> Option<ProjectEntryId> {
9002                None
9003            }
9004
9005            fn project_path(&self, _: &App) -> Option<ProjectPath> {
9006                None
9007            }
9008
9009            fn is_dirty(&self) -> bool {
9010                false
9011            }
9012        }
9013
9014        impl Item for TestPngItemView {
9015            type Event = ();
9016        }
9017        impl EventEmitter<()> for TestPngItemView {}
9018        impl Focusable for TestPngItemView {
9019            fn focus_handle(&self, _cx: &App) -> FocusHandle {
9020                self.focus_handle.clone()
9021            }
9022        }
9023
9024        impl Render for TestPngItemView {
9025            fn render(
9026                &mut self,
9027                _window: &mut Window,
9028                _cx: &mut Context<Self>,
9029            ) -> impl IntoElement {
9030                Empty
9031            }
9032        }
9033
9034        impl ProjectItem for TestPngItemView {
9035            type Item = TestPngItem;
9036
9037            fn for_project_item(
9038                _project: Entity<Project>,
9039                _pane: &Pane,
9040                _item: Entity<Self::Item>,
9041                _: &mut Window,
9042                cx: &mut Context<Self>,
9043            ) -> Self
9044            where
9045                Self: Sized,
9046            {
9047                Self {
9048                    focus_handle: cx.focus_handle(),
9049                }
9050            }
9051        }
9052
9053        // View
9054        struct TestIpynbItemView {
9055            focus_handle: FocusHandle,
9056        }
9057        // Model
9058        struct TestIpynbItem {}
9059
9060        impl project::ProjectItem for TestIpynbItem {
9061            fn try_open(
9062                _project: &Entity<Project>,
9063                path: &ProjectPath,
9064                cx: &mut App,
9065            ) -> Option<Task<gpui::Result<Entity<Self>>>> {
9066                if path.path.extension().unwrap() == "ipynb" {
9067                    Some(cx.spawn(async move |cx| cx.new(|_| TestIpynbItem {})))
9068                } else {
9069                    None
9070                }
9071            }
9072
9073            fn entry_id(&self, _: &App) -> Option<ProjectEntryId> {
9074                None
9075            }
9076
9077            fn project_path(&self, _: &App) -> Option<ProjectPath> {
9078                None
9079            }
9080
9081            fn is_dirty(&self) -> bool {
9082                false
9083            }
9084        }
9085
9086        impl Item for TestIpynbItemView {
9087            type Event = ();
9088        }
9089        impl EventEmitter<()> for TestIpynbItemView {}
9090        impl Focusable for TestIpynbItemView {
9091            fn focus_handle(&self, _cx: &App) -> FocusHandle {
9092                self.focus_handle.clone()
9093            }
9094        }
9095
9096        impl Render for TestIpynbItemView {
9097            fn render(
9098                &mut self,
9099                _window: &mut Window,
9100                _cx: &mut Context<Self>,
9101            ) -> impl IntoElement {
9102                Empty
9103            }
9104        }
9105
9106        impl ProjectItem for TestIpynbItemView {
9107            type Item = TestIpynbItem;
9108
9109            fn for_project_item(
9110                _project: Entity<Project>,
9111                _pane: &Pane,
9112                _item: Entity<Self::Item>,
9113                _: &mut Window,
9114                cx: &mut Context<Self>,
9115            ) -> Self
9116            where
9117                Self: Sized,
9118            {
9119                Self {
9120                    focus_handle: cx.focus_handle(),
9121                }
9122            }
9123        }
9124
9125        struct TestAlternatePngItemView {
9126            focus_handle: FocusHandle,
9127        }
9128
9129        impl Item for TestAlternatePngItemView {
9130            type Event = ();
9131        }
9132
9133        impl EventEmitter<()> for TestAlternatePngItemView {}
9134        impl Focusable for TestAlternatePngItemView {
9135            fn focus_handle(&self, _cx: &App) -> FocusHandle {
9136                self.focus_handle.clone()
9137            }
9138        }
9139
9140        impl Render for TestAlternatePngItemView {
9141            fn render(
9142                &mut self,
9143                _window: &mut Window,
9144                _cx: &mut Context<Self>,
9145            ) -> impl IntoElement {
9146                Empty
9147            }
9148        }
9149
9150        impl ProjectItem for TestAlternatePngItemView {
9151            type Item = TestPngItem;
9152
9153            fn for_project_item(
9154                _project: Entity<Project>,
9155                _pane: &Pane,
9156                _item: Entity<Self::Item>,
9157                _: &mut Window,
9158                cx: &mut Context<Self>,
9159            ) -> Self
9160            where
9161                Self: Sized,
9162            {
9163                Self {
9164                    focus_handle: cx.focus_handle(),
9165                }
9166            }
9167        }
9168
9169        #[gpui::test]
9170        async fn test_register_project_item(cx: &mut TestAppContext) {
9171            init_test(cx);
9172
9173            cx.update(|cx| {
9174                register_project_item::<TestPngItemView>(cx);
9175                register_project_item::<TestIpynbItemView>(cx);
9176            });
9177
9178            let fs = FakeFs::new(cx.executor());
9179            fs.insert_tree(
9180                "/root1",
9181                json!({
9182                    "one.png": "BINARYDATAHERE",
9183                    "two.ipynb": "{ totally a notebook }",
9184                    "three.txt": "editing text, sure why not?"
9185                }),
9186            )
9187            .await;
9188
9189            let project = Project::test(fs, ["root1".as_ref()], cx).await;
9190            let (workspace, cx) =
9191                cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
9192
9193            let worktree_id = project.update(cx, |project, cx| {
9194                project.worktrees(cx).next().unwrap().read(cx).id()
9195            });
9196
9197            let handle = workspace
9198                .update_in(cx, |workspace, window, cx| {
9199                    let project_path = (worktree_id, "one.png");
9200                    workspace.open_path(project_path, None, true, window, cx)
9201                })
9202                .await
9203                .unwrap();
9204
9205            // Now we can check if the handle we got back errored or not
9206            assert_eq!(
9207                handle.to_any().entity_type(),
9208                TypeId::of::<TestPngItemView>()
9209            );
9210
9211            let handle = workspace
9212                .update_in(cx, |workspace, window, cx| {
9213                    let project_path = (worktree_id, "two.ipynb");
9214                    workspace.open_path(project_path, None, true, window, cx)
9215                })
9216                .await
9217                .unwrap();
9218
9219            assert_eq!(
9220                handle.to_any().entity_type(),
9221                TypeId::of::<TestIpynbItemView>()
9222            );
9223
9224            let handle = workspace
9225                .update_in(cx, |workspace, window, cx| {
9226                    let project_path = (worktree_id, "three.txt");
9227                    workspace.open_path(project_path, None, true, window, cx)
9228                })
9229                .await;
9230            assert!(handle.is_err());
9231        }
9232
9233        #[gpui::test]
9234        async fn test_register_project_item_two_enter_one_leaves(cx: &mut TestAppContext) {
9235            init_test(cx);
9236
9237            cx.update(|cx| {
9238                register_project_item::<TestPngItemView>(cx);
9239                register_project_item::<TestAlternatePngItemView>(cx);
9240            });
9241
9242            let fs = FakeFs::new(cx.executor());
9243            fs.insert_tree(
9244                "/root1",
9245                json!({
9246                    "one.png": "BINARYDATAHERE",
9247                    "two.ipynb": "{ totally a notebook }",
9248                    "three.txt": "editing text, sure why not?"
9249                }),
9250            )
9251            .await;
9252            let project = Project::test(fs, ["root1".as_ref()], cx).await;
9253            let (workspace, cx) =
9254                cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
9255            let worktree_id = project.update(cx, |project, cx| {
9256                project.worktrees(cx).next().unwrap().read(cx).id()
9257            });
9258
9259            let handle = workspace
9260                .update_in(cx, |workspace, window, cx| {
9261                    let project_path = (worktree_id, "one.png");
9262                    workspace.open_path(project_path, None, true, window, cx)
9263                })
9264                .await
9265                .unwrap();
9266
9267            // This _must_ be the second item registered
9268            assert_eq!(
9269                handle.to_any().entity_type(),
9270                TypeId::of::<TestAlternatePngItemView>()
9271            );
9272
9273            let handle = workspace
9274                .update_in(cx, |workspace, window, cx| {
9275                    let project_path = (worktree_id, "three.txt");
9276                    workspace.open_path(project_path, None, true, window, cx)
9277                })
9278                .await;
9279            assert!(handle.is_err());
9280        }
9281    }
9282
9283    pub fn init_test(cx: &mut TestAppContext) {
9284        cx.update(|cx| {
9285            let settings_store = SettingsStore::test(cx);
9286            cx.set_global(settings_store);
9287            theme::init(theme::LoadThemes::JustBase, cx);
9288            language::init(cx);
9289            crate::init_settings(cx);
9290            Project::init_settings(cx);
9291        });
9292    }
9293
9294    fn dirty_project_item(id: u64, path: &str, cx: &mut App) -> Entity<TestProjectItem> {
9295        let item = TestProjectItem::new(id, path, cx);
9296        item.update(cx, |item, _| {
9297            item.is_dirty = true;
9298        });
9299        item
9300    }
9301}