workspace.rs

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