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