workspace.rs

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