workspace.rs

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