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