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