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