workspace.rs

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