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            pane::Event::ItemPinned | pane::Event::ItemUnpinned => {}
3765        }
3766
3767        if serialize_workspace {
3768            self.serialize_workspace(window, cx);
3769        }
3770    }
3771
3772    pub fn unfollow_in_pane(
3773        &mut self,
3774        pane: &Entity<Pane>,
3775        window: &mut Window,
3776        cx: &mut Context<Workspace>,
3777    ) -> Option<CollaboratorId> {
3778        let leader_id = self.leader_for_pane(pane)?;
3779        self.unfollow(leader_id, window, cx);
3780        Some(leader_id)
3781    }
3782
3783    pub fn split_pane(
3784        &mut self,
3785        pane_to_split: Entity<Pane>,
3786        split_direction: SplitDirection,
3787        window: &mut Window,
3788        cx: &mut Context<Self>,
3789    ) -> Entity<Pane> {
3790        let new_pane = self.add_pane(window, cx);
3791        self.center
3792            .split(&pane_to_split, &new_pane, split_direction)
3793            .unwrap();
3794        cx.notify();
3795        new_pane
3796    }
3797
3798    pub fn split_and_clone(
3799        &mut self,
3800        pane: Entity<Pane>,
3801        direction: SplitDirection,
3802        window: &mut Window,
3803        cx: &mut Context<Self>,
3804    ) -> Option<Entity<Pane>> {
3805        let item = pane.read(cx).active_item()?;
3806        let maybe_pane_handle =
3807            if let Some(clone) = item.clone_on_split(self.database_id(), window, cx) {
3808                let new_pane = self.add_pane(window, cx);
3809                new_pane.update(cx, |pane, cx| {
3810                    pane.add_item(clone, true, true, None, window, cx)
3811                });
3812                self.center.split(&pane, &new_pane, direction).unwrap();
3813                Some(new_pane)
3814            } else {
3815                None
3816            };
3817        cx.notify();
3818        maybe_pane_handle
3819    }
3820
3821    pub fn split_pane_with_item(
3822        &mut self,
3823        pane_to_split: WeakEntity<Pane>,
3824        split_direction: SplitDirection,
3825        from: WeakEntity<Pane>,
3826        item_id_to_move: EntityId,
3827        window: &mut Window,
3828        cx: &mut Context<Self>,
3829    ) {
3830        let Some(pane_to_split) = pane_to_split.upgrade() else {
3831            return;
3832        };
3833        let Some(from) = from.upgrade() else {
3834            return;
3835        };
3836
3837        let new_pane = self.add_pane(window, cx);
3838        move_item(&from, &new_pane, item_id_to_move, 0, window, cx);
3839        self.center
3840            .split(&pane_to_split, &new_pane, split_direction)
3841            .unwrap();
3842        cx.notify();
3843    }
3844
3845    pub fn split_pane_with_project_entry(
3846        &mut self,
3847        pane_to_split: WeakEntity<Pane>,
3848        split_direction: SplitDirection,
3849        project_entry: ProjectEntryId,
3850        window: &mut Window,
3851        cx: &mut Context<Self>,
3852    ) -> Option<Task<Result<()>>> {
3853        let pane_to_split = pane_to_split.upgrade()?;
3854        let new_pane = self.add_pane(window, cx);
3855        self.center
3856            .split(&pane_to_split, &new_pane, split_direction)
3857            .unwrap();
3858
3859        let path = self.project.read(cx).path_for_entry(project_entry, cx)?;
3860        let task = self.open_path(path, Some(new_pane.downgrade()), true, window, cx);
3861        Some(cx.foreground_executor().spawn(async move {
3862            task.await?;
3863            Ok(())
3864        }))
3865    }
3866
3867    pub fn join_all_panes(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3868        let active_item = self.active_pane.read(cx).active_item();
3869        for pane in &self.panes {
3870            join_pane_into_active(&self.active_pane, pane, window, cx);
3871        }
3872        if let Some(active_item) = active_item {
3873            self.activate_item(active_item.as_ref(), true, true, window, cx);
3874        }
3875        cx.notify();
3876    }
3877
3878    pub fn join_pane_into_next(
3879        &mut self,
3880        pane: Entity<Pane>,
3881        window: &mut Window,
3882        cx: &mut Context<Self>,
3883    ) {
3884        let next_pane = self
3885            .find_pane_in_direction(SplitDirection::Right, cx)
3886            .or_else(|| self.find_pane_in_direction(SplitDirection::Down, cx))
3887            .or_else(|| self.find_pane_in_direction(SplitDirection::Left, cx))
3888            .or_else(|| self.find_pane_in_direction(SplitDirection::Up, cx));
3889        let Some(next_pane) = next_pane else {
3890            return;
3891        };
3892        move_all_items(&pane, &next_pane, window, cx);
3893        cx.notify();
3894    }
3895
3896    fn remove_pane(
3897        &mut self,
3898        pane: Entity<Pane>,
3899        focus_on: Option<Entity<Pane>>,
3900        window: &mut Window,
3901        cx: &mut Context<Self>,
3902    ) {
3903        if self.center.remove(&pane).unwrap() {
3904            self.force_remove_pane(&pane, &focus_on, window, cx);
3905            self.unfollow_in_pane(&pane, window, cx);
3906            self.last_leaders_by_pane.remove(&pane.downgrade());
3907            for removed_item in pane.read(cx).items() {
3908                self.panes_by_item.remove(&removed_item.item_id());
3909            }
3910
3911            cx.notify();
3912        } else {
3913            self.active_item_path_changed(window, cx);
3914        }
3915        cx.emit(Event::PaneRemoved);
3916    }
3917
3918    pub fn panes(&self) -> &[Entity<Pane>] {
3919        &self.panes
3920    }
3921
3922    pub fn active_pane(&self) -> &Entity<Pane> {
3923        &self.active_pane
3924    }
3925
3926    pub fn focused_pane(&self, window: &Window, cx: &App) -> Entity<Pane> {
3927        for dock in self.all_docks() {
3928            if dock.focus_handle(cx).contains_focused(window, cx) {
3929                if let Some(pane) = dock
3930                    .read(cx)
3931                    .active_panel()
3932                    .and_then(|panel| panel.pane(cx))
3933                {
3934                    return pane;
3935                }
3936            }
3937        }
3938        self.active_pane().clone()
3939    }
3940
3941    pub fn adjacent_pane(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Entity<Pane> {
3942        self.find_pane_in_direction(SplitDirection::Right, cx)
3943            .or_else(|| self.find_pane_in_direction(SplitDirection::Left, cx))
3944            .unwrap_or_else(|| {
3945                self.split_pane(self.active_pane.clone(), SplitDirection::Right, window, cx)
3946            })
3947            .clone()
3948    }
3949
3950    pub fn pane_for(&self, handle: &dyn ItemHandle) -> Option<Entity<Pane>> {
3951        let weak_pane = self.panes_by_item.get(&handle.item_id())?;
3952        weak_pane.upgrade()
3953    }
3954
3955    fn collaborator_left(&mut self, peer_id: PeerId, window: &mut Window, cx: &mut Context<Self>) {
3956        self.follower_states.retain(|leader_id, state| {
3957            if *leader_id == CollaboratorId::PeerId(peer_id) {
3958                for item in state.items_by_leader_view_id.values() {
3959                    item.view.set_leader_id(None, window, cx);
3960                }
3961                false
3962            } else {
3963                true
3964            }
3965        });
3966        cx.notify();
3967    }
3968
3969    pub fn start_following(
3970        &mut self,
3971        leader_id: impl Into<CollaboratorId>,
3972        window: &mut Window,
3973        cx: &mut Context<Self>,
3974    ) -> Option<Task<Result<()>>> {
3975        let leader_id = leader_id.into();
3976        let pane = self.active_pane().clone();
3977
3978        self.last_leaders_by_pane
3979            .insert(pane.downgrade(), leader_id);
3980        self.unfollow(leader_id, window, cx);
3981        self.unfollow_in_pane(&pane, window, cx);
3982        self.follower_states.insert(
3983            leader_id,
3984            FollowerState {
3985                center_pane: pane.clone(),
3986                dock_pane: None,
3987                active_view_id: None,
3988                items_by_leader_view_id: Default::default(),
3989            },
3990        );
3991        cx.notify();
3992
3993        match leader_id {
3994            CollaboratorId::PeerId(leader_peer_id) => {
3995                let room_id = self.active_call()?.read(cx).room()?.read(cx).id();
3996                let project_id = self.project.read(cx).remote_id();
3997                let request = self.app_state.client.request(proto::Follow {
3998                    room_id,
3999                    project_id,
4000                    leader_id: Some(leader_peer_id),
4001                });
4002
4003                Some(cx.spawn_in(window, async move |this, cx| {
4004                    let response = request.await?;
4005                    this.update(cx, |this, _| {
4006                        let state = this
4007                            .follower_states
4008                            .get_mut(&leader_id)
4009                            .context("following interrupted")?;
4010                        state.active_view_id = response
4011                            .active_view
4012                            .as_ref()
4013                            .and_then(|view| ViewId::from_proto(view.id.clone()?).ok());
4014                        anyhow::Ok(())
4015                    })??;
4016                    if let Some(view) = response.active_view {
4017                        Self::add_view_from_leader(this.clone(), leader_peer_id, &view, cx).await?;
4018                    }
4019                    this.update_in(cx, |this, window, cx| {
4020                        this.leader_updated(leader_id, window, cx)
4021                    })?;
4022                    Ok(())
4023                }))
4024            }
4025            CollaboratorId::Agent => {
4026                self.leader_updated(leader_id, window, cx)?;
4027                Some(Task::ready(Ok(())))
4028            }
4029        }
4030    }
4031
4032    pub fn follow_next_collaborator(
4033        &mut self,
4034        _: &FollowNextCollaborator,
4035        window: &mut Window,
4036        cx: &mut Context<Self>,
4037    ) {
4038        let collaborators = self.project.read(cx).collaborators();
4039        let next_leader_id = if let Some(leader_id) = self.leader_for_pane(&self.active_pane) {
4040            let mut collaborators = collaborators.keys().copied();
4041            for peer_id in collaborators.by_ref() {
4042                if CollaboratorId::PeerId(peer_id) == leader_id {
4043                    break;
4044                }
4045            }
4046            collaborators.next().map(CollaboratorId::PeerId)
4047        } else if let Some(last_leader_id) =
4048            self.last_leaders_by_pane.get(&self.active_pane.downgrade())
4049        {
4050            match last_leader_id {
4051                CollaboratorId::PeerId(peer_id) => {
4052                    if collaborators.contains_key(peer_id) {
4053                        Some(*last_leader_id)
4054                    } else {
4055                        None
4056                    }
4057                }
4058                CollaboratorId::Agent => Some(CollaboratorId::Agent),
4059            }
4060        } else {
4061            None
4062        };
4063
4064        let pane = self.active_pane.clone();
4065        let Some(leader_id) = next_leader_id.or_else(|| {
4066            Some(CollaboratorId::PeerId(
4067                collaborators.keys().copied().next()?,
4068            ))
4069        }) else {
4070            return;
4071        };
4072        if self.unfollow_in_pane(&pane, window, cx) == Some(leader_id) {
4073            return;
4074        }
4075        if let Some(task) = self.start_following(leader_id, window, cx) {
4076            task.detach_and_log_err(cx)
4077        }
4078    }
4079
4080    pub fn follow(
4081        &mut self,
4082        leader_id: impl Into<CollaboratorId>,
4083        window: &mut Window,
4084        cx: &mut Context<Self>,
4085    ) {
4086        let leader_id = leader_id.into();
4087
4088        if let CollaboratorId::PeerId(peer_id) = leader_id {
4089            let Some(room) = ActiveCall::global(cx).read(cx).room() else {
4090                return;
4091            };
4092            let room = room.read(cx);
4093            let Some(remote_participant) = room.remote_participant_for_peer_id(peer_id) else {
4094                return;
4095            };
4096
4097            let project = self.project.read(cx);
4098
4099            let other_project_id = match remote_participant.location {
4100                call::ParticipantLocation::External => None,
4101                call::ParticipantLocation::UnsharedProject => None,
4102                call::ParticipantLocation::SharedProject { project_id } => {
4103                    if Some(project_id) == project.remote_id() {
4104                        None
4105                    } else {
4106                        Some(project_id)
4107                    }
4108                }
4109            };
4110
4111            // if they are active in another project, follow there.
4112            if let Some(project_id) = other_project_id {
4113                let app_state = self.app_state.clone();
4114                crate::join_in_room_project(project_id, remote_participant.user.id, app_state, cx)
4115                    .detach_and_log_err(cx);
4116            }
4117        }
4118
4119        // if you're already following, find the right pane and focus it.
4120        if let Some(follower_state) = self.follower_states.get(&leader_id) {
4121            window.focus(&follower_state.pane().focus_handle(cx));
4122
4123            return;
4124        }
4125
4126        // Otherwise, follow.
4127        if let Some(task) = self.start_following(leader_id, window, cx) {
4128            task.detach_and_log_err(cx)
4129        }
4130    }
4131
4132    pub fn unfollow(
4133        &mut self,
4134        leader_id: impl Into<CollaboratorId>,
4135        window: &mut Window,
4136        cx: &mut Context<Self>,
4137    ) -> Option<()> {
4138        cx.notify();
4139
4140        let leader_id = leader_id.into();
4141        let state = self.follower_states.remove(&leader_id)?;
4142        for (_, item) in state.items_by_leader_view_id {
4143            item.view.set_leader_id(None, window, cx);
4144        }
4145
4146        if let CollaboratorId::PeerId(leader_peer_id) = leader_id {
4147            let project_id = self.project.read(cx).remote_id();
4148            let room_id = self.active_call()?.read(cx).room()?.read(cx).id();
4149            self.app_state
4150                .client
4151                .send(proto::Unfollow {
4152                    room_id,
4153                    project_id,
4154                    leader_id: Some(leader_peer_id),
4155                })
4156                .log_err();
4157        }
4158
4159        Some(())
4160    }
4161
4162    pub fn is_being_followed(&self, id: impl Into<CollaboratorId>) -> bool {
4163        self.follower_states.contains_key(&id.into())
4164    }
4165
4166    fn active_item_path_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4167        cx.emit(Event::ActiveItemChanged);
4168        let active_entry = self.active_project_path(cx);
4169        self.project
4170            .update(cx, |project, cx| project.set_active_path(active_entry, cx));
4171
4172        self.update_window_title(window, cx);
4173    }
4174
4175    fn update_window_title(&mut self, window: &mut Window, cx: &mut App) {
4176        let project = self.project().read(cx);
4177        let mut title = String::new();
4178
4179        for (i, name) in project.worktree_root_names(cx).enumerate() {
4180            if i > 0 {
4181                title.push_str(", ");
4182            }
4183            title.push_str(name);
4184        }
4185
4186        if title.is_empty() {
4187            title = "empty project".to_string();
4188        }
4189
4190        if let Some(path) = self.active_item(cx).and_then(|item| item.project_path(cx)) {
4191            let filename = path
4192                .path
4193                .file_name()
4194                .map(|s| s.to_string_lossy())
4195                .or_else(|| {
4196                    Some(Cow::Borrowed(
4197                        project
4198                            .worktree_for_id(path.worktree_id, cx)?
4199                            .read(cx)
4200                            .root_name(),
4201                    ))
4202                });
4203
4204            if let Some(filename) = filename {
4205                title.push_str("");
4206                title.push_str(filename.as_ref());
4207            }
4208        }
4209
4210        if project.is_via_collab() {
4211            title.push_str("");
4212        } else if project.is_shared() {
4213            title.push_str("");
4214        }
4215
4216        window.set_window_title(&title);
4217    }
4218
4219    fn update_window_edited(&mut self, window: &mut Window, cx: &mut App) {
4220        let is_edited = !self.project.read(cx).is_disconnected(cx) && !self.dirty_items.is_empty();
4221        if is_edited != self.window_edited {
4222            self.window_edited = is_edited;
4223            window.set_window_edited(self.window_edited)
4224        }
4225    }
4226
4227    fn update_item_dirty_state(
4228        &mut self,
4229        item: &dyn ItemHandle,
4230        window: &mut Window,
4231        cx: &mut App,
4232    ) {
4233        let is_dirty = item.is_dirty(cx);
4234        let item_id = item.item_id();
4235        let was_dirty = self.dirty_items.contains_key(&item_id);
4236        if is_dirty == was_dirty {
4237            return;
4238        }
4239        if was_dirty {
4240            self.dirty_items.remove(&item_id);
4241            self.update_window_edited(window, cx);
4242            return;
4243        }
4244        if let Some(window_handle) = window.window_handle().downcast::<Self>() {
4245            let s = item.on_release(
4246                cx,
4247                Box::new(move |cx| {
4248                    window_handle
4249                        .update(cx, |this, window, cx| {
4250                            this.dirty_items.remove(&item_id);
4251                            this.update_window_edited(window, cx)
4252                        })
4253                        .ok();
4254                }),
4255            );
4256            self.dirty_items.insert(item_id, s);
4257            self.update_window_edited(window, cx);
4258        }
4259    }
4260
4261    fn render_notifications(&self, _window: &mut Window, _cx: &mut Context<Self>) -> Option<Div> {
4262        if self.notifications.is_empty() {
4263            None
4264        } else {
4265            Some(
4266                div()
4267                    .absolute()
4268                    .right_3()
4269                    .bottom_3()
4270                    .w_112()
4271                    .h_full()
4272                    .flex()
4273                    .flex_col()
4274                    .justify_end()
4275                    .gap_2()
4276                    .children(
4277                        self.notifications
4278                            .iter()
4279                            .map(|(_, notification)| notification.clone().into_any()),
4280                    ),
4281            )
4282        }
4283    }
4284
4285    // RPC handlers
4286
4287    fn active_view_for_follower(
4288        &self,
4289        follower_project_id: Option<u64>,
4290        window: &mut Window,
4291        cx: &mut Context<Self>,
4292    ) -> Option<proto::View> {
4293        let (item, panel_id) = self.active_item_for_followers(window, cx);
4294        let item = item?;
4295        let leader_id = self
4296            .pane_for(&*item)
4297            .and_then(|pane| self.leader_for_pane(&pane));
4298        let leader_peer_id = match leader_id {
4299            Some(CollaboratorId::PeerId(peer_id)) => Some(peer_id),
4300            Some(CollaboratorId::Agent) | None => None,
4301        };
4302
4303        let item_handle = item.to_followable_item_handle(cx)?;
4304        let id = item_handle.remote_id(&self.app_state.client, window, cx)?;
4305        let variant = item_handle.to_state_proto(window, cx)?;
4306
4307        if item_handle.is_project_item(window, cx)
4308            && (follower_project_id.is_none()
4309                || follower_project_id != self.project.read(cx).remote_id())
4310        {
4311            return None;
4312        }
4313
4314        Some(proto::View {
4315            id: id.to_proto(),
4316            leader_id: leader_peer_id,
4317            variant: Some(variant),
4318            panel_id: panel_id.map(|id| id as i32),
4319        })
4320    }
4321
4322    fn handle_follow(
4323        &mut self,
4324        follower_project_id: Option<u64>,
4325        window: &mut Window,
4326        cx: &mut Context<Self>,
4327    ) -> proto::FollowResponse {
4328        let active_view = self.active_view_for_follower(follower_project_id, window, cx);
4329
4330        cx.notify();
4331        proto::FollowResponse {
4332            // TODO: Remove after version 0.145.x stabilizes.
4333            active_view_id: active_view.as_ref().and_then(|view| view.id.clone()),
4334            views: active_view.iter().cloned().collect(),
4335            active_view,
4336        }
4337    }
4338
4339    fn handle_update_followers(
4340        &mut self,
4341        leader_id: PeerId,
4342        message: proto::UpdateFollowers,
4343        _window: &mut Window,
4344        _cx: &mut Context<Self>,
4345    ) {
4346        self.leader_updates_tx
4347            .unbounded_send((leader_id, message))
4348            .ok();
4349    }
4350
4351    async fn process_leader_update(
4352        this: &WeakEntity<Self>,
4353        leader_id: PeerId,
4354        update: proto::UpdateFollowers,
4355        cx: &mut AsyncWindowContext,
4356    ) -> Result<()> {
4357        match update.variant.context("invalid update")? {
4358            proto::update_followers::Variant::CreateView(view) => {
4359                let view_id = ViewId::from_proto(view.id.clone().context("invalid view id")?)?;
4360                let should_add_view = this.update(cx, |this, _| {
4361                    if let Some(state) = this.follower_states.get_mut(&leader_id.into()) {
4362                        anyhow::Ok(!state.items_by_leader_view_id.contains_key(&view_id))
4363                    } else {
4364                        anyhow::Ok(false)
4365                    }
4366                })??;
4367
4368                if should_add_view {
4369                    Self::add_view_from_leader(this.clone(), leader_id, &view, cx).await?
4370                }
4371            }
4372            proto::update_followers::Variant::UpdateActiveView(update_active_view) => {
4373                let should_add_view = this.update(cx, |this, _| {
4374                    if let Some(state) = this.follower_states.get_mut(&leader_id.into()) {
4375                        state.active_view_id = update_active_view
4376                            .view
4377                            .as_ref()
4378                            .and_then(|view| ViewId::from_proto(view.id.clone()?).ok());
4379
4380                        if state.active_view_id.is_some_and(|view_id| {
4381                            !state.items_by_leader_view_id.contains_key(&view_id)
4382                        }) {
4383                            anyhow::Ok(true)
4384                        } else {
4385                            anyhow::Ok(false)
4386                        }
4387                    } else {
4388                        anyhow::Ok(false)
4389                    }
4390                })??;
4391
4392                if should_add_view {
4393                    if let Some(view) = update_active_view.view {
4394                        Self::add_view_from_leader(this.clone(), leader_id, &view, cx).await?
4395                    }
4396                }
4397            }
4398            proto::update_followers::Variant::UpdateView(update_view) => {
4399                let variant = update_view.variant.context("missing update view variant")?;
4400                let id = update_view.id.context("missing update view id")?;
4401                let mut tasks = Vec::new();
4402                this.update_in(cx, |this, window, cx| {
4403                    let project = this.project.clone();
4404                    if let Some(state) = this.follower_states.get(&leader_id.into()) {
4405                        let view_id = ViewId::from_proto(id.clone())?;
4406                        if let Some(item) = state.items_by_leader_view_id.get(&view_id) {
4407                            tasks.push(item.view.apply_update_proto(
4408                                &project,
4409                                variant.clone(),
4410                                window,
4411                                cx,
4412                            ));
4413                        }
4414                    }
4415                    anyhow::Ok(())
4416                })??;
4417                try_join_all(tasks).await.log_err();
4418            }
4419        }
4420        this.update_in(cx, |this, window, cx| {
4421            this.leader_updated(leader_id, window, cx)
4422        })?;
4423        Ok(())
4424    }
4425
4426    async fn add_view_from_leader(
4427        this: WeakEntity<Self>,
4428        leader_id: PeerId,
4429        view: &proto::View,
4430        cx: &mut AsyncWindowContext,
4431    ) -> Result<()> {
4432        let this = this.upgrade().context("workspace dropped")?;
4433
4434        let Some(id) = view.id.clone() else {
4435            anyhow::bail!("no id for view");
4436        };
4437        let id = ViewId::from_proto(id)?;
4438        let panel_id = view.panel_id.and_then(proto::PanelId::from_i32);
4439
4440        let pane = this.update(cx, |this, _cx| {
4441            let state = this
4442                .follower_states
4443                .get(&leader_id.into())
4444                .context("stopped following")?;
4445            anyhow::Ok(state.pane().clone())
4446        })??;
4447        let existing_item = pane.update_in(cx, |pane, window, cx| {
4448            let client = this.read(cx).client().clone();
4449            pane.items().find_map(|item| {
4450                let item = item.to_followable_item_handle(cx)?;
4451                if item.remote_id(&client, window, cx) == Some(id) {
4452                    Some(item)
4453                } else {
4454                    None
4455                }
4456            })
4457        })?;
4458        let item = if let Some(existing_item) = existing_item {
4459            existing_item
4460        } else {
4461            let variant = view.variant.clone();
4462            anyhow::ensure!(variant.is_some(), "missing view variant");
4463
4464            let task = cx.update(|window, cx| {
4465                FollowableViewRegistry::from_state_proto(this.clone(), id, variant, window, cx)
4466            })?;
4467
4468            let Some(task) = task else {
4469                anyhow::bail!(
4470                    "failed to construct view from leader (maybe from a different version of zed?)"
4471                );
4472            };
4473
4474            let mut new_item = task.await?;
4475            pane.update_in(cx, |pane, window, cx| {
4476                let mut item_to_remove = None;
4477                for (ix, item) in pane.items().enumerate() {
4478                    if let Some(item) = item.to_followable_item_handle(cx) {
4479                        match new_item.dedup(item.as_ref(), window, cx) {
4480                            Some(item::Dedup::KeepExisting) => {
4481                                new_item =
4482                                    item.boxed_clone().to_followable_item_handle(cx).unwrap();
4483                                break;
4484                            }
4485                            Some(item::Dedup::ReplaceExisting) => {
4486                                item_to_remove = Some((ix, item.item_id()));
4487                                break;
4488                            }
4489                            None => {}
4490                        }
4491                    }
4492                }
4493
4494                if let Some((ix, id)) = item_to_remove {
4495                    pane.remove_item(id, false, false, window, cx);
4496                    pane.add_item(new_item.boxed_clone(), false, false, Some(ix), window, cx);
4497                }
4498            })?;
4499
4500            new_item
4501        };
4502
4503        this.update_in(cx, |this, window, cx| {
4504            let state = this.follower_states.get_mut(&leader_id.into())?;
4505            item.set_leader_id(Some(leader_id.into()), window, cx);
4506            state.items_by_leader_view_id.insert(
4507                id,
4508                FollowerView {
4509                    view: item,
4510                    location: panel_id,
4511                },
4512            );
4513
4514            Some(())
4515        })?;
4516
4517        Ok(())
4518    }
4519
4520    fn handle_agent_location_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4521        let Some(follower_state) = self.follower_states.get_mut(&CollaboratorId::Agent) else {
4522            return;
4523        };
4524
4525        if let Some(agent_location) = self.project.read(cx).agent_location() {
4526            let buffer_entity_id = agent_location.buffer.entity_id();
4527            let view_id = ViewId {
4528                creator: CollaboratorId::Agent,
4529                id: buffer_entity_id.as_u64(),
4530            };
4531            follower_state.active_view_id = Some(view_id);
4532
4533            let item = match follower_state.items_by_leader_view_id.entry(view_id) {
4534                hash_map::Entry::Occupied(entry) => Some(entry.into_mut()),
4535                hash_map::Entry::Vacant(entry) => {
4536                    let existing_view =
4537                        follower_state
4538                            .center_pane
4539                            .read(cx)
4540                            .items()
4541                            .find_map(|item| {
4542                                let item = item.to_followable_item_handle(cx)?;
4543                                if item.is_singleton(cx)
4544                                    && item.project_item_model_ids(cx).as_slice()
4545                                        == [buffer_entity_id]
4546                                {
4547                                    Some(item)
4548                                } else {
4549                                    None
4550                                }
4551                            });
4552                    let view = existing_view.or_else(|| {
4553                        agent_location.buffer.upgrade().and_then(|buffer| {
4554                            cx.update_default_global(|registry: &mut ProjectItemRegistry, cx| {
4555                                registry.build_item(buffer, self.project.clone(), None, window, cx)
4556                            })?
4557                            .to_followable_item_handle(cx)
4558                        })
4559                    });
4560
4561                    if let Some(view) = view {
4562                        Some(entry.insert(FollowerView {
4563                            view,
4564                            location: None,
4565                        }))
4566                    } else {
4567                        None
4568                    }
4569                }
4570            };
4571
4572            if let Some(item) = item {
4573                item.view
4574                    .set_leader_id(Some(CollaboratorId::Agent), window, cx);
4575                item.view
4576                    .update_agent_location(agent_location.position, window, cx);
4577            }
4578        } else {
4579            follower_state.active_view_id = None;
4580        }
4581
4582        self.leader_updated(CollaboratorId::Agent, window, cx);
4583    }
4584
4585    pub fn update_active_view_for_followers(&mut self, window: &mut Window, cx: &mut App) {
4586        let mut is_project_item = true;
4587        let mut update = proto::UpdateActiveView::default();
4588        if window.is_window_active() {
4589            let (active_item, panel_id) = self.active_item_for_followers(window, cx);
4590
4591            if let Some(item) = active_item {
4592                if item.item_focus_handle(cx).contains_focused(window, cx) {
4593                    let leader_id = self
4594                        .pane_for(&*item)
4595                        .and_then(|pane| self.leader_for_pane(&pane));
4596                    let leader_peer_id = match leader_id {
4597                        Some(CollaboratorId::PeerId(peer_id)) => Some(peer_id),
4598                        Some(CollaboratorId::Agent) | None => None,
4599                    };
4600
4601                    if let Some(item) = item.to_followable_item_handle(cx) {
4602                        let id = item
4603                            .remote_id(&self.app_state.client, window, cx)
4604                            .map(|id| id.to_proto());
4605
4606                        if let Some(id) = id.clone() {
4607                            if let Some(variant) = item.to_state_proto(window, cx) {
4608                                let view = Some(proto::View {
4609                                    id: id.clone(),
4610                                    leader_id: leader_peer_id,
4611                                    variant: Some(variant),
4612                                    panel_id: panel_id.map(|id| id as i32),
4613                                });
4614
4615                                is_project_item = item.is_project_item(window, cx);
4616                                update = proto::UpdateActiveView {
4617                                    view,
4618                                    // TODO: Remove after version 0.145.x stabilizes.
4619                                    id: id.clone(),
4620                                    leader_id: leader_peer_id,
4621                                };
4622                            }
4623                        };
4624                    }
4625                }
4626            }
4627        }
4628
4629        let active_view_id = update.view.as_ref().and_then(|view| view.id.as_ref());
4630        if active_view_id != self.last_active_view_id.as_ref() {
4631            self.last_active_view_id = active_view_id.cloned();
4632            self.update_followers(
4633                is_project_item,
4634                proto::update_followers::Variant::UpdateActiveView(update),
4635                window,
4636                cx,
4637            );
4638        }
4639    }
4640
4641    fn active_item_for_followers(
4642        &self,
4643        window: &mut Window,
4644        cx: &mut App,
4645    ) -> (Option<Box<dyn ItemHandle>>, Option<proto::PanelId>) {
4646        let mut active_item = None;
4647        let mut panel_id = None;
4648        for dock in self.all_docks() {
4649            if dock.focus_handle(cx).contains_focused(window, cx) {
4650                if let Some(panel) = dock.read(cx).active_panel() {
4651                    if let Some(pane) = panel.pane(cx) {
4652                        if let Some(item) = pane.read(cx).active_item() {
4653                            active_item = Some(item);
4654                            panel_id = panel.remote_id();
4655                            break;
4656                        }
4657                    }
4658                }
4659            }
4660        }
4661
4662        if active_item.is_none() {
4663            active_item = self.active_pane().read(cx).active_item();
4664        }
4665        (active_item, panel_id)
4666    }
4667
4668    fn update_followers(
4669        &self,
4670        project_only: bool,
4671        update: proto::update_followers::Variant,
4672        _: &mut Window,
4673        cx: &mut App,
4674    ) -> Option<()> {
4675        // If this update only applies to for followers in the current project,
4676        // then skip it unless this project is shared. If it applies to all
4677        // followers, regardless of project, then set `project_id` to none,
4678        // indicating that it goes to all followers.
4679        let project_id = if project_only {
4680            Some(self.project.read(cx).remote_id()?)
4681        } else {
4682            None
4683        };
4684        self.app_state().workspace_store.update(cx, |store, cx| {
4685            store.update_followers(project_id, update, cx)
4686        })
4687    }
4688
4689    pub fn leader_for_pane(&self, pane: &Entity<Pane>) -> Option<CollaboratorId> {
4690        self.follower_states.iter().find_map(|(leader_id, state)| {
4691            if state.center_pane == *pane || state.dock_pane.as_ref() == Some(pane) {
4692                Some(*leader_id)
4693            } else {
4694                None
4695            }
4696        })
4697    }
4698
4699    fn leader_updated(
4700        &mut self,
4701        leader_id: impl Into<CollaboratorId>,
4702        window: &mut Window,
4703        cx: &mut Context<Self>,
4704    ) -> Option<Box<dyn ItemHandle>> {
4705        cx.notify();
4706
4707        let leader_id = leader_id.into();
4708        let (panel_id, item) = match leader_id {
4709            CollaboratorId::PeerId(peer_id) => self.active_item_for_peer(peer_id, window, cx)?,
4710            CollaboratorId::Agent => (None, self.active_item_for_agent()?),
4711        };
4712
4713        let state = self.follower_states.get(&leader_id)?;
4714        let mut transfer_focus = state.center_pane.read(cx).has_focus(window, cx);
4715        let pane;
4716        if let Some(panel_id) = panel_id {
4717            pane = self
4718                .activate_panel_for_proto_id(panel_id, window, cx)?
4719                .pane(cx)?;
4720            let state = self.follower_states.get_mut(&leader_id)?;
4721            state.dock_pane = Some(pane.clone());
4722        } else {
4723            pane = state.center_pane.clone();
4724            let state = self.follower_states.get_mut(&leader_id)?;
4725            if let Some(dock_pane) = state.dock_pane.take() {
4726                transfer_focus |= dock_pane.focus_handle(cx).contains_focused(window, cx);
4727            }
4728        }
4729
4730        pane.update(cx, |pane, cx| {
4731            let focus_active_item = pane.has_focus(window, cx) || transfer_focus;
4732            if let Some(index) = pane.index_for_item(item.as_ref()) {
4733                pane.activate_item(index, false, false, window, cx);
4734            } else {
4735                pane.add_item(item.boxed_clone(), false, false, None, window, cx)
4736            }
4737
4738            if focus_active_item {
4739                pane.focus_active_item(window, cx)
4740            }
4741        });
4742
4743        Some(item)
4744    }
4745
4746    fn active_item_for_agent(&self) -> Option<Box<dyn ItemHandle>> {
4747        let state = self.follower_states.get(&CollaboratorId::Agent)?;
4748        let active_view_id = state.active_view_id?;
4749        Some(
4750            state
4751                .items_by_leader_view_id
4752                .get(&active_view_id)?
4753                .view
4754                .boxed_clone(),
4755        )
4756    }
4757
4758    fn active_item_for_peer(
4759        &self,
4760        peer_id: PeerId,
4761        window: &mut Window,
4762        cx: &mut Context<Self>,
4763    ) -> Option<(Option<PanelId>, Box<dyn ItemHandle>)> {
4764        let call = self.active_call()?;
4765        let room = call.read(cx).room()?.read(cx);
4766        let participant = room.remote_participant_for_peer_id(peer_id)?;
4767        let leader_in_this_app;
4768        let leader_in_this_project;
4769        match participant.location {
4770            call::ParticipantLocation::SharedProject { project_id } => {
4771                leader_in_this_app = true;
4772                leader_in_this_project = Some(project_id) == self.project.read(cx).remote_id();
4773            }
4774            call::ParticipantLocation::UnsharedProject => {
4775                leader_in_this_app = true;
4776                leader_in_this_project = false;
4777            }
4778            call::ParticipantLocation::External => {
4779                leader_in_this_app = false;
4780                leader_in_this_project = false;
4781            }
4782        };
4783        let state = self.follower_states.get(&peer_id.into())?;
4784        let mut item_to_activate = None;
4785        if let (Some(active_view_id), true) = (state.active_view_id, leader_in_this_app) {
4786            if let Some(item) = state.items_by_leader_view_id.get(&active_view_id) {
4787                if leader_in_this_project || !item.view.is_project_item(window, cx) {
4788                    item_to_activate = Some((item.location, item.view.boxed_clone()));
4789                }
4790            }
4791        } else if let Some(shared_screen) =
4792            self.shared_screen_for_peer(peer_id, &state.center_pane, window, cx)
4793        {
4794            item_to_activate = Some((None, Box::new(shared_screen)));
4795        }
4796        item_to_activate
4797    }
4798
4799    fn shared_screen_for_peer(
4800        &self,
4801        peer_id: PeerId,
4802        pane: &Entity<Pane>,
4803        window: &mut Window,
4804        cx: &mut App,
4805    ) -> Option<Entity<SharedScreen>> {
4806        let call = self.active_call()?;
4807        let room = call.read(cx).room()?.clone();
4808        let participant = room.read(cx).remote_participant_for_peer_id(peer_id)?;
4809        let track = participant.video_tracks.values().next()?.clone();
4810        let user = participant.user.clone();
4811
4812        for item in pane.read(cx).items_of_type::<SharedScreen>() {
4813            if item.read(cx).peer_id == peer_id {
4814                return Some(item);
4815            }
4816        }
4817
4818        Some(cx.new(|cx| SharedScreen::new(track, peer_id, user.clone(), room.clone(), window, cx)))
4819    }
4820
4821    pub fn on_window_activation_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4822        if window.is_window_active() {
4823            self.update_active_view_for_followers(window, cx);
4824
4825            if let Some(database_id) = self.database_id {
4826                cx.background_spawn(persistence::DB.update_timestamp(database_id))
4827                    .detach();
4828            }
4829        } else {
4830            for pane in &self.panes {
4831                pane.update(cx, |pane, cx| {
4832                    if let Some(item) = pane.active_item() {
4833                        item.workspace_deactivated(window, cx);
4834                    }
4835                    for item in pane.items() {
4836                        if matches!(
4837                            item.workspace_settings(cx).autosave,
4838                            AutosaveSetting::OnWindowChange | AutosaveSetting::OnFocusChange
4839                        ) {
4840                            Pane::autosave_item(item.as_ref(), self.project.clone(), window, cx)
4841                                .detach_and_log_err(cx);
4842                        }
4843                    }
4844                });
4845            }
4846        }
4847    }
4848
4849    pub fn active_call(&self) -> Option<&Entity<ActiveCall>> {
4850        self.active_call.as_ref().map(|(call, _)| call)
4851    }
4852
4853    fn on_active_call_event(
4854        &mut self,
4855        _: &Entity<ActiveCall>,
4856        event: &call::room::Event,
4857        window: &mut Window,
4858        cx: &mut Context<Self>,
4859    ) {
4860        match event {
4861            call::room::Event::ParticipantLocationChanged { participant_id }
4862            | call::room::Event::RemoteVideoTracksChanged { participant_id } => {
4863                self.leader_updated(participant_id, window, cx);
4864            }
4865            _ => {}
4866        }
4867    }
4868
4869    pub fn database_id(&self) -> Option<WorkspaceId> {
4870        self.database_id
4871    }
4872
4873    pub fn session_id(&self) -> Option<String> {
4874        self.session_id.clone()
4875    }
4876
4877    fn local_paths(&self, cx: &App) -> Option<Vec<Arc<Path>>> {
4878        let project = self.project().read(cx);
4879
4880        if project.is_local() {
4881            Some(
4882                project
4883                    .visible_worktrees(cx)
4884                    .map(|worktree| worktree.read(cx).abs_path())
4885                    .collect::<Vec<_>>(),
4886            )
4887        } else {
4888            None
4889        }
4890    }
4891
4892    fn remove_panes(&mut self, member: Member, window: &mut Window, cx: &mut Context<Workspace>) {
4893        match member {
4894            Member::Axis(PaneAxis { members, .. }) => {
4895                for child in members.iter() {
4896                    self.remove_panes(child.clone(), window, cx)
4897                }
4898            }
4899            Member::Pane(pane) => {
4900                self.force_remove_pane(&pane, &None, window, cx);
4901            }
4902        }
4903    }
4904
4905    fn remove_from_session(&mut self, window: &mut Window, cx: &mut App) -> Task<()> {
4906        self.session_id.take();
4907        self.serialize_workspace_internal(window, cx)
4908    }
4909
4910    fn force_remove_pane(
4911        &mut self,
4912        pane: &Entity<Pane>,
4913        focus_on: &Option<Entity<Pane>>,
4914        window: &mut Window,
4915        cx: &mut Context<Workspace>,
4916    ) {
4917        self.panes.retain(|p| p != pane);
4918        if let Some(focus_on) = focus_on {
4919            focus_on.update(cx, |pane, cx| window.focus(&pane.focus_handle(cx)));
4920        } else {
4921            if self.active_pane() == pane {
4922                self.panes
4923                    .last()
4924                    .unwrap()
4925                    .update(cx, |pane, cx| window.focus(&pane.focus_handle(cx)));
4926            }
4927        }
4928        if self.last_active_center_pane == Some(pane.downgrade()) {
4929            self.last_active_center_pane = None;
4930        }
4931        cx.notify();
4932    }
4933
4934    fn serialize_workspace(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4935        if self._schedule_serialize.is_none() {
4936            self._schedule_serialize = Some(cx.spawn_in(window, async move |this, cx| {
4937                cx.background_executor()
4938                    .timer(Duration::from_millis(100))
4939                    .await;
4940                this.update_in(cx, |this, window, cx| {
4941                    this.serialize_workspace_internal(window, cx).detach();
4942                    this._schedule_serialize.take();
4943                })
4944                .log_err();
4945            }));
4946        }
4947    }
4948
4949    fn serialize_workspace_internal(&self, window: &mut Window, cx: &mut App) -> Task<()> {
4950        let Some(database_id) = self.database_id() else {
4951            return Task::ready(());
4952        };
4953
4954        fn serialize_pane_handle(
4955            pane_handle: &Entity<Pane>,
4956            window: &mut Window,
4957            cx: &mut App,
4958        ) -> SerializedPane {
4959            let (items, active, pinned_count) = {
4960                let pane = pane_handle.read(cx);
4961                let active_item_id = pane.active_item().map(|item| item.item_id());
4962                (
4963                    pane.items()
4964                        .filter_map(|handle| {
4965                            let handle = handle.to_serializable_item_handle(cx)?;
4966
4967                            Some(SerializedItem {
4968                                kind: Arc::from(handle.serialized_item_kind()),
4969                                item_id: handle.item_id().as_u64(),
4970                                active: Some(handle.item_id()) == active_item_id,
4971                                preview: pane.is_active_preview_item(handle.item_id()),
4972                            })
4973                        })
4974                        .collect::<Vec<_>>(),
4975                    pane.has_focus(window, cx),
4976                    pane.pinned_count(),
4977                )
4978            };
4979
4980            SerializedPane::new(items, active, pinned_count)
4981        }
4982
4983        fn build_serialized_pane_group(
4984            pane_group: &Member,
4985            window: &mut Window,
4986            cx: &mut App,
4987        ) -> SerializedPaneGroup {
4988            match pane_group {
4989                Member::Axis(PaneAxis {
4990                    axis,
4991                    members,
4992                    flexes,
4993                    bounding_boxes: _,
4994                }) => SerializedPaneGroup::Group {
4995                    axis: SerializedAxis(*axis),
4996                    children: members
4997                        .iter()
4998                        .map(|member| build_serialized_pane_group(member, window, cx))
4999                        .collect::<Vec<_>>(),
5000                    flexes: Some(flexes.lock().clone()),
5001                },
5002                Member::Pane(pane_handle) => {
5003                    SerializedPaneGroup::Pane(serialize_pane_handle(pane_handle, window, cx))
5004                }
5005            }
5006        }
5007
5008        fn build_serialized_docks(
5009            this: &Workspace,
5010            window: &mut Window,
5011            cx: &mut App,
5012        ) -> DockStructure {
5013            let left_dock = this.left_dock.read(cx);
5014            let left_visible = left_dock.is_open();
5015            let left_active_panel = left_dock
5016                .active_panel()
5017                .map(|panel| panel.persistent_name().to_string());
5018            let left_dock_zoom = left_dock
5019                .active_panel()
5020                .map(|panel| panel.is_zoomed(window, cx))
5021                .unwrap_or(false);
5022
5023            let right_dock = this.right_dock.read(cx);
5024            let right_visible = right_dock.is_open();
5025            let right_active_panel = right_dock
5026                .active_panel()
5027                .map(|panel| panel.persistent_name().to_string());
5028            let right_dock_zoom = right_dock
5029                .active_panel()
5030                .map(|panel| panel.is_zoomed(window, cx))
5031                .unwrap_or(false);
5032
5033            let bottom_dock = this.bottom_dock.read(cx);
5034            let bottom_visible = bottom_dock.is_open();
5035            let bottom_active_panel = bottom_dock
5036                .active_panel()
5037                .map(|panel| panel.persistent_name().to_string());
5038            let bottom_dock_zoom = bottom_dock
5039                .active_panel()
5040                .map(|panel| panel.is_zoomed(window, cx))
5041                .unwrap_or(false);
5042
5043            DockStructure {
5044                left: DockData {
5045                    visible: left_visible,
5046                    active_panel: left_active_panel,
5047                    zoom: left_dock_zoom,
5048                },
5049                right: DockData {
5050                    visible: right_visible,
5051                    active_panel: right_active_panel,
5052                    zoom: right_dock_zoom,
5053                },
5054                bottom: DockData {
5055                    visible: bottom_visible,
5056                    active_panel: bottom_active_panel,
5057                    zoom: bottom_dock_zoom,
5058                },
5059            }
5060        }
5061
5062        if let Some(location) = self.serialize_workspace_location(cx) {
5063            let breakpoints = self.project.update(cx, |project, cx| {
5064                project
5065                    .breakpoint_store()
5066                    .read(cx)
5067                    .all_source_breakpoints(cx)
5068            });
5069
5070            let center_group = build_serialized_pane_group(&self.center.root, window, cx);
5071            let docks = build_serialized_docks(self, window, cx);
5072            let window_bounds = Some(SerializedWindowBounds(window.window_bounds()));
5073            let serialized_workspace = SerializedWorkspace {
5074                id: database_id,
5075                location,
5076                center_group,
5077                window_bounds,
5078                display: Default::default(),
5079                docks,
5080                centered_layout: self.centered_layout,
5081                session_id: self.session_id.clone(),
5082                breakpoints,
5083                window_id: Some(window.window_handle().window_id().as_u64()),
5084            };
5085
5086            return window.spawn(cx, async move |_| {
5087                persistence::DB.save_workspace(serialized_workspace).await;
5088            });
5089        }
5090        Task::ready(())
5091    }
5092
5093    fn serialize_workspace_location(&self, cx: &App) -> Option<SerializedWorkspaceLocation> {
5094        if let Some(ssh_project) = &self.serialized_ssh_project {
5095            Some(SerializedWorkspaceLocation::Ssh(ssh_project.clone()))
5096        } else if let Some(local_paths) = self.local_paths(cx) {
5097            if !local_paths.is_empty() {
5098                Some(SerializedWorkspaceLocation::from_local_paths(local_paths))
5099            } else {
5100                None
5101            }
5102        } else {
5103            None
5104        }
5105    }
5106
5107    fn update_history(&self, cx: &mut App) {
5108        let Some(id) = self.database_id() else {
5109            return;
5110        };
5111        let Some(location) = self.serialize_workspace_location(cx) else {
5112            return;
5113        };
5114        if let Some(manager) = HistoryManager::global(cx) {
5115            manager.update(cx, |this, cx| {
5116                this.update_history(id, HistoryManagerEntry::new(id, &location), cx);
5117            });
5118        }
5119    }
5120
5121    async fn serialize_items(
5122        this: &WeakEntity<Self>,
5123        items_rx: UnboundedReceiver<Box<dyn SerializableItemHandle>>,
5124        cx: &mut AsyncWindowContext,
5125    ) -> Result<()> {
5126        const CHUNK_SIZE: usize = 200;
5127
5128        let mut serializable_items = items_rx.ready_chunks(CHUNK_SIZE);
5129
5130        while let Some(items_received) = serializable_items.next().await {
5131            let unique_items =
5132                items_received
5133                    .into_iter()
5134                    .fold(HashMap::default(), |mut acc, item| {
5135                        acc.entry(item.item_id()).or_insert(item);
5136                        acc
5137                    });
5138
5139            // We use into_iter() here so that the references to the items are moved into
5140            // the tasks and not kept alive while we're sleeping.
5141            for (_, item) in unique_items.into_iter() {
5142                if let Ok(Some(task)) = this.update_in(cx, |workspace, window, cx| {
5143                    item.serialize(workspace, false, window, cx)
5144                }) {
5145                    cx.background_spawn(async move { task.await.log_err() })
5146                        .detach();
5147                }
5148            }
5149
5150            cx.background_executor()
5151                .timer(SERIALIZATION_THROTTLE_TIME)
5152                .await;
5153        }
5154
5155        Ok(())
5156    }
5157
5158    pub(crate) fn enqueue_item_serialization(
5159        &mut self,
5160        item: Box<dyn SerializableItemHandle>,
5161    ) -> Result<()> {
5162        self.serializable_items_tx
5163            .unbounded_send(item)
5164            .map_err(|err| anyhow!("failed to send serializable item over channel: {err}"))
5165    }
5166
5167    pub(crate) fn load_workspace(
5168        serialized_workspace: SerializedWorkspace,
5169        paths_to_open: Vec<Option<ProjectPath>>,
5170        window: &mut Window,
5171        cx: &mut Context<Workspace>,
5172    ) -> Task<Result<Vec<Option<Box<dyn ItemHandle>>>>> {
5173        cx.spawn_in(window, async move |workspace, cx| {
5174            let project = workspace.read_with(cx, |workspace, _| workspace.project().clone())?;
5175
5176            let mut center_group = None;
5177            let mut center_items = None;
5178
5179            // Traverse the splits tree and add to things
5180            if let Some((group, active_pane, items)) = serialized_workspace
5181                .center_group
5182                .deserialize(&project, serialized_workspace.id, workspace.clone(), cx)
5183                .await
5184            {
5185                center_items = Some(items);
5186                center_group = Some((group, active_pane))
5187            }
5188
5189            let mut items_by_project_path = HashMap::default();
5190            let mut item_ids_by_kind = HashMap::default();
5191            let mut all_deserialized_items = Vec::default();
5192            cx.update(|_, cx| {
5193                for item in center_items.unwrap_or_default().into_iter().flatten() {
5194                    if let Some(serializable_item_handle) = item.to_serializable_item_handle(cx) {
5195                        item_ids_by_kind
5196                            .entry(serializable_item_handle.serialized_item_kind())
5197                            .or_insert(Vec::new())
5198                            .push(item.item_id().as_u64() as ItemId);
5199                    }
5200
5201                    if let Some(project_path) = item.project_path(cx) {
5202                        items_by_project_path.insert(project_path, item.clone());
5203                    }
5204                    all_deserialized_items.push(item);
5205                }
5206            })?;
5207
5208            let opened_items = paths_to_open
5209                .into_iter()
5210                .map(|path_to_open| {
5211                    path_to_open
5212                        .and_then(|path_to_open| items_by_project_path.remove(&path_to_open))
5213                })
5214                .collect::<Vec<_>>();
5215
5216            // Remove old panes from workspace panes list
5217            workspace.update_in(cx, |workspace, window, cx| {
5218                if let Some((center_group, active_pane)) = center_group {
5219                    workspace.remove_panes(workspace.center.root.clone(), window, cx);
5220
5221                    // Swap workspace center group
5222                    workspace.center = PaneGroup::with_root(center_group);
5223                    if let Some(active_pane) = active_pane {
5224                        workspace.set_active_pane(&active_pane, window, cx);
5225                        cx.focus_self(window);
5226                    } else {
5227                        workspace.set_active_pane(&workspace.center.first_pane(), window, cx);
5228                    }
5229                }
5230
5231                let docks = serialized_workspace.docks;
5232
5233                for (dock, serialized_dock) in [
5234                    (&mut workspace.right_dock, docks.right),
5235                    (&mut workspace.left_dock, docks.left),
5236                    (&mut workspace.bottom_dock, docks.bottom),
5237                ]
5238                .iter_mut()
5239                {
5240                    dock.update(cx, |dock, cx| {
5241                        dock.serialized_dock = Some(serialized_dock.clone());
5242                        dock.restore_state(window, cx);
5243                    });
5244                }
5245
5246                cx.notify();
5247            })?;
5248
5249            let _ = project
5250                .update(cx, |project, cx| {
5251                    project
5252                        .breakpoint_store()
5253                        .update(cx, |breakpoint_store, cx| {
5254                            breakpoint_store
5255                                .with_serialized_breakpoints(serialized_workspace.breakpoints, cx)
5256                        })
5257                })?
5258                .await;
5259
5260            // Clean up all the items that have _not_ been loaded. Our ItemIds aren't stable. That means
5261            // after loading the items, we might have different items and in order to avoid
5262            // the database filling up, we delete items that haven't been loaded now.
5263            //
5264            // The items that have been loaded, have been saved after they've been added to the workspace.
5265            let clean_up_tasks = workspace.update_in(cx, |_, window, cx| {
5266                item_ids_by_kind
5267                    .into_iter()
5268                    .map(|(item_kind, loaded_items)| {
5269                        SerializableItemRegistry::cleanup(
5270                            item_kind,
5271                            serialized_workspace.id,
5272                            loaded_items,
5273                            window,
5274                            cx,
5275                        )
5276                        .log_err()
5277                    })
5278                    .collect::<Vec<_>>()
5279            })?;
5280
5281            futures::future::join_all(clean_up_tasks).await;
5282
5283            workspace
5284                .update_in(cx, |workspace, window, cx| {
5285                    // Serialize ourself to make sure our timestamps and any pane / item changes are replicated
5286                    workspace.serialize_workspace_internal(window, cx).detach();
5287
5288                    // Ensure that we mark the window as edited if we did load dirty items
5289                    workspace.update_window_edited(window, cx);
5290                })
5291                .ok();
5292
5293            Ok(opened_items)
5294        })
5295    }
5296
5297    fn actions(&self, div: Div, window: &mut Window, cx: &mut Context<Self>) -> Div {
5298        self.add_workspace_actions_listeners(div, window, cx)
5299            .on_action(cx.listener(Self::close_inactive_items_and_panes))
5300            .on_action(cx.listener(Self::close_all_items_and_panes))
5301            .on_action(cx.listener(Self::save_all))
5302            .on_action(cx.listener(Self::send_keystrokes))
5303            .on_action(cx.listener(Self::add_folder_to_project))
5304            .on_action(cx.listener(Self::follow_next_collaborator))
5305            .on_action(cx.listener(Self::close_window))
5306            .on_action(cx.listener(Self::activate_pane_at_index))
5307            .on_action(cx.listener(Self::move_item_to_pane_at_index))
5308            .on_action(cx.listener(Self::move_focused_panel_to_next_position))
5309            .on_action(cx.listener(|workspace, _: &Unfollow, window, cx| {
5310                let pane = workspace.active_pane().clone();
5311                workspace.unfollow_in_pane(&pane, window, cx);
5312            }))
5313            .on_action(cx.listener(|workspace, action: &Save, window, cx| {
5314                workspace
5315                    .save_active_item(action.save_intent.unwrap_or(SaveIntent::Save), window, cx)
5316                    .detach_and_prompt_err("Failed to save", window, cx, |_, _, _| None);
5317            }))
5318            .on_action(cx.listener(|workspace, _: &SaveWithoutFormat, window, cx| {
5319                workspace
5320                    .save_active_item(SaveIntent::SaveWithoutFormat, window, cx)
5321                    .detach_and_prompt_err("Failed to save", window, cx, |_, _, _| None);
5322            }))
5323            .on_action(cx.listener(|workspace, _: &SaveAs, window, cx| {
5324                workspace
5325                    .save_active_item(SaveIntent::SaveAs, window, cx)
5326                    .detach_and_prompt_err("Failed to save", window, cx, |_, _, _| None);
5327            }))
5328            .on_action(
5329                cx.listener(|workspace, _: &ActivatePreviousPane, window, cx| {
5330                    workspace.activate_previous_pane(window, cx)
5331                }),
5332            )
5333            .on_action(cx.listener(|workspace, _: &ActivateNextPane, window, cx| {
5334                workspace.activate_next_pane(window, cx)
5335            }))
5336            .on_action(
5337                cx.listener(|workspace, _: &ActivateNextWindow, _window, cx| {
5338                    workspace.activate_next_window(cx)
5339                }),
5340            )
5341            .on_action(
5342                cx.listener(|workspace, _: &ActivatePreviousWindow, _window, cx| {
5343                    workspace.activate_previous_window(cx)
5344                }),
5345            )
5346            .on_action(cx.listener(|workspace, _: &ActivatePaneLeft, window, cx| {
5347                workspace.activate_pane_in_direction(SplitDirection::Left, window, cx)
5348            }))
5349            .on_action(cx.listener(|workspace, _: &ActivatePaneRight, window, cx| {
5350                workspace.activate_pane_in_direction(SplitDirection::Right, window, cx)
5351            }))
5352            .on_action(cx.listener(|workspace, _: &ActivatePaneUp, window, cx| {
5353                workspace.activate_pane_in_direction(SplitDirection::Up, window, cx)
5354            }))
5355            .on_action(cx.listener(|workspace, _: &ActivatePaneDown, window, cx| {
5356                workspace.activate_pane_in_direction(SplitDirection::Down, window, cx)
5357            }))
5358            .on_action(cx.listener(|workspace, _: &ActivateNextPane, window, cx| {
5359                workspace.activate_next_pane(window, cx)
5360            }))
5361            .on_action(cx.listener(
5362                |workspace, action: &MoveItemToPaneInDirection, window, cx| {
5363                    workspace.move_item_to_pane_in_direction(action, window, cx)
5364                },
5365            ))
5366            .on_action(cx.listener(|workspace, _: &SwapPaneLeft, _, cx| {
5367                workspace.swap_pane_in_direction(SplitDirection::Left, cx)
5368            }))
5369            .on_action(cx.listener(|workspace, _: &SwapPaneRight, _, cx| {
5370                workspace.swap_pane_in_direction(SplitDirection::Right, cx)
5371            }))
5372            .on_action(cx.listener(|workspace, _: &SwapPaneUp, _, cx| {
5373                workspace.swap_pane_in_direction(SplitDirection::Up, cx)
5374            }))
5375            .on_action(cx.listener(|workspace, _: &SwapPaneDown, _, cx| {
5376                workspace.swap_pane_in_direction(SplitDirection::Down, cx)
5377            }))
5378            .on_action(cx.listener(|this, _: &ToggleLeftDock, window, cx| {
5379                this.toggle_dock(DockPosition::Left, window, cx);
5380            }))
5381            .on_action(cx.listener(
5382                |workspace: &mut Workspace, _: &ToggleRightDock, window, cx| {
5383                    workspace.toggle_dock(DockPosition::Right, window, cx);
5384                },
5385            ))
5386            .on_action(cx.listener(
5387                |workspace: &mut Workspace, _: &ToggleBottomDock, window, cx| {
5388                    workspace.toggle_dock(DockPosition::Bottom, window, cx);
5389                },
5390            ))
5391            .on_action(cx.listener(
5392                |workspace: &mut Workspace, _: &CloseActiveDock, window, cx| {
5393                    workspace.close_active_dock(window, cx);
5394                },
5395            ))
5396            .on_action(
5397                cx.listener(|workspace: &mut Workspace, _: &CloseAllDocks, window, cx| {
5398                    workspace.close_all_docks(window, cx);
5399                }),
5400            )
5401            .on_action(cx.listener(
5402                |workspace: &mut Workspace, _: &ClearAllNotifications, _, cx| {
5403                    workspace.clear_all_notifications(cx);
5404                },
5405            ))
5406            .on_action(cx.listener(
5407                |workspace: &mut Workspace, _: &SuppressNotification, _, cx| {
5408                    if let Some((notification_id, _)) = workspace.notifications.pop() {
5409                        workspace.suppress_notification(&notification_id, cx);
5410                    }
5411                },
5412            ))
5413            .on_action(cx.listener(
5414                |workspace: &mut Workspace, _: &ReopenClosedItem, window, cx| {
5415                    workspace.reopen_closed_item(window, cx).detach();
5416                },
5417            ))
5418            .on_action(cx.listener(Workspace::toggle_centered_layout))
5419            .on_action(cx.listener(Workspace::cancel))
5420    }
5421
5422    #[cfg(any(test, feature = "test-support"))]
5423    pub fn test_new(project: Entity<Project>, window: &mut Window, cx: &mut Context<Self>) -> Self {
5424        use node_runtime::NodeRuntime;
5425        use session::Session;
5426
5427        let client = project.read(cx).client();
5428        let user_store = project.read(cx).user_store();
5429
5430        let workspace_store = cx.new(|cx| WorkspaceStore::new(client.clone(), cx));
5431        let session = cx.new(|cx| AppSession::new(Session::test(), cx));
5432        window.activate_window();
5433        let app_state = Arc::new(AppState {
5434            languages: project.read(cx).languages().clone(),
5435            workspace_store,
5436            client,
5437            user_store,
5438            fs: project.read(cx).fs().clone(),
5439            build_window_options: |_, _| Default::default(),
5440            node_runtime: NodeRuntime::unavailable(),
5441            session,
5442        });
5443        let workspace = Self::new(Default::default(), project, app_state, window, cx);
5444        workspace
5445            .active_pane
5446            .update(cx, |pane, cx| window.focus(&pane.focus_handle(cx)));
5447        workspace
5448    }
5449
5450    pub fn register_action<A: Action>(
5451        &mut self,
5452        callback: impl Fn(&mut Self, &A, &mut Window, &mut Context<Self>) + 'static,
5453    ) -> &mut Self {
5454        let callback = Arc::new(callback);
5455
5456        self.workspace_actions.push(Box::new(move |div, _, cx| {
5457            let callback = callback.clone();
5458            div.on_action(cx.listener(move |workspace, event, window, cx| {
5459                (callback)(workspace, event, window, cx)
5460            }))
5461        }));
5462        self
5463    }
5464
5465    fn add_workspace_actions_listeners(
5466        &self,
5467        mut div: Div,
5468        window: &mut Window,
5469        cx: &mut Context<Self>,
5470    ) -> Div {
5471        for action in self.workspace_actions.iter() {
5472            div = (action)(div, window, cx)
5473        }
5474        div
5475    }
5476
5477    pub fn has_active_modal(&self, _: &mut Window, cx: &mut App) -> bool {
5478        self.modal_layer.read(cx).has_active_modal()
5479    }
5480
5481    pub fn active_modal<V: ManagedView + 'static>(&self, cx: &App) -> Option<Entity<V>> {
5482        self.modal_layer.read(cx).active_modal()
5483    }
5484
5485    pub fn toggle_modal<V: ModalView, B>(&mut self, window: &mut Window, cx: &mut App, build: B)
5486    where
5487        B: FnOnce(&mut Window, &mut Context<V>) -> V,
5488    {
5489        self.modal_layer.update(cx, |modal_layer, cx| {
5490            modal_layer.toggle_modal(window, cx, build)
5491        })
5492    }
5493
5494    pub fn toggle_status_toast<V: ToastView>(&mut self, entity: Entity<V>, cx: &mut App) {
5495        self.toast_layer
5496            .update(cx, |toast_layer, cx| toast_layer.toggle_toast(cx, entity))
5497    }
5498
5499    pub fn toggle_centered_layout(
5500        &mut self,
5501        _: &ToggleCenteredLayout,
5502        _: &mut Window,
5503        cx: &mut Context<Self>,
5504    ) {
5505        self.centered_layout = !self.centered_layout;
5506        if let Some(database_id) = self.database_id() {
5507            cx.background_spawn(DB.set_centered_layout(database_id, self.centered_layout))
5508                .detach_and_log_err(cx);
5509        }
5510        cx.notify();
5511    }
5512
5513    fn adjust_padding(padding: Option<f32>) -> f32 {
5514        padding
5515            .unwrap_or(Self::DEFAULT_PADDING)
5516            .clamp(0.0, Self::MAX_PADDING)
5517    }
5518
5519    fn render_dock(
5520        &self,
5521        position: DockPosition,
5522        dock: &Entity<Dock>,
5523        window: &mut Window,
5524        cx: &mut App,
5525    ) -> Option<Div> {
5526        if self.zoomed_position == Some(position) {
5527            return None;
5528        }
5529
5530        let leader_border = dock.read(cx).active_panel().and_then(|panel| {
5531            let pane = panel.pane(cx)?;
5532            let follower_states = &self.follower_states;
5533            leader_border_for_pane(follower_states, &pane, window, cx)
5534        });
5535
5536        Some(
5537            div()
5538                .flex()
5539                .flex_none()
5540                .overflow_hidden()
5541                .child(dock.clone())
5542                .children(leader_border),
5543        )
5544    }
5545
5546    pub fn for_window(window: &mut Window, _: &mut App) -> Option<Entity<Workspace>> {
5547        window.root().flatten()
5548    }
5549
5550    pub fn zoomed_item(&self) -> Option<&AnyWeakView> {
5551        self.zoomed.as_ref()
5552    }
5553
5554    pub fn activate_next_window(&mut self, cx: &mut Context<Self>) {
5555        let Some(current_window_id) = cx.active_window().map(|a| a.window_id()) else {
5556            return;
5557        };
5558        let windows = cx.windows();
5559        let Some(next_window) = windows
5560            .iter()
5561            .cycle()
5562            .skip_while(|window| window.window_id() != current_window_id)
5563            .nth(1)
5564        else {
5565            return;
5566        };
5567        next_window
5568            .update(cx, |_, window, _| window.activate_window())
5569            .ok();
5570    }
5571
5572    pub fn activate_previous_window(&mut self, cx: &mut Context<Self>) {
5573        let Some(current_window_id) = cx.active_window().map(|a| a.window_id()) else {
5574            return;
5575        };
5576        let windows = cx.windows();
5577        let Some(prev_window) = windows
5578            .iter()
5579            .rev()
5580            .cycle()
5581            .skip_while(|window| window.window_id() != current_window_id)
5582            .nth(1)
5583        else {
5584            return;
5585        };
5586        prev_window
5587            .update(cx, |_, window, _| window.activate_window())
5588            .ok();
5589    }
5590
5591    pub fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
5592        if cx.stop_active_drag(window) {
5593            return;
5594        } else if let Some((notification_id, _)) = self.notifications.pop() {
5595            dismiss_app_notification(&notification_id, cx);
5596        } else {
5597            cx.emit(Event::ClearActivityIndicator);
5598            cx.propagate();
5599        }
5600    }
5601}
5602
5603fn leader_border_for_pane(
5604    follower_states: &HashMap<CollaboratorId, FollowerState>,
5605    pane: &Entity<Pane>,
5606    _: &Window,
5607    cx: &App,
5608) -> Option<Div> {
5609    let (leader_id, _follower_state) = follower_states.iter().find_map(|(leader_id, state)| {
5610        if state.pane() == pane {
5611            Some((*leader_id, state))
5612        } else {
5613            None
5614        }
5615    })?;
5616
5617    let mut leader_color = match leader_id {
5618        CollaboratorId::PeerId(leader_peer_id) => {
5619            let room = ActiveCall::try_global(cx)?.read(cx).room()?.read(cx);
5620            let leader = room.remote_participant_for_peer_id(leader_peer_id)?;
5621
5622            cx.theme()
5623                .players()
5624                .color_for_participant(leader.participant_index.0)
5625                .cursor
5626        }
5627        CollaboratorId::Agent => cx.theme().players().agent().cursor,
5628    };
5629    leader_color.fade_out(0.3);
5630    Some(
5631        div()
5632            .absolute()
5633            .size_full()
5634            .left_0()
5635            .top_0()
5636            .border_2()
5637            .border_color(leader_color),
5638    )
5639}
5640
5641fn window_bounds_env_override() -> Option<Bounds<Pixels>> {
5642    ZED_WINDOW_POSITION
5643        .zip(*ZED_WINDOW_SIZE)
5644        .map(|(position, size)| Bounds {
5645            origin: position,
5646            size,
5647        })
5648}
5649
5650fn open_items(
5651    serialized_workspace: Option<SerializedWorkspace>,
5652    mut project_paths_to_open: Vec<(PathBuf, Option<ProjectPath>)>,
5653    window: &mut Window,
5654    cx: &mut Context<Workspace>,
5655) -> impl 'static + Future<Output = Result<Vec<Option<Result<Box<dyn ItemHandle>>>>>> + use<> {
5656    let restored_items = serialized_workspace.map(|serialized_workspace| {
5657        Workspace::load_workspace(
5658            serialized_workspace,
5659            project_paths_to_open
5660                .iter()
5661                .map(|(_, project_path)| project_path)
5662                .cloned()
5663                .collect(),
5664            window,
5665            cx,
5666        )
5667    });
5668
5669    cx.spawn_in(window, async move |workspace, cx| {
5670        let mut opened_items = Vec::with_capacity(project_paths_to_open.len());
5671
5672        if let Some(restored_items) = restored_items {
5673            let restored_items = restored_items.await?;
5674
5675            let restored_project_paths = restored_items
5676                .iter()
5677                .filter_map(|item| {
5678                    cx.update(|_, cx| item.as_ref()?.project_path(cx))
5679                        .ok()
5680                        .flatten()
5681                })
5682                .collect::<HashSet<_>>();
5683
5684            for restored_item in restored_items {
5685                opened_items.push(restored_item.map(Ok));
5686            }
5687
5688            project_paths_to_open
5689                .iter_mut()
5690                .for_each(|(_, project_path)| {
5691                    if let Some(project_path_to_open) = project_path {
5692                        if restored_project_paths.contains(project_path_to_open) {
5693                            *project_path = None;
5694                        }
5695                    }
5696                });
5697        } else {
5698            for _ in 0..project_paths_to_open.len() {
5699                opened_items.push(None);
5700            }
5701        }
5702        assert!(opened_items.len() == project_paths_to_open.len());
5703
5704        let tasks =
5705            project_paths_to_open
5706                .into_iter()
5707                .enumerate()
5708                .map(|(ix, (abs_path, project_path))| {
5709                    let workspace = workspace.clone();
5710                    cx.spawn(async move |cx| {
5711                        let file_project_path = project_path?;
5712                        let abs_path_task = workspace.update(cx, |workspace, cx| {
5713                            workspace.project().update(cx, |project, cx| {
5714                                project.resolve_abs_path(abs_path.to_string_lossy().as_ref(), cx)
5715                            })
5716                        });
5717
5718                        // We only want to open file paths here. If one of the items
5719                        // here is a directory, it was already opened further above
5720                        // with a `find_or_create_worktree`.
5721                        if let Ok(task) = abs_path_task {
5722                            if task.await.map_or(true, |p| p.is_file()) {
5723                                return Some((
5724                                    ix,
5725                                    workspace
5726                                        .update_in(cx, |workspace, window, cx| {
5727                                            workspace.open_path(
5728                                                file_project_path,
5729                                                None,
5730                                                true,
5731                                                window,
5732                                                cx,
5733                                            )
5734                                        })
5735                                        .log_err()?
5736                                        .await,
5737                                ));
5738                            }
5739                        }
5740                        None
5741                    })
5742                });
5743
5744        let tasks = tasks.collect::<Vec<_>>();
5745
5746        let tasks = futures::future::join_all(tasks);
5747        for (ix, path_open_result) in tasks.await.into_iter().flatten() {
5748            opened_items[ix] = Some(path_open_result);
5749        }
5750
5751        Ok(opened_items)
5752    })
5753}
5754
5755enum ActivateInDirectionTarget {
5756    Pane(Entity<Pane>),
5757    Dock(Entity<Dock>),
5758}
5759
5760fn notify_if_database_failed(workspace: WindowHandle<Workspace>, cx: &mut AsyncApp) {
5761    workspace
5762        .update(cx, |workspace, _, cx| {
5763            if (*db::ALL_FILE_DB_FAILED).load(std::sync::atomic::Ordering::Acquire) {
5764                struct DatabaseFailedNotification;
5765
5766                workspace.show_notification(
5767                    NotificationId::unique::<DatabaseFailedNotification>(),
5768                    cx,
5769                    |cx| {
5770                        cx.new(|cx| {
5771                            MessageNotification::new("Failed to load the database file.", cx)
5772                                .primary_message("File an Issue")
5773                                .primary_icon(IconName::Plus)
5774                                .primary_on_click(|window, cx| {
5775                                    window.dispatch_action(Box::new(FileBugReport), cx)
5776                                })
5777                        })
5778                    },
5779                );
5780            }
5781        })
5782        .log_err();
5783}
5784
5785impl Focusable for Workspace {
5786    fn focus_handle(&self, cx: &App) -> FocusHandle {
5787        self.active_pane.focus_handle(cx)
5788    }
5789}
5790
5791#[derive(Clone)]
5792struct DraggedDock(DockPosition);
5793
5794impl Render for DraggedDock {
5795    fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
5796        gpui::Empty
5797    }
5798}
5799
5800impl Render for Workspace {
5801    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
5802        let mut context = KeyContext::new_with_defaults();
5803        context.add("Workspace");
5804        context.set("keyboard_layout", cx.keyboard_layout().name().to_string());
5805        let centered_layout = self.centered_layout
5806            && self.center.panes().len() == 1
5807            && self.active_item(cx).is_some();
5808        let render_padding = |size| {
5809            (size > 0.0).then(|| {
5810                div()
5811                    .h_full()
5812                    .w(relative(size))
5813                    .bg(cx.theme().colors().editor_background)
5814                    .border_color(cx.theme().colors().pane_group_border)
5815            })
5816        };
5817        let paddings = if centered_layout {
5818            let settings = WorkspaceSettings::get_global(cx).centered_layout;
5819            (
5820                render_padding(Self::adjust_padding(settings.left_padding)),
5821                render_padding(Self::adjust_padding(settings.right_padding)),
5822            )
5823        } else {
5824            (None, None)
5825        };
5826        let ui_font = theme::setup_ui_font(window, cx);
5827
5828        let theme = cx.theme().clone();
5829        let colors = theme.colors();
5830        let notification_entities = self
5831            .notifications
5832            .iter()
5833            .map(|(_, notification)| notification.entity_id())
5834            .collect::<Vec<_>>();
5835
5836        client_side_decorations(
5837            self.actions(div(), window, cx)
5838                .key_context(context)
5839                .relative()
5840                .size_full()
5841                .flex()
5842                .flex_col()
5843                .font(ui_font)
5844                .gap_0()
5845                .justify_start()
5846                .items_start()
5847                .text_color(colors.text)
5848                .overflow_hidden()
5849                .children(self.titlebar_item.clone())
5850                .on_modifiers_changed(move |_, _, cx| {
5851                    for &id in &notification_entities {
5852                        cx.notify(id);
5853                    }
5854                })
5855                .child(
5856                    div()
5857                        .size_full()
5858                        .relative()
5859                        .flex_1()
5860                        .flex()
5861                        .flex_col()
5862                        .child(
5863                            div()
5864                                .id("workspace")
5865                                .bg(colors.background)
5866                                .relative()
5867                                .flex_1()
5868                                .w_full()
5869                                .flex()
5870                                .flex_col()
5871                                .overflow_hidden()
5872                                .border_t_1()
5873                                .border_b_1()
5874                                .border_color(colors.border)
5875                                .child({
5876                                    let this = cx.entity().clone();
5877                                    canvas(
5878                                        move |bounds, window, cx| {
5879                                            this.update(cx, |this, cx| {
5880                                                let bounds_changed = this.bounds != bounds;
5881                                                this.bounds = bounds;
5882
5883                                                if bounds_changed {
5884                                                    this.left_dock.update(cx, |dock, cx| {
5885                                                        dock.clamp_panel_size(
5886                                                            bounds.size.width,
5887                                                            window,
5888                                                            cx,
5889                                                        )
5890                                                    });
5891
5892                                                    this.right_dock.update(cx, |dock, cx| {
5893                                                        dock.clamp_panel_size(
5894                                                            bounds.size.width,
5895                                                            window,
5896                                                            cx,
5897                                                        )
5898                                                    });
5899
5900                                                    this.bottom_dock.update(cx, |dock, cx| {
5901                                                        dock.clamp_panel_size(
5902                                                            bounds.size.height,
5903                                                            window,
5904                                                            cx,
5905                                                        )
5906                                                    });
5907                                                }
5908                                            })
5909                                        },
5910                                        |_, _, _, _| {},
5911                                    )
5912                                    .absolute()
5913                                    .size_full()
5914                                })
5915                                .when(self.zoomed.is_none(), |this| {
5916                                    this.on_drag_move(cx.listener(
5917                                        move |workspace,
5918                                              e: &DragMoveEvent<DraggedDock>,
5919                                              window,
5920                                              cx| {
5921                                            if workspace.previous_dock_drag_coordinates
5922                                                != Some(e.event.position)
5923                                            {
5924                                                workspace.previous_dock_drag_coordinates =
5925                                                    Some(e.event.position);
5926                                                match e.drag(cx).0 {
5927                                                    DockPosition::Left => {
5928                                                        resize_left_dock(
5929                                                            e.event.position.x
5930                                                                - workspace.bounds.left(),
5931                                                            workspace,
5932                                                            window,
5933                                                            cx,
5934                                                        );
5935                                                    }
5936                                                    DockPosition::Right => {
5937                                                        resize_right_dock(
5938                                                            workspace.bounds.right()
5939                                                                - e.event.position.x,
5940                                                            workspace,
5941                                                            window,
5942                                                            cx,
5943                                                        );
5944                                                    }
5945                                                    DockPosition::Bottom => {
5946                                                        resize_bottom_dock(
5947                                                            workspace.bounds.bottom()
5948                                                                - e.event.position.y,
5949                                                            workspace,
5950                                                            window,
5951                                                            cx,
5952                                                        );
5953                                                    }
5954                                                };
5955                                                workspace.serialize_workspace(window, cx);
5956                                            }
5957                                        },
5958                                    ))
5959                                })
5960                                .child({
5961                                    match self.bottom_dock_layout {
5962                                        BottomDockLayout::Full => div()
5963                                            .flex()
5964                                            .flex_col()
5965                                            .h_full()
5966                                            .child(
5967                                                div()
5968                                                    .flex()
5969                                                    .flex_row()
5970                                                    .flex_1()
5971                                                    .overflow_hidden()
5972                                                    .children(self.render_dock(
5973                                                        DockPosition::Left,
5974                                                        &self.left_dock,
5975                                                        window,
5976                                                        cx,
5977                                                    ))
5978                                                    .child(
5979                                                        div()
5980                                                            .flex()
5981                                                            .flex_col()
5982                                                            .flex_1()
5983                                                            .overflow_hidden()
5984                                                            .child(
5985                                                                h_flex()
5986                                                                    .flex_1()
5987                                                                    .when_some(
5988                                                                        paddings.0,
5989                                                                        |this, p| {
5990                                                                            this.child(
5991                                                                                p.border_r_1(),
5992                                                                            )
5993                                                                        },
5994                                                                    )
5995                                                                    .child(self.center.render(
5996                                                                        self.zoomed.as_ref(),
5997                                                                        &PaneRenderContext {
5998                                                                            follower_states:
5999                                                                                &self.follower_states,
6000                                                                            active_call: self.active_call(),
6001                                                                            active_pane: &self.active_pane,
6002                                                                            app_state: &self.app_state,
6003                                                                            project: &self.project,
6004                                                                            workspace: &self.weak_self,
6005                                                                        },
6006                                                                        window,
6007                                                                        cx,
6008                                                                    ))
6009                                                                    .when_some(
6010                                                                        paddings.1,
6011                                                                        |this, p| {
6012                                                                            this.child(
6013                                                                                p.border_l_1(),
6014                                                                            )
6015                                                                        },
6016                                                                    ),
6017                                                            ),
6018                                                    )
6019                                                    .children(self.render_dock(
6020                                                        DockPosition::Right,
6021                                                        &self.right_dock,
6022                                                        window,
6023                                                        cx,
6024                                                    )),
6025                                            )
6026                                            .child(div().w_full().children(self.render_dock(
6027                                                DockPosition::Bottom,
6028                                                &self.bottom_dock,
6029                                                window,
6030                                                cx
6031                                            ))),
6032
6033                                        BottomDockLayout::LeftAligned => div()
6034                                            .flex()
6035                                            .flex_row()
6036                                            .h_full()
6037                                            .child(
6038                                                div()
6039                                                    .flex()
6040                                                    .flex_col()
6041                                                    .flex_1()
6042                                                    .h_full()
6043                                                    .child(
6044                                                        div()
6045                                                            .flex()
6046                                                            .flex_row()
6047                                                            .flex_1()
6048                                                            .children(self.render_dock(DockPosition::Left, &self.left_dock, window, cx))
6049                                                            .child(
6050                                                                div()
6051                                                                    .flex()
6052                                                                    .flex_col()
6053                                                                    .flex_1()
6054                                                                    .overflow_hidden()
6055                                                                    .child(
6056                                                                        h_flex()
6057                                                                            .flex_1()
6058                                                                            .when_some(paddings.0, |this, p| this.child(p.border_r_1()))
6059                                                                            .child(self.center.render(
6060                                                                                self.zoomed.as_ref(),
6061                                                                                &PaneRenderContext {
6062                                                                                    follower_states:
6063                                                                                        &self.follower_states,
6064                                                                                    active_call: self.active_call(),
6065                                                                                    active_pane: &self.active_pane,
6066                                                                                    app_state: &self.app_state,
6067                                                                                    project: &self.project,
6068                                                                                    workspace: &self.weak_self,
6069                                                                                },
6070                                                                                window,
6071                                                                                cx,
6072                                                                            ))
6073                                                                            .when_some(paddings.1, |this, p| this.child(p.border_l_1())),
6074                                                                    )
6075                                                            )
6076                                                    )
6077                                                    .child(
6078                                                        div()
6079                                                            .w_full()
6080                                                            .children(self.render_dock(DockPosition::Bottom, &self.bottom_dock, window, cx))
6081                                                    ),
6082                                            )
6083                                            .children(self.render_dock(
6084                                                DockPosition::Right,
6085                                                &self.right_dock,
6086                                                window,
6087                                                cx,
6088                                            )),
6089
6090                                        BottomDockLayout::RightAligned => div()
6091                                            .flex()
6092                                            .flex_row()
6093                                            .h_full()
6094                                            .children(self.render_dock(
6095                                                DockPosition::Left,
6096                                                &self.left_dock,
6097                                                window,
6098                                                cx,
6099                                            ))
6100                                            .child(
6101                                                div()
6102                                                    .flex()
6103                                                    .flex_col()
6104                                                    .flex_1()
6105                                                    .h_full()
6106                                                    .child(
6107                                                        div()
6108                                                            .flex()
6109                                                            .flex_row()
6110                                                            .flex_1()
6111                                                            .child(
6112                                                                div()
6113                                                                    .flex()
6114                                                                    .flex_col()
6115                                                                    .flex_1()
6116                                                                    .overflow_hidden()
6117                                                                    .child(
6118                                                                        h_flex()
6119                                                                            .flex_1()
6120                                                                            .when_some(paddings.0, |this, p| this.child(p.border_r_1()))
6121                                                                            .child(self.center.render(
6122                                                                                self.zoomed.as_ref(),
6123                                                                                &PaneRenderContext {
6124                                                                                    follower_states:
6125                                                                                        &self.follower_states,
6126                                                                                    active_call: self.active_call(),
6127                                                                                    active_pane: &self.active_pane,
6128                                                                                    app_state: &self.app_state,
6129                                                                                    project: &self.project,
6130                                                                                    workspace: &self.weak_self,
6131                                                                                },
6132                                                                                window,
6133                                                                                cx,
6134                                                                            ))
6135                                                                            .when_some(paddings.1, |this, p| this.child(p.border_l_1())),
6136                                                                    )
6137                                                            )
6138                                                            .children(self.render_dock(DockPosition::Right, &self.right_dock, window, cx))
6139                                                    )
6140                                                    .child(
6141                                                        div()
6142                                                            .w_full()
6143                                                            .children(self.render_dock(DockPosition::Bottom, &self.bottom_dock, window, cx))
6144                                                    ),
6145                                            ),
6146
6147                                        BottomDockLayout::Contained => div()
6148                                            .flex()
6149                                            .flex_row()
6150                                            .h_full()
6151                                            .children(self.render_dock(
6152                                                DockPosition::Left,
6153                                                &self.left_dock,
6154                                                window,
6155                                                cx,
6156                                            ))
6157                                            .child(
6158                                                div()
6159                                                    .flex()
6160                                                    .flex_col()
6161                                                    .flex_1()
6162                                                    .overflow_hidden()
6163                                                    .child(
6164                                                        h_flex()
6165                                                            .flex_1()
6166                                                            .when_some(paddings.0, |this, p| {
6167                                                                this.child(p.border_r_1())
6168                                                            })
6169                                                            .child(self.center.render(
6170                                                                self.zoomed.as_ref(),
6171                                                                &PaneRenderContext {
6172                                                                    follower_states:
6173                                                                        &self.follower_states,
6174                                                                    active_call: self.active_call(),
6175                                                                    active_pane: &self.active_pane,
6176                                                                    app_state: &self.app_state,
6177                                                                    project: &self.project,
6178                                                                    workspace: &self.weak_self,
6179                                                                },
6180                                                                window,
6181                                                                cx,
6182                                                            ))
6183                                                            .when_some(paddings.1, |this, p| {
6184                                                                this.child(p.border_l_1())
6185                                                            }),
6186                                                    )
6187                                                    .children(self.render_dock(
6188                                                        DockPosition::Bottom,
6189                                                        &self.bottom_dock,
6190                                                        window,
6191                                                        cx,
6192                                                    )),
6193                                            )
6194                                            .children(self.render_dock(
6195                                                DockPosition::Right,
6196                                                &self.right_dock,
6197                                                window,
6198                                                cx,
6199                                            )),
6200                                    }
6201                                })
6202                                .children(self.zoomed.as_ref().and_then(|view| {
6203                                    let zoomed_view = view.upgrade()?;
6204                                    let div = div()
6205                                        .occlude()
6206                                        .absolute()
6207                                        .overflow_hidden()
6208                                        .border_color(colors.border)
6209                                        .bg(colors.background)
6210                                        .child(zoomed_view)
6211                                        .inset_0()
6212                                        .shadow_lg();
6213
6214                                    Some(match self.zoomed_position {
6215                                        Some(DockPosition::Left) => div.right_2().border_r_1(),
6216                                        Some(DockPosition::Right) => div.left_2().border_l_1(),
6217                                        Some(DockPosition::Bottom) => div.top_2().border_t_1(),
6218                                        None => {
6219                                            div.top_2().bottom_2().left_2().right_2().border_1()
6220                                        }
6221                                    })
6222                                }))
6223                                .children(self.render_notifications(window, cx)),
6224                        )
6225                        .child(self.status_bar.clone())
6226                        .child(self.modal_layer.clone())
6227                        .child(self.toast_layer.clone()),
6228                ),
6229            window,
6230            cx,
6231        )
6232    }
6233}
6234
6235fn resize_bottom_dock(
6236    new_size: Pixels,
6237    workspace: &mut Workspace,
6238    window: &mut Window,
6239    cx: &mut App,
6240) {
6241    let size =
6242        new_size.min(workspace.bounds.bottom() - RESIZE_HANDLE_SIZE - workspace.bounds.top());
6243    workspace.bottom_dock.update(cx, |bottom_dock, cx| {
6244        bottom_dock.resize_active_panel(Some(size), window, cx);
6245    });
6246}
6247
6248fn resize_right_dock(
6249    new_size: Pixels,
6250    workspace: &mut Workspace,
6251    window: &mut Window,
6252    cx: &mut App,
6253) {
6254    let size = new_size.max(workspace.bounds.left() - RESIZE_HANDLE_SIZE);
6255    workspace.right_dock.update(cx, |right_dock, cx| {
6256        right_dock.resize_active_panel(Some(size), window, cx);
6257    });
6258}
6259
6260fn resize_left_dock(
6261    new_size: Pixels,
6262    workspace: &mut Workspace,
6263    window: &mut Window,
6264    cx: &mut App,
6265) {
6266    let size = new_size.min(workspace.bounds.right() - RESIZE_HANDLE_SIZE);
6267
6268    workspace.left_dock.update(cx, |left_dock, cx| {
6269        left_dock.resize_active_panel(Some(size), window, cx);
6270    });
6271}
6272
6273impl WorkspaceStore {
6274    pub fn new(client: Arc<Client>, cx: &mut Context<Self>) -> Self {
6275        Self {
6276            workspaces: Default::default(),
6277            _subscriptions: vec![
6278                client.add_request_handler(cx.weak_entity(), Self::handle_follow),
6279                client.add_message_handler(cx.weak_entity(), Self::handle_update_followers),
6280            ],
6281            client,
6282        }
6283    }
6284
6285    pub fn update_followers(
6286        &self,
6287        project_id: Option<u64>,
6288        update: proto::update_followers::Variant,
6289        cx: &App,
6290    ) -> Option<()> {
6291        let active_call = ActiveCall::try_global(cx)?;
6292        let room_id = active_call.read(cx).room()?.read(cx).id();
6293        self.client
6294            .send(proto::UpdateFollowers {
6295                room_id,
6296                project_id,
6297                variant: Some(update),
6298            })
6299            .log_err()
6300    }
6301
6302    pub async fn handle_follow(
6303        this: Entity<Self>,
6304        envelope: TypedEnvelope<proto::Follow>,
6305        mut cx: AsyncApp,
6306    ) -> Result<proto::FollowResponse> {
6307        this.update(&mut cx, |this, cx| {
6308            let follower = Follower {
6309                project_id: envelope.payload.project_id,
6310                peer_id: envelope.original_sender_id()?,
6311            };
6312
6313            let mut response = proto::FollowResponse::default();
6314            this.workspaces.retain(|workspace| {
6315                workspace
6316                    .update(cx, |workspace, window, cx| {
6317                        let handler_response =
6318                            workspace.handle_follow(follower.project_id, window, cx);
6319                        if let Some(active_view) = handler_response.active_view.clone() {
6320                            if workspace.project.read(cx).remote_id() == follower.project_id {
6321                                response.active_view = Some(active_view)
6322                            }
6323                        }
6324                    })
6325                    .is_ok()
6326            });
6327
6328            Ok(response)
6329        })?
6330    }
6331
6332    async fn handle_update_followers(
6333        this: Entity<Self>,
6334        envelope: TypedEnvelope<proto::UpdateFollowers>,
6335        mut cx: AsyncApp,
6336    ) -> Result<()> {
6337        let leader_id = envelope.original_sender_id()?;
6338        let update = envelope.payload;
6339
6340        this.update(&mut cx, |this, cx| {
6341            this.workspaces.retain(|workspace| {
6342                workspace
6343                    .update(cx, |workspace, window, cx| {
6344                        let project_id = workspace.project.read(cx).remote_id();
6345                        if update.project_id != project_id && update.project_id.is_some() {
6346                            return;
6347                        }
6348                        workspace.handle_update_followers(leader_id, update.clone(), window, cx);
6349                    })
6350                    .is_ok()
6351            });
6352            Ok(())
6353        })?
6354    }
6355}
6356
6357impl ViewId {
6358    pub(crate) fn from_proto(message: proto::ViewId) -> Result<Self> {
6359        Ok(Self {
6360            creator: message
6361                .creator
6362                .map(CollaboratorId::PeerId)
6363                .context("creator is missing")?,
6364            id: message.id,
6365        })
6366    }
6367
6368    pub(crate) fn to_proto(self) -> Option<proto::ViewId> {
6369        if let CollaboratorId::PeerId(peer_id) = self.creator {
6370            Some(proto::ViewId {
6371                creator: Some(peer_id),
6372                id: self.id,
6373            })
6374        } else {
6375            None
6376        }
6377    }
6378}
6379
6380impl FollowerState {
6381    fn pane(&self) -> &Entity<Pane> {
6382        self.dock_pane.as_ref().unwrap_or(&self.center_pane)
6383    }
6384}
6385
6386pub trait WorkspaceHandle {
6387    fn file_project_paths(&self, cx: &App) -> Vec<ProjectPath>;
6388}
6389
6390impl WorkspaceHandle for Entity<Workspace> {
6391    fn file_project_paths(&self, cx: &App) -> Vec<ProjectPath> {
6392        self.read(cx)
6393            .worktrees(cx)
6394            .flat_map(|worktree| {
6395                let worktree_id = worktree.read(cx).id();
6396                worktree.read(cx).files(true, 0).map(move |f| ProjectPath {
6397                    worktree_id,
6398                    path: f.path.clone(),
6399                })
6400            })
6401            .collect::<Vec<_>>()
6402    }
6403}
6404
6405impl std::fmt::Debug for OpenPaths {
6406    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
6407        f.debug_struct("OpenPaths")
6408            .field("paths", &self.paths)
6409            .finish()
6410    }
6411}
6412
6413pub async fn last_opened_workspace_location() -> Option<SerializedWorkspaceLocation> {
6414    DB.last_workspace().await.log_err().flatten()
6415}
6416
6417pub fn last_session_workspace_locations(
6418    last_session_id: &str,
6419    last_session_window_stack: Option<Vec<WindowId>>,
6420) -> Option<Vec<SerializedWorkspaceLocation>> {
6421    DB.last_session_workspace_locations(last_session_id, last_session_window_stack)
6422        .log_err()
6423}
6424
6425actions!(
6426    collab,
6427    [
6428        OpenChannelNotes,
6429        Mute,
6430        Deafen,
6431        LeaveCall,
6432        ShareProject,
6433        ScreenShare
6434    ]
6435);
6436actions!(zed, [OpenLog]);
6437
6438async fn join_channel_internal(
6439    channel_id: ChannelId,
6440    app_state: &Arc<AppState>,
6441    requesting_window: Option<WindowHandle<Workspace>>,
6442    active_call: &Entity<ActiveCall>,
6443    cx: &mut AsyncApp,
6444) -> Result<bool> {
6445    let (should_prompt, open_room) = active_call.update(cx, |active_call, cx| {
6446        let Some(room) = active_call.room().map(|room| room.read(cx)) else {
6447            return (false, None);
6448        };
6449
6450        let already_in_channel = room.channel_id() == Some(channel_id);
6451        let should_prompt = room.is_sharing_project()
6452            && !room.remote_participants().is_empty()
6453            && !already_in_channel;
6454        let open_room = if already_in_channel {
6455            active_call.room().cloned()
6456        } else {
6457            None
6458        };
6459        (should_prompt, open_room)
6460    })?;
6461
6462    if let Some(room) = open_room {
6463        let task = room.update(cx, |room, cx| {
6464            if let Some((project, host)) = room.most_active_project(cx) {
6465                return Some(join_in_room_project(project, host, app_state.clone(), cx));
6466            }
6467
6468            None
6469        })?;
6470        if let Some(task) = task {
6471            task.await?;
6472        }
6473        return anyhow::Ok(true);
6474    }
6475
6476    if should_prompt {
6477        if let Some(workspace) = requesting_window {
6478            let answer = workspace
6479                .update(cx, |_, window, cx| {
6480                    window.prompt(
6481                        PromptLevel::Warning,
6482                        "Do you want to switch channels?",
6483                        Some("Leaving this call will unshare your current project."),
6484                        &["Yes, Join Channel", "Cancel"],
6485                        cx,
6486                    )
6487                })?
6488                .await;
6489
6490            if answer == Ok(1) {
6491                return Ok(false);
6492            }
6493        } else {
6494            return Ok(false); // unreachable!() hopefully
6495        }
6496    }
6497
6498    let client = cx.update(|cx| active_call.read(cx).client())?;
6499
6500    let mut client_status = client.status();
6501
6502    // this loop will terminate within client::CONNECTION_TIMEOUT seconds.
6503    'outer: loop {
6504        let Some(status) = client_status.recv().await else {
6505            anyhow::bail!("error connecting");
6506        };
6507
6508        match status {
6509            Status::Connecting
6510            | Status::Authenticating
6511            | Status::Reconnecting
6512            | Status::Reauthenticating => continue,
6513            Status::Connected { .. } => break 'outer,
6514            Status::SignedOut => return Err(ErrorCode::SignedOut.into()),
6515            Status::UpgradeRequired => return Err(ErrorCode::UpgradeRequired.into()),
6516            Status::ConnectionError | Status::ConnectionLost | Status::ReconnectionError { .. } => {
6517                return Err(ErrorCode::Disconnected.into());
6518            }
6519        }
6520    }
6521
6522    let room = active_call
6523        .update(cx, |active_call, cx| {
6524            active_call.join_channel(channel_id, cx)
6525        })?
6526        .await?;
6527
6528    let Some(room) = room else {
6529        return anyhow::Ok(true);
6530    };
6531
6532    room.update(cx, |room, _| room.room_update_completed())?
6533        .await;
6534
6535    let task = room.update(cx, |room, cx| {
6536        if let Some((project, host)) = room.most_active_project(cx) {
6537            return Some(join_in_room_project(project, host, app_state.clone(), cx));
6538        }
6539
6540        // If you are the first to join a channel, see if you should share your project.
6541        if room.remote_participants().is_empty() && !room.local_participant_is_guest() {
6542            if let Some(workspace) = requesting_window {
6543                let project = workspace.update(cx, |workspace, _, cx| {
6544                    let project = workspace.project.read(cx);
6545
6546                    if !CallSettings::get_global(cx).share_on_join {
6547                        return None;
6548                    }
6549
6550                    if (project.is_local() || project.is_via_ssh())
6551                        && project.visible_worktrees(cx).any(|tree| {
6552                            tree.read(cx)
6553                                .root_entry()
6554                                .map_or(false, |entry| entry.is_dir())
6555                        })
6556                    {
6557                        Some(workspace.project.clone())
6558                    } else {
6559                        None
6560                    }
6561                });
6562                if let Ok(Some(project)) = project {
6563                    return Some(cx.spawn(async move |room, cx| {
6564                        room.update(cx, |room, cx| room.share_project(project, cx))?
6565                            .await?;
6566                        Ok(())
6567                    }));
6568                }
6569            }
6570        }
6571
6572        None
6573    })?;
6574    if let Some(task) = task {
6575        task.await?;
6576        return anyhow::Ok(true);
6577    }
6578    anyhow::Ok(false)
6579}
6580
6581pub fn join_channel(
6582    channel_id: ChannelId,
6583    app_state: Arc<AppState>,
6584    requesting_window: Option<WindowHandle<Workspace>>,
6585    cx: &mut App,
6586) -> Task<Result<()>> {
6587    let active_call = ActiveCall::global(cx);
6588    cx.spawn(async move |cx| {
6589        let result = join_channel_internal(
6590            channel_id,
6591            &app_state,
6592            requesting_window,
6593            &active_call,
6594             cx,
6595        )
6596            .await;
6597
6598        // join channel succeeded, and opened a window
6599        if matches!(result, Ok(true)) {
6600            return anyhow::Ok(());
6601        }
6602
6603        // find an existing workspace to focus and show call controls
6604        let mut active_window =
6605            requesting_window.or_else(|| activate_any_workspace_window( cx));
6606        if active_window.is_none() {
6607            // no open workspaces, make one to show the error in (blergh)
6608            let (window_handle, _) = cx
6609                .update(|cx| {
6610                    Workspace::new_local(vec![], app_state.clone(), requesting_window, None, cx)
6611                })?
6612                .await?;
6613
6614            if result.is_ok() {
6615                cx.update(|cx| {
6616                    cx.dispatch_action(&OpenChannelNotes);
6617                }).log_err();
6618            }
6619
6620            active_window = Some(window_handle);
6621        }
6622
6623        if let Err(err) = result {
6624            log::error!("failed to join channel: {}", err);
6625            if let Some(active_window) = active_window {
6626                active_window
6627                    .update(cx, |_, window, cx| {
6628                        let detail: SharedString = match err.error_code() {
6629                            ErrorCode::SignedOut => {
6630                                "Please sign in to continue.".into()
6631                            }
6632                            ErrorCode::UpgradeRequired => {
6633                                "Your are running an unsupported version of Zed. Please update to continue.".into()
6634                            }
6635                            ErrorCode::NoSuchChannel => {
6636                                "No matching channel was found. Please check the link and try again.".into()
6637                            }
6638                            ErrorCode::Forbidden => {
6639                                "This channel is private, and you do not have access. Please ask someone to add you and try again.".into()
6640                            }
6641                            ErrorCode::Disconnected => "Please check your internet connection and try again.".into(),
6642                            _ => format!("{}\n\nPlease try again.", err).into(),
6643                        };
6644                        window.prompt(
6645                            PromptLevel::Critical,
6646                            "Failed to join channel",
6647                            Some(&detail),
6648                            &["Ok"],
6649                        cx)
6650                    })?
6651                    .await
6652                    .ok();
6653            }
6654        }
6655
6656        // return ok, we showed the error to the user.
6657        anyhow::Ok(())
6658    })
6659}
6660
6661pub async fn get_any_active_workspace(
6662    app_state: Arc<AppState>,
6663    mut cx: AsyncApp,
6664) -> anyhow::Result<WindowHandle<Workspace>> {
6665    // find an existing workspace to focus and show call controls
6666    let active_window = activate_any_workspace_window(&mut cx);
6667    if active_window.is_none() {
6668        cx.update(|cx| Workspace::new_local(vec![], app_state.clone(), None, None, cx))?
6669            .await?;
6670    }
6671    activate_any_workspace_window(&mut cx).context("could not open zed")
6672}
6673
6674fn activate_any_workspace_window(cx: &mut AsyncApp) -> Option<WindowHandle<Workspace>> {
6675    cx.update(|cx| {
6676        if let Some(workspace_window) = cx
6677            .active_window()
6678            .and_then(|window| window.downcast::<Workspace>())
6679        {
6680            return Some(workspace_window);
6681        }
6682
6683        for window in cx.windows() {
6684            if let Some(workspace_window) = window.downcast::<Workspace>() {
6685                workspace_window
6686                    .update(cx, |_, window, _| window.activate_window())
6687                    .ok();
6688                return Some(workspace_window);
6689            }
6690        }
6691        None
6692    })
6693    .ok()
6694    .flatten()
6695}
6696
6697pub fn local_workspace_windows(cx: &App) -> Vec<WindowHandle<Workspace>> {
6698    cx.windows()
6699        .into_iter()
6700        .filter_map(|window| window.downcast::<Workspace>())
6701        .filter(|workspace| {
6702            workspace
6703                .read(cx)
6704                .is_ok_and(|workspace| workspace.project.read(cx).is_local())
6705        })
6706        .collect()
6707}
6708
6709#[derive(Default)]
6710pub struct OpenOptions {
6711    pub visible: Option<OpenVisible>,
6712    pub focus: Option<bool>,
6713    pub open_new_workspace: Option<bool>,
6714    pub replace_window: Option<WindowHandle<Workspace>>,
6715    pub env: Option<HashMap<String, String>>,
6716}
6717
6718#[allow(clippy::type_complexity)]
6719pub fn open_paths(
6720    abs_paths: &[PathBuf],
6721    app_state: Arc<AppState>,
6722    open_options: OpenOptions,
6723    cx: &mut App,
6724) -> Task<
6725    anyhow::Result<(
6726        WindowHandle<Workspace>,
6727        Vec<Option<anyhow::Result<Box<dyn ItemHandle>>>>,
6728    )>,
6729> {
6730    let abs_paths = abs_paths.to_vec();
6731    let mut existing = None;
6732    let mut best_match = None;
6733    let mut open_visible = OpenVisible::All;
6734
6735    cx.spawn(async move |cx| {
6736        if open_options.open_new_workspace != Some(true) {
6737            let all_paths = abs_paths.iter().map(|path| app_state.fs.metadata(path));
6738            let all_metadatas = futures::future::join_all(all_paths)
6739                .await
6740                .into_iter()
6741                .filter_map(|result| result.ok().flatten())
6742                .collect::<Vec<_>>();
6743
6744            cx.update(|cx| {
6745                for window in local_workspace_windows(&cx) {
6746                    if let Ok(workspace) = window.read(&cx) {
6747                        let m = workspace.project.read(&cx).visibility_for_paths(
6748                            &abs_paths,
6749                            &all_metadatas,
6750                            open_options.open_new_workspace == None,
6751                            cx,
6752                        );
6753                        if m > best_match {
6754                            existing = Some(window);
6755                            best_match = m;
6756                        } else if best_match.is_none()
6757                            && open_options.open_new_workspace == Some(false)
6758                        {
6759                            existing = Some(window)
6760                        }
6761                    }
6762                }
6763            })?;
6764
6765            if open_options.open_new_workspace.is_none() && existing.is_none() {
6766                if all_metadatas.iter().all(|file| !file.is_dir) {
6767                    cx.update(|cx| {
6768                        if let Some(window) = cx
6769                            .active_window()
6770                            .and_then(|window| window.downcast::<Workspace>())
6771                        {
6772                            if let Ok(workspace) = window.read(cx) {
6773                                let project = workspace.project().read(cx);
6774                                if project.is_local() && !project.is_via_collab() {
6775                                    existing = Some(window);
6776                                    open_visible = OpenVisible::None;
6777                                    return;
6778                                }
6779                            }
6780                        }
6781                        for window in local_workspace_windows(cx) {
6782                            if let Ok(workspace) = window.read(cx) {
6783                                let project = workspace.project().read(cx);
6784                                if project.is_via_collab() {
6785                                    continue;
6786                                }
6787                                existing = Some(window);
6788                                open_visible = OpenVisible::None;
6789                                break;
6790                            }
6791                        }
6792                    })?;
6793                }
6794            }
6795        }
6796
6797        if let Some(existing) = existing {
6798            let open_task = existing
6799                .update(cx, |workspace, window, cx| {
6800                    window.activate_window();
6801                    workspace.open_paths(
6802                        abs_paths,
6803                        OpenOptions {
6804                            visible: Some(open_visible),
6805                            ..Default::default()
6806                        },
6807                        None,
6808                        window,
6809                        cx,
6810                    )
6811                })?
6812                .await;
6813
6814            _ = existing.update(cx, |workspace, _, cx| {
6815                for item in open_task.iter().flatten() {
6816                    if let Err(e) = item {
6817                        workspace.show_error(&e, cx);
6818                    }
6819                }
6820            });
6821
6822            Ok((existing, open_task))
6823        } else {
6824            cx.update(move |cx| {
6825                Workspace::new_local(
6826                    abs_paths,
6827                    app_state.clone(),
6828                    open_options.replace_window,
6829                    open_options.env,
6830                    cx,
6831                )
6832            })?
6833            .await
6834        }
6835    })
6836}
6837
6838pub fn open_new(
6839    open_options: OpenOptions,
6840    app_state: Arc<AppState>,
6841    cx: &mut App,
6842    init: impl FnOnce(&mut Workspace, &mut Window, &mut Context<Workspace>) + 'static + Send,
6843) -> Task<anyhow::Result<()>> {
6844    let task = Workspace::new_local(Vec::new(), app_state, None, open_options.env, cx);
6845    cx.spawn(async move |cx| {
6846        let (workspace, opened_paths) = task.await?;
6847        workspace.update(cx, |workspace, window, cx| {
6848            if opened_paths.is_empty() {
6849                init(workspace, window, cx)
6850            }
6851        })?;
6852        Ok(())
6853    })
6854}
6855
6856pub fn create_and_open_local_file(
6857    path: &'static Path,
6858    window: &mut Window,
6859    cx: &mut Context<Workspace>,
6860    default_content: impl 'static + Send + FnOnce() -> Rope,
6861) -> Task<Result<Box<dyn ItemHandle>>> {
6862    cx.spawn_in(window, async move |workspace, cx| {
6863        let fs = workspace.read_with(cx, |workspace, _| workspace.app_state().fs.clone())?;
6864        if !fs.is_file(path).await {
6865            fs.create_file(path, Default::default()).await?;
6866            fs.save(path, &default_content(), Default::default())
6867                .await?;
6868        }
6869
6870        let mut items = workspace
6871            .update_in(cx, |workspace, window, cx| {
6872                workspace.with_local_workspace(window, cx, |workspace, window, cx| {
6873                    workspace.open_paths(
6874                        vec![path.to_path_buf()],
6875                        OpenOptions {
6876                            visible: Some(OpenVisible::None),
6877                            ..Default::default()
6878                        },
6879                        None,
6880                        window,
6881                        cx,
6882                    )
6883                })
6884            })?
6885            .await?
6886            .await;
6887
6888        let item = items.pop().flatten();
6889        item.with_context(|| format!("path {path:?} is not a file"))?
6890    })
6891}
6892
6893pub fn open_ssh_project_with_new_connection(
6894    window: WindowHandle<Workspace>,
6895    connection_options: SshConnectionOptions,
6896    cancel_rx: oneshot::Receiver<()>,
6897    delegate: Arc<dyn SshClientDelegate>,
6898    app_state: Arc<AppState>,
6899    paths: Vec<PathBuf>,
6900    cx: &mut App,
6901) -> Task<Result<()>> {
6902    cx.spawn(async move |cx| {
6903        let (serialized_ssh_project, workspace_id, serialized_workspace) =
6904            serialize_ssh_project(connection_options.clone(), paths.clone(), &cx).await?;
6905
6906        let session = match cx
6907            .update(|cx| {
6908                remote::SshRemoteClient::new(
6909                    ConnectionIdentifier::Workspace(workspace_id.0),
6910                    connection_options,
6911                    cancel_rx,
6912                    delegate,
6913                    cx,
6914                )
6915            })?
6916            .await?
6917        {
6918            Some(result) => result,
6919            None => return Ok(()),
6920        };
6921
6922        let project = cx.update(|cx| {
6923            project::Project::ssh(
6924                session,
6925                app_state.client.clone(),
6926                app_state.node_runtime.clone(),
6927                app_state.user_store.clone(),
6928                app_state.languages.clone(),
6929                app_state.fs.clone(),
6930                cx,
6931            )
6932        })?;
6933
6934        open_ssh_project_inner(
6935            project,
6936            paths,
6937            serialized_ssh_project,
6938            workspace_id,
6939            serialized_workspace,
6940            app_state,
6941            window,
6942            cx,
6943        )
6944        .await
6945    })
6946}
6947
6948pub fn open_ssh_project_with_existing_connection(
6949    connection_options: SshConnectionOptions,
6950    project: Entity<Project>,
6951    paths: Vec<PathBuf>,
6952    app_state: Arc<AppState>,
6953    window: WindowHandle<Workspace>,
6954    cx: &mut AsyncApp,
6955) -> Task<Result<()>> {
6956    cx.spawn(async move |cx| {
6957        let (serialized_ssh_project, workspace_id, serialized_workspace) =
6958            serialize_ssh_project(connection_options.clone(), paths.clone(), &cx).await?;
6959
6960        open_ssh_project_inner(
6961            project,
6962            paths,
6963            serialized_ssh_project,
6964            workspace_id,
6965            serialized_workspace,
6966            app_state,
6967            window,
6968            cx,
6969        )
6970        .await
6971    })
6972}
6973
6974async fn open_ssh_project_inner(
6975    project: Entity<Project>,
6976    paths: Vec<PathBuf>,
6977    serialized_ssh_project: SerializedSshProject,
6978    workspace_id: WorkspaceId,
6979    serialized_workspace: Option<SerializedWorkspace>,
6980    app_state: Arc<AppState>,
6981    window: WindowHandle<Workspace>,
6982    cx: &mut AsyncApp,
6983) -> Result<()> {
6984    let toolchains = DB.toolchains(workspace_id).await?;
6985    for (toolchain, worktree_id, path) in toolchains {
6986        project
6987            .update(cx, |this, cx| {
6988                this.activate_toolchain(ProjectPath { worktree_id, path }, toolchain, cx)
6989            })?
6990            .await;
6991    }
6992    let mut project_paths_to_open = vec![];
6993    let mut project_path_errors = vec![];
6994
6995    for path in paths {
6996        let result = cx
6997            .update(|cx| Workspace::project_path_for_path(project.clone(), &path, true, cx))?
6998            .await;
6999        match result {
7000            Ok((_, project_path)) => {
7001                project_paths_to_open.push((path.clone(), Some(project_path)));
7002            }
7003            Err(error) => {
7004                project_path_errors.push(error);
7005            }
7006        };
7007    }
7008
7009    if project_paths_to_open.is_empty() {
7010        return Err(project_path_errors.pop().context("no paths given")?);
7011    }
7012
7013    cx.update_window(window.into(), |_, window, cx| {
7014        window.replace_root(cx, |window, cx| {
7015            telemetry::event!("SSH Project Opened");
7016
7017            let mut workspace =
7018                Workspace::new(Some(workspace_id), project, app_state.clone(), window, cx);
7019            workspace.set_serialized_ssh_project(serialized_ssh_project);
7020            workspace.update_history(cx);
7021            workspace
7022        });
7023    })?;
7024
7025    window
7026        .update(cx, |_, window, cx| {
7027            window.activate_window();
7028            open_items(serialized_workspace, project_paths_to_open, window, cx)
7029        })?
7030        .await?;
7031
7032    window.update(cx, |workspace, _, cx| {
7033        for error in project_path_errors {
7034            if error.error_code() == proto::ErrorCode::DevServerProjectPathDoesNotExist {
7035                if let Some(path) = error.error_tag("path") {
7036                    workspace.show_error(&anyhow!("'{path}' does not exist"), cx)
7037                }
7038            } else {
7039                workspace.show_error(&error, cx)
7040            }
7041        }
7042    })?;
7043
7044    Ok(())
7045}
7046
7047fn serialize_ssh_project(
7048    connection_options: SshConnectionOptions,
7049    paths: Vec<PathBuf>,
7050    cx: &AsyncApp,
7051) -> Task<
7052    Result<(
7053        SerializedSshProject,
7054        WorkspaceId,
7055        Option<SerializedWorkspace>,
7056    )>,
7057> {
7058    cx.background_spawn(async move {
7059        let serialized_ssh_project = persistence::DB
7060            .get_or_create_ssh_project(
7061                connection_options.host.clone(),
7062                connection_options.port,
7063                paths
7064                    .iter()
7065                    .map(|path| path.to_string_lossy().to_string())
7066                    .collect::<Vec<_>>(),
7067                connection_options.username.clone(),
7068            )
7069            .await?;
7070
7071        let serialized_workspace =
7072            persistence::DB.workspace_for_ssh_project(&serialized_ssh_project);
7073
7074        let workspace_id = if let Some(workspace_id) =
7075            serialized_workspace.as_ref().map(|workspace| workspace.id)
7076        {
7077            workspace_id
7078        } else {
7079            persistence::DB.next_id().await?
7080        };
7081
7082        Ok((serialized_ssh_project, workspace_id, serialized_workspace))
7083    })
7084}
7085
7086pub fn join_in_room_project(
7087    project_id: u64,
7088    follow_user_id: u64,
7089    app_state: Arc<AppState>,
7090    cx: &mut App,
7091) -> Task<Result<()>> {
7092    let windows = cx.windows();
7093    cx.spawn(async move |cx| {
7094        let existing_workspace = windows.into_iter().find_map(|window_handle| {
7095            window_handle
7096                .downcast::<Workspace>()
7097                .and_then(|window_handle| {
7098                    window_handle
7099                        .update(cx, |workspace, _window, cx| {
7100                            if workspace.project().read(cx).remote_id() == Some(project_id) {
7101                                Some(window_handle)
7102                            } else {
7103                                None
7104                            }
7105                        })
7106                        .unwrap_or(None)
7107                })
7108        });
7109
7110        let workspace = if let Some(existing_workspace) = existing_workspace {
7111            existing_workspace
7112        } else {
7113            let active_call = cx.update(|cx| ActiveCall::global(cx))?;
7114            let room = active_call
7115                .read_with(cx, |call, _| call.room().cloned())?
7116                .context("not in a call")?;
7117            let project = room
7118                .update(cx, |room, cx| {
7119                    room.join_project(
7120                        project_id,
7121                        app_state.languages.clone(),
7122                        app_state.fs.clone(),
7123                        cx,
7124                    )
7125                })?
7126                .await?;
7127
7128            let window_bounds_override = window_bounds_env_override();
7129            cx.update(|cx| {
7130                let mut options = (app_state.build_window_options)(None, cx);
7131                options.window_bounds = window_bounds_override.map(WindowBounds::Windowed);
7132                cx.open_window(options, |window, cx| {
7133                    cx.new(|cx| {
7134                        Workspace::new(Default::default(), project, app_state.clone(), window, cx)
7135                    })
7136                })
7137            })??
7138        };
7139
7140        workspace.update(cx, |workspace, window, cx| {
7141            cx.activate(true);
7142            window.activate_window();
7143
7144            if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
7145                let follow_peer_id = room
7146                    .read(cx)
7147                    .remote_participants()
7148                    .iter()
7149                    .find(|(_, participant)| participant.user.id == follow_user_id)
7150                    .map(|(_, p)| p.peer_id)
7151                    .or_else(|| {
7152                        // If we couldn't follow the given user, follow the host instead.
7153                        let collaborator = workspace
7154                            .project()
7155                            .read(cx)
7156                            .collaborators()
7157                            .values()
7158                            .find(|collaborator| collaborator.is_host)?;
7159                        Some(collaborator.peer_id)
7160                    });
7161
7162                if let Some(follow_peer_id) = follow_peer_id {
7163                    workspace.follow(follow_peer_id, window, cx);
7164                }
7165            }
7166        })?;
7167
7168        anyhow::Ok(())
7169    })
7170}
7171
7172pub fn reload(reload: &Reload, cx: &mut App) {
7173    let should_confirm = WorkspaceSettings::get_global(cx).confirm_quit;
7174    let mut workspace_windows = cx
7175        .windows()
7176        .into_iter()
7177        .filter_map(|window| window.downcast::<Workspace>())
7178        .collect::<Vec<_>>();
7179
7180    // If multiple windows have unsaved changes, and need a save prompt,
7181    // prompt in the active window before switching to a different window.
7182    workspace_windows.sort_by_key(|window| window.is_active(cx) == Some(false));
7183
7184    let mut prompt = None;
7185    if let (true, Some(window)) = (should_confirm, workspace_windows.first()) {
7186        prompt = window
7187            .update(cx, |_, window, cx| {
7188                window.prompt(
7189                    PromptLevel::Info,
7190                    "Are you sure you want to restart?",
7191                    None,
7192                    &["Restart", "Cancel"],
7193                    cx,
7194                )
7195            })
7196            .ok();
7197    }
7198
7199    let binary_path = reload.binary_path.clone();
7200    cx.spawn(async move |cx| {
7201        if let Some(prompt) = prompt {
7202            let answer = prompt.await?;
7203            if answer != 0 {
7204                return Ok(());
7205            }
7206        }
7207
7208        // If the user cancels any save prompt, then keep the app open.
7209        for window in workspace_windows {
7210            if let Ok(should_close) = window.update(cx, |workspace, window, cx| {
7211                workspace.prepare_to_close(CloseIntent::Quit, window, cx)
7212            }) {
7213                if !should_close.await? {
7214                    return Ok(());
7215                }
7216            }
7217        }
7218
7219        cx.update(|cx| cx.restart(binary_path))
7220    })
7221    .detach_and_log_err(cx);
7222}
7223
7224fn parse_pixel_position_env_var(value: &str) -> Option<Point<Pixels>> {
7225    let mut parts = value.split(',');
7226    let x: usize = parts.next()?.parse().ok()?;
7227    let y: usize = parts.next()?.parse().ok()?;
7228    Some(point(px(x as f32), px(y as f32)))
7229}
7230
7231fn parse_pixel_size_env_var(value: &str) -> Option<Size<Pixels>> {
7232    let mut parts = value.split(',');
7233    let width: usize = parts.next()?.parse().ok()?;
7234    let height: usize = parts.next()?.parse().ok()?;
7235    Some(size(px(width as f32), px(height as f32)))
7236}
7237
7238pub fn client_side_decorations(
7239    element: impl IntoElement,
7240    window: &mut Window,
7241    cx: &mut App,
7242) -> Stateful<Div> {
7243    const BORDER_SIZE: Pixels = px(1.0);
7244    let decorations = window.window_decorations();
7245
7246    if matches!(decorations, Decorations::Client { .. }) {
7247        window.set_client_inset(theme::CLIENT_SIDE_DECORATION_SHADOW);
7248    }
7249
7250    struct GlobalResizeEdge(ResizeEdge);
7251    impl Global for GlobalResizeEdge {}
7252
7253    div()
7254        .id("window-backdrop")
7255        .bg(transparent_black())
7256        .map(|div| match decorations {
7257            Decorations::Server => div,
7258            Decorations::Client { tiling, .. } => div
7259                .when(!(tiling.top || tiling.right), |div| {
7260                    div.rounded_tr(theme::CLIENT_SIDE_DECORATION_ROUNDING)
7261                })
7262                .when(!(tiling.top || tiling.left), |div| {
7263                    div.rounded_tl(theme::CLIENT_SIDE_DECORATION_ROUNDING)
7264                })
7265                .when(!(tiling.bottom || tiling.right), |div| {
7266                    div.rounded_br(theme::CLIENT_SIDE_DECORATION_ROUNDING)
7267                })
7268                .when(!(tiling.bottom || tiling.left), |div| {
7269                    div.rounded_bl(theme::CLIENT_SIDE_DECORATION_ROUNDING)
7270                })
7271                .when(!tiling.top, |div| {
7272                    div.pt(theme::CLIENT_SIDE_DECORATION_SHADOW)
7273                })
7274                .when(!tiling.bottom, |div| {
7275                    div.pb(theme::CLIENT_SIDE_DECORATION_SHADOW)
7276                })
7277                .when(!tiling.left, |div| {
7278                    div.pl(theme::CLIENT_SIDE_DECORATION_SHADOW)
7279                })
7280                .when(!tiling.right, |div| {
7281                    div.pr(theme::CLIENT_SIDE_DECORATION_SHADOW)
7282                })
7283                .on_mouse_move(move |e, window, cx| {
7284                    let size = window.window_bounds().get_bounds().size;
7285                    let pos = e.position;
7286
7287                    let new_edge =
7288                        resize_edge(pos, theme::CLIENT_SIDE_DECORATION_SHADOW, size, tiling);
7289
7290                    let edge = cx.try_global::<GlobalResizeEdge>();
7291                    if new_edge != edge.map(|edge| edge.0) {
7292                        window
7293                            .window_handle()
7294                            .update(cx, |workspace, _, cx| {
7295                                cx.notify(workspace.entity_id());
7296                            })
7297                            .ok();
7298                    }
7299                })
7300                .on_mouse_down(MouseButton::Left, move |e, window, _| {
7301                    let size = window.window_bounds().get_bounds().size;
7302                    let pos = e.position;
7303
7304                    let edge = match resize_edge(
7305                        pos,
7306                        theme::CLIENT_SIDE_DECORATION_SHADOW,
7307                        size,
7308                        tiling,
7309                    ) {
7310                        Some(value) => value,
7311                        None => return,
7312                    };
7313
7314                    window.start_window_resize(edge);
7315                }),
7316        })
7317        .size_full()
7318        .child(
7319            div()
7320                .cursor(CursorStyle::Arrow)
7321                .map(|div| match decorations {
7322                    Decorations::Server => div,
7323                    Decorations::Client { tiling } => div
7324                        .border_color(cx.theme().colors().border)
7325                        .when(!(tiling.top || tiling.right), |div| {
7326                            div.rounded_tr(theme::CLIENT_SIDE_DECORATION_ROUNDING)
7327                        })
7328                        .when(!(tiling.top || tiling.left), |div| {
7329                            div.rounded_tl(theme::CLIENT_SIDE_DECORATION_ROUNDING)
7330                        })
7331                        .when(!(tiling.bottom || tiling.right), |div| {
7332                            div.rounded_br(theme::CLIENT_SIDE_DECORATION_ROUNDING)
7333                        })
7334                        .when(!(tiling.bottom || tiling.left), |div| {
7335                            div.rounded_bl(theme::CLIENT_SIDE_DECORATION_ROUNDING)
7336                        })
7337                        .when(!tiling.top, |div| div.border_t(BORDER_SIZE))
7338                        .when(!tiling.bottom, |div| div.border_b(BORDER_SIZE))
7339                        .when(!tiling.left, |div| div.border_l(BORDER_SIZE))
7340                        .when(!tiling.right, |div| div.border_r(BORDER_SIZE))
7341                        .when(!tiling.is_tiled(), |div| {
7342                            div.shadow(vec![gpui::BoxShadow {
7343                                color: Hsla {
7344                                    h: 0.,
7345                                    s: 0.,
7346                                    l: 0.,
7347                                    a: 0.4,
7348                                },
7349                                blur_radius: theme::CLIENT_SIDE_DECORATION_SHADOW / 2.,
7350                                spread_radius: px(0.),
7351                                offset: point(px(0.0), px(0.0)),
7352                            }])
7353                        }),
7354                })
7355                .on_mouse_move(|_e, _, cx| {
7356                    cx.stop_propagation();
7357                })
7358                .size_full()
7359                .child(element),
7360        )
7361        .map(|div| match decorations {
7362            Decorations::Server => div,
7363            Decorations::Client { tiling, .. } => div.child(
7364                canvas(
7365                    |_bounds, window, _| {
7366                        window.insert_hitbox(
7367                            Bounds::new(
7368                                point(px(0.0), px(0.0)),
7369                                window.window_bounds().get_bounds().size,
7370                            ),
7371                            HitboxBehavior::Normal,
7372                        )
7373                    },
7374                    move |_bounds, hitbox, window, cx| {
7375                        let mouse = window.mouse_position();
7376                        let size = window.window_bounds().get_bounds().size;
7377                        let Some(edge) =
7378                            resize_edge(mouse, theme::CLIENT_SIDE_DECORATION_SHADOW, size, tiling)
7379                        else {
7380                            return;
7381                        };
7382                        cx.set_global(GlobalResizeEdge(edge));
7383                        window.set_cursor_style(
7384                            match edge {
7385                                ResizeEdge::Top | ResizeEdge::Bottom => CursorStyle::ResizeUpDown,
7386                                ResizeEdge::Left | ResizeEdge::Right => {
7387                                    CursorStyle::ResizeLeftRight
7388                                }
7389                                ResizeEdge::TopLeft | ResizeEdge::BottomRight => {
7390                                    CursorStyle::ResizeUpLeftDownRight
7391                                }
7392                                ResizeEdge::TopRight | ResizeEdge::BottomLeft => {
7393                                    CursorStyle::ResizeUpRightDownLeft
7394                                }
7395                            },
7396                            Some(&hitbox),
7397                        );
7398                    },
7399                )
7400                .size_full()
7401                .absolute(),
7402            ),
7403        })
7404}
7405
7406fn resize_edge(
7407    pos: Point<Pixels>,
7408    shadow_size: Pixels,
7409    window_size: Size<Pixels>,
7410    tiling: Tiling,
7411) -> Option<ResizeEdge> {
7412    let bounds = Bounds::new(Point::default(), window_size).inset(shadow_size * 1.5);
7413    if bounds.contains(&pos) {
7414        return None;
7415    }
7416
7417    let corner_size = size(shadow_size * 1.5, shadow_size * 1.5);
7418    let top_left_bounds = Bounds::new(Point::new(px(0.), px(0.)), corner_size);
7419    if !tiling.top && top_left_bounds.contains(&pos) {
7420        return Some(ResizeEdge::TopLeft);
7421    }
7422
7423    let top_right_bounds = Bounds::new(
7424        Point::new(window_size.width - corner_size.width, px(0.)),
7425        corner_size,
7426    );
7427    if !tiling.top && top_right_bounds.contains(&pos) {
7428        return Some(ResizeEdge::TopRight);
7429    }
7430
7431    let bottom_left_bounds = Bounds::new(
7432        Point::new(px(0.), window_size.height - corner_size.height),
7433        corner_size,
7434    );
7435    if !tiling.bottom && bottom_left_bounds.contains(&pos) {
7436        return Some(ResizeEdge::BottomLeft);
7437    }
7438
7439    let bottom_right_bounds = Bounds::new(
7440        Point::new(
7441            window_size.width - corner_size.width,
7442            window_size.height - corner_size.height,
7443        ),
7444        corner_size,
7445    );
7446    if !tiling.bottom && bottom_right_bounds.contains(&pos) {
7447        return Some(ResizeEdge::BottomRight);
7448    }
7449
7450    if !tiling.top && pos.y < shadow_size {
7451        Some(ResizeEdge::Top)
7452    } else if !tiling.bottom && pos.y > window_size.height - shadow_size {
7453        Some(ResizeEdge::Bottom)
7454    } else if !tiling.left && pos.x < shadow_size {
7455        Some(ResizeEdge::Left)
7456    } else if !tiling.right && pos.x > window_size.width - shadow_size {
7457        Some(ResizeEdge::Right)
7458    } else {
7459        None
7460    }
7461}
7462
7463fn join_pane_into_active(
7464    active_pane: &Entity<Pane>,
7465    pane: &Entity<Pane>,
7466    window: &mut Window,
7467    cx: &mut App,
7468) {
7469    if pane == active_pane {
7470        return;
7471    } else if pane.read(cx).items_len() == 0 {
7472        pane.update(cx, |_, cx| {
7473            cx.emit(pane::Event::Remove {
7474                focus_on_pane: None,
7475            });
7476        })
7477    } else {
7478        move_all_items(pane, active_pane, window, cx);
7479    }
7480}
7481
7482fn move_all_items(
7483    from_pane: &Entity<Pane>,
7484    to_pane: &Entity<Pane>,
7485    window: &mut Window,
7486    cx: &mut App,
7487) {
7488    let destination_is_different = from_pane != to_pane;
7489    let mut moved_items = 0;
7490    for (item_ix, item_handle) in from_pane
7491        .read(cx)
7492        .items()
7493        .enumerate()
7494        .map(|(ix, item)| (ix, item.clone()))
7495        .collect::<Vec<_>>()
7496    {
7497        let ix = item_ix - moved_items;
7498        if destination_is_different {
7499            // Close item from previous pane
7500            from_pane.update(cx, |source, cx| {
7501                source.remove_item_and_focus_on_pane(ix, false, to_pane.clone(), window, cx);
7502            });
7503            moved_items += 1;
7504        }
7505
7506        // This automatically removes duplicate items in the pane
7507        to_pane.update(cx, |destination, cx| {
7508            destination.add_item(item_handle, true, true, None, window, cx);
7509            window.focus(&destination.focus_handle(cx))
7510        });
7511    }
7512}
7513
7514pub fn move_item(
7515    source: &Entity<Pane>,
7516    destination: &Entity<Pane>,
7517    item_id_to_move: EntityId,
7518    destination_index: usize,
7519    window: &mut Window,
7520    cx: &mut App,
7521) {
7522    let Some((item_ix, item_handle)) = source
7523        .read(cx)
7524        .items()
7525        .enumerate()
7526        .find(|(_, item_handle)| item_handle.item_id() == item_id_to_move)
7527        .map(|(ix, item)| (ix, item.clone()))
7528    else {
7529        // Tab was closed during drag
7530        return;
7531    };
7532
7533    if source != destination {
7534        // Close item from previous pane
7535        source.update(cx, |source, cx| {
7536            source.remove_item_and_focus_on_pane(item_ix, false, destination.clone(), window, cx);
7537        });
7538    }
7539
7540    // This automatically removes duplicate items in the pane
7541    destination.update(cx, |destination, cx| {
7542        destination.add_item(item_handle, true, true, Some(destination_index), window, cx);
7543        window.focus(&destination.focus_handle(cx))
7544    });
7545}
7546
7547pub fn move_active_item(
7548    source: &Entity<Pane>,
7549    destination: &Entity<Pane>,
7550    focus_destination: bool,
7551    close_if_empty: bool,
7552    window: &mut Window,
7553    cx: &mut App,
7554) {
7555    if source == destination {
7556        return;
7557    }
7558    let Some(active_item) = source.read(cx).active_item() else {
7559        return;
7560    };
7561    source.update(cx, |source_pane, cx| {
7562        let item_id = active_item.item_id();
7563        source_pane.remove_item(item_id, false, close_if_empty, window, cx);
7564        destination.update(cx, |target_pane, cx| {
7565            target_pane.add_item(
7566                active_item,
7567                focus_destination,
7568                focus_destination,
7569                Some(target_pane.items_len()),
7570                window,
7571                cx,
7572            );
7573        });
7574    });
7575}
7576
7577#[derive(Debug)]
7578pub struct WorkspacePosition {
7579    pub window_bounds: Option<WindowBounds>,
7580    pub display: Option<Uuid>,
7581    pub centered_layout: bool,
7582}
7583
7584pub fn ssh_workspace_position_from_db(
7585    host: String,
7586    port: Option<u16>,
7587    user: Option<String>,
7588    paths_to_open: &[PathBuf],
7589    cx: &App,
7590) -> Task<Result<WorkspacePosition>> {
7591    let paths = paths_to_open
7592        .iter()
7593        .map(|path| path.to_string_lossy().to_string())
7594        .collect::<Vec<_>>();
7595
7596    cx.background_spawn(async move {
7597        let serialized_ssh_project = persistence::DB
7598            .get_or_create_ssh_project(host, port, paths, user)
7599            .await
7600            .context("fetching serialized ssh project")?;
7601        let serialized_workspace =
7602            persistence::DB.workspace_for_ssh_project(&serialized_ssh_project);
7603
7604        let (window_bounds, display) = if let Some(bounds) = window_bounds_env_override() {
7605            (Some(WindowBounds::Windowed(bounds)), None)
7606        } else {
7607            let restorable_bounds = serialized_workspace
7608                .as_ref()
7609                .and_then(|workspace| Some((workspace.display?, workspace.window_bounds?)))
7610                .or_else(|| {
7611                    let (display, window_bounds) = DB.last_window().log_err()?;
7612                    Some((display?, window_bounds?))
7613                });
7614
7615            if let Some((serialized_display, serialized_status)) = restorable_bounds {
7616                (Some(serialized_status.0), Some(serialized_display))
7617            } else {
7618                (None, None)
7619            }
7620        };
7621
7622        let centered_layout = serialized_workspace
7623            .as_ref()
7624            .map(|w| w.centered_layout)
7625            .unwrap_or(false);
7626
7627        Ok(WorkspacePosition {
7628            window_bounds,
7629            display,
7630            centered_layout,
7631        })
7632    })
7633}
7634
7635#[cfg(test)]
7636mod tests {
7637    use std::{cell::RefCell, rc::Rc};
7638
7639    use super::*;
7640    use crate::{
7641        dock::{PanelEvent, test::TestPanel},
7642        item::{
7643            ItemEvent,
7644            test::{TestItem, TestProjectItem},
7645        },
7646    };
7647    use fs::FakeFs;
7648    use gpui::{
7649        DismissEvent, Empty, EventEmitter, FocusHandle, Focusable, Render, TestAppContext,
7650        UpdateGlobal, VisualTestContext, px,
7651    };
7652    use project::{Project, ProjectEntryId};
7653    use serde_json::json;
7654    use settings::SettingsStore;
7655
7656    #[gpui::test]
7657    async fn test_tab_disambiguation(cx: &mut TestAppContext) {
7658        init_test(cx);
7659
7660        let fs = FakeFs::new(cx.executor());
7661        let project = Project::test(fs, [], cx).await;
7662        let (workspace, cx) =
7663            cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
7664
7665        // Adding an item with no ambiguity renders the tab without detail.
7666        let item1 = cx.new(|cx| {
7667            let mut item = TestItem::new(cx);
7668            item.tab_descriptions = Some(vec!["c", "b1/c", "a/b1/c"]);
7669            item
7670        });
7671        workspace.update_in(cx, |workspace, window, cx| {
7672            workspace.add_item_to_active_pane(Box::new(item1.clone()), None, true, window, cx);
7673        });
7674        item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(0)));
7675
7676        // Adding an item that creates ambiguity increases the level of detail on
7677        // both tabs.
7678        let item2 = cx.new_window_entity(|_window, cx| {
7679            let mut item = TestItem::new(cx);
7680            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
7681            item
7682        });
7683        workspace.update_in(cx, |workspace, window, cx| {
7684            workspace.add_item_to_active_pane(Box::new(item2.clone()), None, true, window, cx);
7685        });
7686        item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
7687        item2.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
7688
7689        // Adding an item that creates ambiguity increases the level of detail only
7690        // on the ambiguous tabs. In this case, the ambiguity can't be resolved so
7691        // we stop at the highest detail available.
7692        let item3 = cx.new(|cx| {
7693            let mut item = TestItem::new(cx);
7694            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
7695            item
7696        });
7697        workspace.update_in(cx, |workspace, window, cx| {
7698            workspace.add_item_to_active_pane(Box::new(item3.clone()), None, true, window, cx);
7699        });
7700        item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
7701        item2.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
7702        item3.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
7703    }
7704
7705    #[gpui::test]
7706    async fn test_tracking_active_path(cx: &mut TestAppContext) {
7707        init_test(cx);
7708
7709        let fs = FakeFs::new(cx.executor());
7710        fs.insert_tree(
7711            "/root1",
7712            json!({
7713                "one.txt": "",
7714                "two.txt": "",
7715            }),
7716        )
7717        .await;
7718        fs.insert_tree(
7719            "/root2",
7720            json!({
7721                "three.txt": "",
7722            }),
7723        )
7724        .await;
7725
7726        let project = Project::test(fs, ["root1".as_ref()], cx).await;
7727        let (workspace, cx) =
7728            cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
7729        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
7730        let worktree_id = project.update(cx, |project, cx| {
7731            project.worktrees(cx).next().unwrap().read(cx).id()
7732        });
7733
7734        let item1 = cx.new(|cx| {
7735            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "one.txt", cx)])
7736        });
7737        let item2 = cx.new(|cx| {
7738            TestItem::new(cx).with_project_items(&[TestProjectItem::new(2, "two.txt", cx)])
7739        });
7740
7741        // Add an item to an empty pane
7742        workspace.update_in(cx, |workspace, window, cx| {
7743            workspace.add_item_to_active_pane(Box::new(item1), None, true, window, cx)
7744        });
7745        project.update(cx, |project, cx| {
7746            assert_eq!(
7747                project.active_entry(),
7748                project
7749                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
7750                    .map(|e| e.id)
7751            );
7752        });
7753        assert_eq!(cx.window_title().as_deref(), Some("root1 — one.txt"));
7754
7755        // Add a second item to a non-empty pane
7756        workspace.update_in(cx, |workspace, window, cx| {
7757            workspace.add_item_to_active_pane(Box::new(item2), None, true, window, cx)
7758        });
7759        assert_eq!(cx.window_title().as_deref(), Some("root1 — two.txt"));
7760        project.update(cx, |project, cx| {
7761            assert_eq!(
7762                project.active_entry(),
7763                project
7764                    .entry_for_path(&(worktree_id, "two.txt").into(), cx)
7765                    .map(|e| e.id)
7766            );
7767        });
7768
7769        // Close the active item
7770        pane.update_in(cx, |pane, window, cx| {
7771            pane.close_active_item(&Default::default(), window, cx)
7772        })
7773        .await
7774        .unwrap();
7775        assert_eq!(cx.window_title().as_deref(), Some("root1 — one.txt"));
7776        project.update(cx, |project, cx| {
7777            assert_eq!(
7778                project.active_entry(),
7779                project
7780                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
7781                    .map(|e| e.id)
7782            );
7783        });
7784
7785        // Add a project folder
7786        project
7787            .update(cx, |project, cx| {
7788                project.find_or_create_worktree("root2", true, cx)
7789            })
7790            .await
7791            .unwrap();
7792        assert_eq!(cx.window_title().as_deref(), Some("root1, root2 — one.txt"));
7793
7794        // Remove a project folder
7795        project.update(cx, |project, cx| project.remove_worktree(worktree_id, cx));
7796        assert_eq!(cx.window_title().as_deref(), Some("root2 — one.txt"));
7797    }
7798
7799    #[gpui::test]
7800    async fn test_close_window(cx: &mut TestAppContext) {
7801        init_test(cx);
7802
7803        let fs = FakeFs::new(cx.executor());
7804        fs.insert_tree("/root", json!({ "one": "" })).await;
7805
7806        let project = Project::test(fs, ["root".as_ref()], cx).await;
7807        let (workspace, cx) =
7808            cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
7809
7810        // When there are no dirty items, there's nothing to do.
7811        let item1 = cx.new(TestItem::new);
7812        workspace.update_in(cx, |w, window, cx| {
7813            w.add_item_to_active_pane(Box::new(item1.clone()), None, true, window, cx)
7814        });
7815        let task = workspace.update_in(cx, |w, window, cx| {
7816            w.prepare_to_close(CloseIntent::CloseWindow, window, cx)
7817        });
7818        assert!(task.await.unwrap());
7819
7820        // When there are dirty untitled items, prompt to save each one. If the user
7821        // cancels any prompt, then abort.
7822        let item2 = cx.new(|cx| TestItem::new(cx).with_dirty(true));
7823        let item3 = cx.new(|cx| {
7824            TestItem::new(cx)
7825                .with_dirty(true)
7826                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
7827        });
7828        workspace.update_in(cx, |w, window, cx| {
7829            w.add_item_to_active_pane(Box::new(item2.clone()), None, true, window, cx);
7830            w.add_item_to_active_pane(Box::new(item3.clone()), None, true, window, cx);
7831        });
7832        let task = workspace.update_in(cx, |w, window, cx| {
7833            w.prepare_to_close(CloseIntent::CloseWindow, window, cx)
7834        });
7835        cx.executor().run_until_parked();
7836        cx.simulate_prompt_answer("Cancel"); // cancel save all
7837        cx.executor().run_until_parked();
7838        assert!(!cx.has_pending_prompt());
7839        assert!(!task.await.unwrap());
7840    }
7841
7842    #[gpui::test]
7843    async fn test_close_window_with_serializable_items(cx: &mut TestAppContext) {
7844        init_test(cx);
7845
7846        // Register TestItem as a serializable item
7847        cx.update(|cx| {
7848            register_serializable_item::<TestItem>(cx);
7849        });
7850
7851        let fs = FakeFs::new(cx.executor());
7852        fs.insert_tree("/root", json!({ "one": "" })).await;
7853
7854        let project = Project::test(fs, ["root".as_ref()], cx).await;
7855        let (workspace, cx) =
7856            cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
7857
7858        // When there are dirty untitled items, but they can serialize, then there is no prompt.
7859        let item1 = cx.new(|cx| {
7860            TestItem::new(cx)
7861                .with_dirty(true)
7862                .with_serialize(|| Some(Task::ready(Ok(()))))
7863        });
7864        let item2 = cx.new(|cx| {
7865            TestItem::new(cx)
7866                .with_dirty(true)
7867                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
7868                .with_serialize(|| Some(Task::ready(Ok(()))))
7869        });
7870        workspace.update_in(cx, |w, window, cx| {
7871            w.add_item_to_active_pane(Box::new(item1.clone()), None, true, window, cx);
7872            w.add_item_to_active_pane(Box::new(item2.clone()), None, true, window, cx);
7873        });
7874        let task = workspace.update_in(cx, |w, window, cx| {
7875            w.prepare_to_close(CloseIntent::CloseWindow, window, cx)
7876        });
7877        assert!(task.await.unwrap());
7878    }
7879
7880    #[gpui::test]
7881    async fn test_close_pane_items(cx: &mut TestAppContext) {
7882        init_test(cx);
7883
7884        let fs = FakeFs::new(cx.executor());
7885
7886        let project = Project::test(fs, None, cx).await;
7887        let (workspace, cx) =
7888            cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
7889
7890        let item1 = cx.new(|cx| {
7891            TestItem::new(cx)
7892                .with_dirty(true)
7893                .with_project_items(&[dirty_project_item(1, "1.txt", cx)])
7894        });
7895        let item2 = cx.new(|cx| {
7896            TestItem::new(cx)
7897                .with_dirty(true)
7898                .with_conflict(true)
7899                .with_project_items(&[dirty_project_item(2, "2.txt", cx)])
7900        });
7901        let item3 = cx.new(|cx| {
7902            TestItem::new(cx)
7903                .with_dirty(true)
7904                .with_conflict(true)
7905                .with_project_items(&[dirty_project_item(3, "3.txt", cx)])
7906        });
7907        let item4 = cx.new(|cx| {
7908            TestItem::new(cx).with_dirty(true).with_project_items(&[{
7909                let project_item = TestProjectItem::new_untitled(cx);
7910                project_item.update(cx, |project_item, _| project_item.is_dirty = true);
7911                project_item
7912            }])
7913        });
7914        let pane = workspace.update_in(cx, |workspace, window, cx| {
7915            workspace.add_item_to_active_pane(Box::new(item1.clone()), None, true, window, cx);
7916            workspace.add_item_to_active_pane(Box::new(item2.clone()), None, true, window, cx);
7917            workspace.add_item_to_active_pane(Box::new(item3.clone()), None, true, window, cx);
7918            workspace.add_item_to_active_pane(Box::new(item4.clone()), None, true, window, cx);
7919            workspace.active_pane().clone()
7920        });
7921
7922        let close_items = pane.update_in(cx, |pane, window, cx| {
7923            pane.activate_item(1, true, true, window, cx);
7924            assert_eq!(pane.active_item().unwrap().item_id(), item2.item_id());
7925            let item1_id = item1.item_id();
7926            let item3_id = item3.item_id();
7927            let item4_id = item4.item_id();
7928            pane.close_items(window, cx, SaveIntent::Close, move |id| {
7929                [item1_id, item3_id, item4_id].contains(&id)
7930            })
7931        });
7932        cx.executor().run_until_parked();
7933
7934        assert!(cx.has_pending_prompt());
7935        cx.simulate_prompt_answer("Save all");
7936
7937        cx.executor().run_until_parked();
7938
7939        // Item 1 is saved. There's a prompt to save item 3.
7940        pane.update(cx, |pane, cx| {
7941            assert_eq!(item1.read(cx).save_count, 1);
7942            assert_eq!(item1.read(cx).save_as_count, 0);
7943            assert_eq!(item1.read(cx).reload_count, 0);
7944            assert_eq!(pane.items_len(), 3);
7945            assert_eq!(pane.active_item().unwrap().item_id(), item3.item_id());
7946        });
7947        assert!(cx.has_pending_prompt());
7948
7949        // Cancel saving item 3.
7950        cx.simulate_prompt_answer("Discard");
7951        cx.executor().run_until_parked();
7952
7953        // Item 3 is reloaded. There's a prompt to save item 4.
7954        pane.update(cx, |pane, cx| {
7955            assert_eq!(item3.read(cx).save_count, 0);
7956            assert_eq!(item3.read(cx).save_as_count, 0);
7957            assert_eq!(item3.read(cx).reload_count, 1);
7958            assert_eq!(pane.items_len(), 2);
7959            assert_eq!(pane.active_item().unwrap().item_id(), item4.item_id());
7960        });
7961
7962        // There's a prompt for a path for item 4.
7963        cx.simulate_new_path_selection(|_| Some(Default::default()));
7964        close_items.await.unwrap();
7965
7966        // The requested items are closed.
7967        pane.update(cx, |pane, cx| {
7968            assert_eq!(item4.read(cx).save_count, 0);
7969            assert_eq!(item4.read(cx).save_as_count, 1);
7970            assert_eq!(item4.read(cx).reload_count, 0);
7971            assert_eq!(pane.items_len(), 1);
7972            assert_eq!(pane.active_item().unwrap().item_id(), item2.item_id());
7973        });
7974    }
7975
7976    #[gpui::test]
7977    async fn test_prompting_to_save_only_on_last_item_for_entry(cx: &mut TestAppContext) {
7978        init_test(cx);
7979
7980        let fs = FakeFs::new(cx.executor());
7981        let project = Project::test(fs, [], cx).await;
7982        let (workspace, cx) =
7983            cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
7984
7985        // Create several workspace items with single project entries, and two
7986        // workspace items with multiple project entries.
7987        let single_entry_items = (0..=4)
7988            .map(|project_entry_id| {
7989                cx.new(|cx| {
7990                    TestItem::new(cx)
7991                        .with_dirty(true)
7992                        .with_project_items(&[dirty_project_item(
7993                            project_entry_id,
7994                            &format!("{project_entry_id}.txt"),
7995                            cx,
7996                        )])
7997                })
7998            })
7999            .collect::<Vec<_>>();
8000        let item_2_3 = cx.new(|cx| {
8001            TestItem::new(cx)
8002                .with_dirty(true)
8003                .with_singleton(false)
8004                .with_project_items(&[
8005                    single_entry_items[2].read(cx).project_items[0].clone(),
8006                    single_entry_items[3].read(cx).project_items[0].clone(),
8007                ])
8008        });
8009        let item_3_4 = cx.new(|cx| {
8010            TestItem::new(cx)
8011                .with_dirty(true)
8012                .with_singleton(false)
8013                .with_project_items(&[
8014                    single_entry_items[3].read(cx).project_items[0].clone(),
8015                    single_entry_items[4].read(cx).project_items[0].clone(),
8016                ])
8017        });
8018
8019        // Create two panes that contain the following project entries:
8020        //   left pane:
8021        //     multi-entry items:   (2, 3)
8022        //     single-entry items:  0, 2, 3, 4
8023        //   right pane:
8024        //     single-entry items:  4, 1
8025        //     multi-entry items:   (3, 4)
8026        let (left_pane, right_pane) = workspace.update_in(cx, |workspace, window, cx| {
8027            let left_pane = workspace.active_pane().clone();
8028            workspace.add_item_to_active_pane(Box::new(item_2_3.clone()), None, true, window, cx);
8029            workspace.add_item_to_active_pane(
8030                single_entry_items[0].boxed_clone(),
8031                None,
8032                true,
8033                window,
8034                cx,
8035            );
8036            workspace.add_item_to_active_pane(
8037                single_entry_items[2].boxed_clone(),
8038                None,
8039                true,
8040                window,
8041                cx,
8042            );
8043            workspace.add_item_to_active_pane(
8044                single_entry_items[3].boxed_clone(),
8045                None,
8046                true,
8047                window,
8048                cx,
8049            );
8050            workspace.add_item_to_active_pane(
8051                single_entry_items[4].boxed_clone(),
8052                None,
8053                true,
8054                window,
8055                cx,
8056            );
8057
8058            let right_pane = workspace
8059                .split_and_clone(left_pane.clone(), SplitDirection::Right, window, cx)
8060                .unwrap();
8061
8062            right_pane.update(cx, |pane, cx| {
8063                pane.add_item(
8064                    single_entry_items[1].boxed_clone(),
8065                    true,
8066                    true,
8067                    None,
8068                    window,
8069                    cx,
8070                );
8071                pane.add_item(Box::new(item_3_4.clone()), true, true, None, window, cx);
8072            });
8073
8074            (left_pane, right_pane)
8075        });
8076
8077        cx.focus(&right_pane);
8078
8079        let mut close = right_pane.update_in(cx, |pane, window, cx| {
8080            pane.close_all_items(&CloseAllItems::default(), window, cx)
8081                .unwrap()
8082        });
8083        cx.executor().run_until_parked();
8084
8085        let msg = cx.pending_prompt().unwrap().0;
8086        assert!(msg.contains("1.txt"));
8087        assert!(!msg.contains("2.txt"));
8088        assert!(!msg.contains("3.txt"));
8089        assert!(!msg.contains("4.txt"));
8090
8091        cx.simulate_prompt_answer("Cancel");
8092        close.await;
8093
8094        left_pane
8095            .update_in(cx, |left_pane, window, cx| {
8096                left_pane.close_item_by_id(
8097                    single_entry_items[3].entity_id(),
8098                    SaveIntent::Skip,
8099                    window,
8100                    cx,
8101                )
8102            })
8103            .await
8104            .unwrap();
8105
8106        close = right_pane.update_in(cx, |pane, window, cx| {
8107            pane.close_all_items(&CloseAllItems::default(), window, cx)
8108                .unwrap()
8109        });
8110        cx.executor().run_until_parked();
8111
8112        let details = cx.pending_prompt().unwrap().1;
8113        assert!(details.contains("1.txt"));
8114        assert!(!details.contains("2.txt"));
8115        assert!(details.contains("3.txt"));
8116        // ideally this assertion could be made, but today we can only
8117        // save whole items not project items, so the orphaned item 3 causes
8118        // 4 to be saved too.
8119        // assert!(!details.contains("4.txt"));
8120
8121        cx.simulate_prompt_answer("Save all");
8122
8123        cx.executor().run_until_parked();
8124        close.await;
8125        right_pane.read_with(cx, |pane, _| {
8126            assert_eq!(pane.items_len(), 0);
8127        });
8128    }
8129
8130    #[gpui::test]
8131    async fn test_autosave(cx: &mut gpui::TestAppContext) {
8132        init_test(cx);
8133
8134        let fs = FakeFs::new(cx.executor());
8135        let project = Project::test(fs, [], cx).await;
8136        let (workspace, cx) =
8137            cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
8138        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
8139
8140        let item = cx.new(|cx| {
8141            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
8142        });
8143        let item_id = item.entity_id();
8144        workspace.update_in(cx, |workspace, window, cx| {
8145            workspace.add_item_to_active_pane(Box::new(item.clone()), None, true, window, cx);
8146        });
8147
8148        // Autosave on window change.
8149        item.update(cx, |item, cx| {
8150            SettingsStore::update_global(cx, |settings, cx| {
8151                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
8152                    settings.autosave = Some(AutosaveSetting::OnWindowChange);
8153                })
8154            });
8155            item.is_dirty = true;
8156        });
8157
8158        // Deactivating the window saves the file.
8159        cx.deactivate_window();
8160        item.read_with(cx, |item, _| assert_eq!(item.save_count, 1));
8161
8162        // Re-activating the window doesn't save the file.
8163        cx.update(|window, _| window.activate_window());
8164        cx.executor().run_until_parked();
8165        item.read_with(cx, |item, _| assert_eq!(item.save_count, 1));
8166
8167        // Autosave on focus change.
8168        item.update_in(cx, |item, window, cx| {
8169            cx.focus_self(window);
8170            SettingsStore::update_global(cx, |settings, cx| {
8171                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
8172                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
8173                })
8174            });
8175            item.is_dirty = true;
8176        });
8177
8178        // Blurring the item saves the file.
8179        item.update_in(cx, |_, window, _| window.blur());
8180        cx.executor().run_until_parked();
8181        item.read_with(cx, |item, _| assert_eq!(item.save_count, 2));
8182
8183        // Deactivating the window still saves the file.
8184        item.update_in(cx, |item, window, cx| {
8185            cx.focus_self(window);
8186            item.is_dirty = true;
8187        });
8188        cx.deactivate_window();
8189        item.update(cx, |item, _| assert_eq!(item.save_count, 3));
8190
8191        // Autosave after delay.
8192        item.update(cx, |item, cx| {
8193            SettingsStore::update_global(cx, |settings, cx| {
8194                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
8195                    settings.autosave = Some(AutosaveSetting::AfterDelay { milliseconds: 500 });
8196                })
8197            });
8198            item.is_dirty = true;
8199            cx.emit(ItemEvent::Edit);
8200        });
8201
8202        // Delay hasn't fully expired, so the file is still dirty and unsaved.
8203        cx.executor().advance_clock(Duration::from_millis(250));
8204        item.read_with(cx, |item, _| assert_eq!(item.save_count, 3));
8205
8206        // After delay expires, the file is saved.
8207        cx.executor().advance_clock(Duration::from_millis(250));
8208        item.read_with(cx, |item, _| assert_eq!(item.save_count, 4));
8209
8210        // Autosave on focus change, ensuring closing the tab counts as such.
8211        item.update(cx, |item, cx| {
8212            SettingsStore::update_global(cx, |settings, cx| {
8213                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
8214                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
8215                })
8216            });
8217            item.is_dirty = true;
8218            for project_item in &mut item.project_items {
8219                project_item.update(cx, |project_item, _| project_item.is_dirty = true);
8220            }
8221        });
8222
8223        pane.update_in(cx, |pane, window, cx| {
8224            pane.close_items(window, cx, SaveIntent::Close, move |id| id == item_id)
8225        })
8226        .await
8227        .unwrap();
8228        assert!(!cx.has_pending_prompt());
8229        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
8230
8231        // Add the item again, ensuring autosave is prevented if the underlying file has been deleted.
8232        workspace.update_in(cx, |workspace, window, cx| {
8233            workspace.add_item_to_active_pane(Box::new(item.clone()), None, true, window, cx);
8234        });
8235        item.update_in(cx, |item, window, cx| {
8236            item.project_items[0].update(cx, |item, _| {
8237                item.entry_id = None;
8238            });
8239            item.is_dirty = true;
8240            window.blur();
8241        });
8242        cx.run_until_parked();
8243        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
8244
8245        // Ensure autosave is prevented for deleted files also when closing the buffer.
8246        let _close_items = pane.update_in(cx, |pane, window, cx| {
8247            pane.close_items(window, cx, SaveIntent::Close, move |id| id == item_id)
8248        });
8249        cx.run_until_parked();
8250        assert!(cx.has_pending_prompt());
8251        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
8252    }
8253
8254    #[gpui::test]
8255    async fn test_pane_navigation(cx: &mut gpui::TestAppContext) {
8256        init_test(cx);
8257
8258        let fs = FakeFs::new(cx.executor());
8259
8260        let project = Project::test(fs, [], cx).await;
8261        let (workspace, cx) =
8262            cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
8263
8264        let item = cx.new(|cx| {
8265            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
8266        });
8267        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
8268        let toolbar = pane.read_with(cx, |pane, _| pane.toolbar().clone());
8269        let toolbar_notify_count = Rc::new(RefCell::new(0));
8270
8271        workspace.update_in(cx, |workspace, window, cx| {
8272            workspace.add_item_to_active_pane(Box::new(item.clone()), None, true, window, cx);
8273            let toolbar_notification_count = toolbar_notify_count.clone();
8274            cx.observe_in(&toolbar, window, move |_, _, _, _| {
8275                *toolbar_notification_count.borrow_mut() += 1
8276            })
8277            .detach();
8278        });
8279
8280        pane.read_with(cx, |pane, _| {
8281            assert!(!pane.can_navigate_backward());
8282            assert!(!pane.can_navigate_forward());
8283        });
8284
8285        item.update_in(cx, |item, _, cx| {
8286            item.set_state("one".to_string(), cx);
8287        });
8288
8289        // Toolbar must be notified to re-render the navigation buttons
8290        assert_eq!(*toolbar_notify_count.borrow(), 1);
8291
8292        pane.read_with(cx, |pane, _| {
8293            assert!(pane.can_navigate_backward());
8294            assert!(!pane.can_navigate_forward());
8295        });
8296
8297        workspace
8298            .update_in(cx, |workspace, window, cx| {
8299                workspace.go_back(pane.downgrade(), window, cx)
8300            })
8301            .await
8302            .unwrap();
8303
8304        assert_eq!(*toolbar_notify_count.borrow(), 2);
8305        pane.read_with(cx, |pane, _| {
8306            assert!(!pane.can_navigate_backward());
8307            assert!(pane.can_navigate_forward());
8308        });
8309    }
8310
8311    #[gpui::test]
8312    async fn test_toggle_docks_and_panels(cx: &mut gpui::TestAppContext) {
8313        init_test(cx);
8314        let fs = FakeFs::new(cx.executor());
8315
8316        let project = Project::test(fs, [], cx).await;
8317        let (workspace, cx) =
8318            cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
8319
8320        let panel = workspace.update_in(cx, |workspace, window, cx| {
8321            let panel = cx.new(|cx| TestPanel::new(DockPosition::Right, cx));
8322            workspace.add_panel(panel.clone(), window, cx);
8323
8324            workspace
8325                .right_dock()
8326                .update(cx, |right_dock, cx| right_dock.set_open(true, window, cx));
8327
8328            panel
8329        });
8330
8331        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
8332        pane.update_in(cx, |pane, window, cx| {
8333            let item = cx.new(TestItem::new);
8334            pane.add_item(Box::new(item), true, true, None, window, cx);
8335        });
8336
8337        // Transfer focus from center to panel
8338        workspace.update_in(cx, |workspace, window, cx| {
8339            workspace.toggle_panel_focus::<TestPanel>(window, cx);
8340        });
8341
8342        workspace.update_in(cx, |workspace, window, cx| {
8343            assert!(workspace.right_dock().read(cx).is_open());
8344            assert!(!panel.is_zoomed(window, cx));
8345            assert!(panel.read(cx).focus_handle(cx).contains_focused(window, cx));
8346        });
8347
8348        // Transfer focus from panel to center
8349        workspace.update_in(cx, |workspace, window, cx| {
8350            workspace.toggle_panel_focus::<TestPanel>(window, cx);
8351        });
8352
8353        workspace.update_in(cx, |workspace, window, cx| {
8354            assert!(workspace.right_dock().read(cx).is_open());
8355            assert!(!panel.is_zoomed(window, cx));
8356            assert!(!panel.read(cx).focus_handle(cx).contains_focused(window, cx));
8357        });
8358
8359        // Close the dock
8360        workspace.update_in(cx, |workspace, window, cx| {
8361            workspace.toggle_dock(DockPosition::Right, window, cx);
8362        });
8363
8364        workspace.update_in(cx, |workspace, window, cx| {
8365            assert!(!workspace.right_dock().read(cx).is_open());
8366            assert!(!panel.is_zoomed(window, cx));
8367            assert!(!panel.read(cx).focus_handle(cx).contains_focused(window, cx));
8368        });
8369
8370        // Open the dock
8371        workspace.update_in(cx, |workspace, window, cx| {
8372            workspace.toggle_dock(DockPosition::Right, window, cx);
8373        });
8374
8375        workspace.update_in(cx, |workspace, window, cx| {
8376            assert!(workspace.right_dock().read(cx).is_open());
8377            assert!(!panel.is_zoomed(window, cx));
8378            assert!(panel.read(cx).focus_handle(cx).contains_focused(window, cx));
8379        });
8380
8381        // Focus and zoom panel
8382        panel.update_in(cx, |panel, window, cx| {
8383            cx.focus_self(window);
8384            panel.set_zoomed(true, window, cx)
8385        });
8386
8387        workspace.update_in(cx, |workspace, window, cx| {
8388            assert!(workspace.right_dock().read(cx).is_open());
8389            assert!(panel.is_zoomed(window, cx));
8390            assert!(panel.read(cx).focus_handle(cx).contains_focused(window, cx));
8391        });
8392
8393        // Transfer focus to the center closes the dock
8394        workspace.update_in(cx, |workspace, window, cx| {
8395            workspace.toggle_panel_focus::<TestPanel>(window, cx);
8396        });
8397
8398        workspace.update_in(cx, |workspace, window, cx| {
8399            assert!(!workspace.right_dock().read(cx).is_open());
8400            assert!(panel.is_zoomed(window, cx));
8401            assert!(!panel.read(cx).focus_handle(cx).contains_focused(window, cx));
8402        });
8403
8404        // Transferring focus back to the panel keeps it zoomed
8405        workspace.update_in(cx, |workspace, window, cx| {
8406            workspace.toggle_panel_focus::<TestPanel>(window, cx);
8407        });
8408
8409        workspace.update_in(cx, |workspace, window, cx| {
8410            assert!(workspace.right_dock().read(cx).is_open());
8411            assert!(panel.is_zoomed(window, cx));
8412            assert!(panel.read(cx).focus_handle(cx).contains_focused(window, cx));
8413        });
8414
8415        // Close the dock while it is zoomed
8416        workspace.update_in(cx, |workspace, window, cx| {
8417            workspace.toggle_dock(DockPosition::Right, window, cx)
8418        });
8419
8420        workspace.update_in(cx, |workspace, window, cx| {
8421            assert!(!workspace.right_dock().read(cx).is_open());
8422            assert!(panel.is_zoomed(window, cx));
8423            assert!(workspace.zoomed.is_none());
8424            assert!(!panel.read(cx).focus_handle(cx).contains_focused(window, cx));
8425        });
8426
8427        // Opening the dock, when it's zoomed, retains focus
8428        workspace.update_in(cx, |workspace, window, cx| {
8429            workspace.toggle_dock(DockPosition::Right, window, cx)
8430        });
8431
8432        workspace.update_in(cx, |workspace, window, cx| {
8433            assert!(workspace.right_dock().read(cx).is_open());
8434            assert!(panel.is_zoomed(window, cx));
8435            assert!(workspace.zoomed.is_some());
8436            assert!(panel.read(cx).focus_handle(cx).contains_focused(window, cx));
8437        });
8438
8439        // Unzoom and close the panel, zoom the active pane.
8440        panel.update_in(cx, |panel, window, cx| panel.set_zoomed(false, window, cx));
8441        workspace.update_in(cx, |workspace, window, cx| {
8442            workspace.toggle_dock(DockPosition::Right, window, cx)
8443        });
8444        pane.update_in(cx, |pane, window, cx| {
8445            pane.toggle_zoom(&Default::default(), window, cx)
8446        });
8447
8448        // Opening a dock unzooms the pane.
8449        workspace.update_in(cx, |workspace, window, cx| {
8450            workspace.toggle_dock(DockPosition::Right, window, cx)
8451        });
8452        workspace.update_in(cx, |workspace, window, cx| {
8453            let pane = pane.read(cx);
8454            assert!(!pane.is_zoomed());
8455            assert!(!pane.focus_handle(cx).is_focused(window));
8456            assert!(workspace.right_dock().read(cx).is_open());
8457            assert!(workspace.zoomed.is_none());
8458        });
8459    }
8460
8461    #[gpui::test]
8462    async fn test_join_pane_into_next(cx: &mut gpui::TestAppContext) {
8463        init_test(cx);
8464
8465        let fs = FakeFs::new(cx.executor());
8466
8467        let project = Project::test(fs, None, cx).await;
8468        let (workspace, cx) =
8469            cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
8470
8471        // Let's arrange the panes like this:
8472        //
8473        // +-----------------------+
8474        // |         top           |
8475        // +------+--------+-------+
8476        // | left | center | right |
8477        // +------+--------+-------+
8478        // |        bottom         |
8479        // +-----------------------+
8480
8481        let top_item = cx.new(|cx| {
8482            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "top.txt", cx)])
8483        });
8484        let bottom_item = cx.new(|cx| {
8485            TestItem::new(cx).with_project_items(&[TestProjectItem::new(2, "bottom.txt", cx)])
8486        });
8487        let left_item = cx.new(|cx| {
8488            TestItem::new(cx).with_project_items(&[TestProjectItem::new(3, "left.txt", cx)])
8489        });
8490        let right_item = cx.new(|cx| {
8491            TestItem::new(cx).with_project_items(&[TestProjectItem::new(4, "right.txt", cx)])
8492        });
8493        let center_item = cx.new(|cx| {
8494            TestItem::new(cx).with_project_items(&[TestProjectItem::new(5, "center.txt", cx)])
8495        });
8496
8497        let top_pane_id = workspace.update_in(cx, |workspace, window, cx| {
8498            let top_pane_id = workspace.active_pane().entity_id();
8499            workspace.add_item_to_active_pane(Box::new(top_item.clone()), None, false, window, cx);
8500            workspace.split_pane(
8501                workspace.active_pane().clone(),
8502                SplitDirection::Down,
8503                window,
8504                cx,
8505            );
8506            top_pane_id
8507        });
8508        let bottom_pane_id = workspace.update_in(cx, |workspace, window, cx| {
8509            let bottom_pane_id = workspace.active_pane().entity_id();
8510            workspace.add_item_to_active_pane(
8511                Box::new(bottom_item.clone()),
8512                None,
8513                false,
8514                window,
8515                cx,
8516            );
8517            workspace.split_pane(
8518                workspace.active_pane().clone(),
8519                SplitDirection::Up,
8520                window,
8521                cx,
8522            );
8523            bottom_pane_id
8524        });
8525        let left_pane_id = workspace.update_in(cx, |workspace, window, cx| {
8526            let left_pane_id = workspace.active_pane().entity_id();
8527            workspace.add_item_to_active_pane(Box::new(left_item.clone()), None, false, window, cx);
8528            workspace.split_pane(
8529                workspace.active_pane().clone(),
8530                SplitDirection::Right,
8531                window,
8532                cx,
8533            );
8534            left_pane_id
8535        });
8536        let right_pane_id = workspace.update_in(cx, |workspace, window, cx| {
8537            let right_pane_id = workspace.active_pane().entity_id();
8538            workspace.add_item_to_active_pane(
8539                Box::new(right_item.clone()),
8540                None,
8541                false,
8542                window,
8543                cx,
8544            );
8545            workspace.split_pane(
8546                workspace.active_pane().clone(),
8547                SplitDirection::Left,
8548                window,
8549                cx,
8550            );
8551            right_pane_id
8552        });
8553        let center_pane_id = workspace.update_in(cx, |workspace, window, cx| {
8554            let center_pane_id = workspace.active_pane().entity_id();
8555            workspace.add_item_to_active_pane(
8556                Box::new(center_item.clone()),
8557                None,
8558                false,
8559                window,
8560                cx,
8561            );
8562            center_pane_id
8563        });
8564        cx.executor().run_until_parked();
8565
8566        workspace.update_in(cx, |workspace, window, cx| {
8567            assert_eq!(center_pane_id, workspace.active_pane().entity_id());
8568
8569            // Join into next from center pane into right
8570            workspace.join_pane_into_next(workspace.active_pane().clone(), window, cx);
8571        });
8572
8573        workspace.update_in(cx, |workspace, window, cx| {
8574            let active_pane = workspace.active_pane();
8575            assert_eq!(right_pane_id, active_pane.entity_id());
8576            assert_eq!(2, active_pane.read(cx).items_len());
8577            let item_ids_in_pane =
8578                HashSet::from_iter(active_pane.read(cx).items().map(|item| item.item_id()));
8579            assert!(item_ids_in_pane.contains(&center_item.item_id()));
8580            assert!(item_ids_in_pane.contains(&right_item.item_id()));
8581
8582            // Join into next from right pane into bottom
8583            workspace.join_pane_into_next(workspace.active_pane().clone(), window, cx);
8584        });
8585
8586        workspace.update_in(cx, |workspace, window, cx| {
8587            let active_pane = workspace.active_pane();
8588            assert_eq!(bottom_pane_id, active_pane.entity_id());
8589            assert_eq!(3, active_pane.read(cx).items_len());
8590            let item_ids_in_pane =
8591                HashSet::from_iter(active_pane.read(cx).items().map(|item| item.item_id()));
8592            assert!(item_ids_in_pane.contains(&center_item.item_id()));
8593            assert!(item_ids_in_pane.contains(&right_item.item_id()));
8594            assert!(item_ids_in_pane.contains(&bottom_item.item_id()));
8595
8596            // Join into next from bottom pane into left
8597            workspace.join_pane_into_next(workspace.active_pane().clone(), window, cx);
8598        });
8599
8600        workspace.update_in(cx, |workspace, window, cx| {
8601            let active_pane = workspace.active_pane();
8602            assert_eq!(left_pane_id, active_pane.entity_id());
8603            assert_eq!(4, active_pane.read(cx).items_len());
8604            let item_ids_in_pane =
8605                HashSet::from_iter(active_pane.read(cx).items().map(|item| item.item_id()));
8606            assert!(item_ids_in_pane.contains(&center_item.item_id()));
8607            assert!(item_ids_in_pane.contains(&right_item.item_id()));
8608            assert!(item_ids_in_pane.contains(&bottom_item.item_id()));
8609            assert!(item_ids_in_pane.contains(&left_item.item_id()));
8610
8611            // Join into next from left pane into top
8612            workspace.join_pane_into_next(workspace.active_pane().clone(), window, cx);
8613        });
8614
8615        workspace.update_in(cx, |workspace, window, cx| {
8616            let active_pane = workspace.active_pane();
8617            assert_eq!(top_pane_id, active_pane.entity_id());
8618            assert_eq!(5, active_pane.read(cx).items_len());
8619            let item_ids_in_pane =
8620                HashSet::from_iter(active_pane.read(cx).items().map(|item| item.item_id()));
8621            assert!(item_ids_in_pane.contains(&center_item.item_id()));
8622            assert!(item_ids_in_pane.contains(&right_item.item_id()));
8623            assert!(item_ids_in_pane.contains(&bottom_item.item_id()));
8624            assert!(item_ids_in_pane.contains(&left_item.item_id()));
8625            assert!(item_ids_in_pane.contains(&top_item.item_id()));
8626
8627            // Single pane left: no-op
8628            workspace.join_pane_into_next(workspace.active_pane().clone(), window, cx)
8629        });
8630
8631        workspace.update(cx, |workspace, _cx| {
8632            let active_pane = workspace.active_pane();
8633            assert_eq!(top_pane_id, active_pane.entity_id());
8634        });
8635    }
8636
8637    fn add_an_item_to_active_pane(
8638        cx: &mut VisualTestContext,
8639        workspace: &Entity<Workspace>,
8640        item_id: u64,
8641    ) -> Entity<TestItem> {
8642        let item = cx.new(|cx| {
8643            TestItem::new(cx).with_project_items(&[TestProjectItem::new(
8644                item_id,
8645                "item{item_id}.txt",
8646                cx,
8647            )])
8648        });
8649        workspace.update_in(cx, |workspace, window, cx| {
8650            workspace.add_item_to_active_pane(Box::new(item.clone()), None, false, window, cx);
8651        });
8652        return item;
8653    }
8654
8655    fn split_pane(cx: &mut VisualTestContext, workspace: &Entity<Workspace>) -> Entity<Pane> {
8656        return workspace.update_in(cx, |workspace, window, cx| {
8657            let new_pane = workspace.split_pane(
8658                workspace.active_pane().clone(),
8659                SplitDirection::Right,
8660                window,
8661                cx,
8662            );
8663            new_pane
8664        });
8665    }
8666
8667    #[gpui::test]
8668    async fn test_join_all_panes(cx: &mut gpui::TestAppContext) {
8669        init_test(cx);
8670        let fs = FakeFs::new(cx.executor());
8671        let project = Project::test(fs, None, cx).await;
8672        let (workspace, cx) =
8673            cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
8674
8675        add_an_item_to_active_pane(cx, &workspace, 1);
8676        split_pane(cx, &workspace);
8677        add_an_item_to_active_pane(cx, &workspace, 2);
8678        split_pane(cx, &workspace); // empty pane
8679        split_pane(cx, &workspace);
8680        let last_item = add_an_item_to_active_pane(cx, &workspace, 3);
8681
8682        cx.executor().run_until_parked();
8683
8684        workspace.update(cx, |workspace, cx| {
8685            let num_panes = workspace.panes().len();
8686            let num_items_in_current_pane = workspace.active_pane().read(cx).items().count();
8687            let active_item = workspace
8688                .active_pane()
8689                .read(cx)
8690                .active_item()
8691                .expect("item is in focus");
8692
8693            assert_eq!(num_panes, 4);
8694            assert_eq!(num_items_in_current_pane, 1);
8695            assert_eq!(active_item.item_id(), last_item.item_id());
8696        });
8697
8698        workspace.update_in(cx, |workspace, window, cx| {
8699            workspace.join_all_panes(window, cx);
8700        });
8701
8702        workspace.update(cx, |workspace, cx| {
8703            let num_panes = workspace.panes().len();
8704            let num_items_in_current_pane = workspace.active_pane().read(cx).items().count();
8705            let active_item = workspace
8706                .active_pane()
8707                .read(cx)
8708                .active_item()
8709                .expect("item is in focus");
8710
8711            assert_eq!(num_panes, 1);
8712            assert_eq!(num_items_in_current_pane, 3);
8713            assert_eq!(active_item.item_id(), last_item.item_id());
8714        });
8715    }
8716    struct TestModal(FocusHandle);
8717
8718    impl TestModal {
8719        fn new(_: &mut Window, cx: &mut Context<Self>) -> Self {
8720            Self(cx.focus_handle())
8721        }
8722    }
8723
8724    impl EventEmitter<DismissEvent> for TestModal {}
8725
8726    impl Focusable for TestModal {
8727        fn focus_handle(&self, _cx: &App) -> FocusHandle {
8728            self.0.clone()
8729        }
8730    }
8731
8732    impl ModalView for TestModal {}
8733
8734    impl Render for TestModal {
8735        fn render(
8736            &mut self,
8737            _window: &mut Window,
8738            _cx: &mut Context<TestModal>,
8739        ) -> impl IntoElement {
8740            div().track_focus(&self.0)
8741        }
8742    }
8743
8744    #[gpui::test]
8745    async fn test_panels(cx: &mut gpui::TestAppContext) {
8746        init_test(cx);
8747        let fs = FakeFs::new(cx.executor());
8748
8749        let project = Project::test(fs, [], cx).await;
8750        let (workspace, cx) =
8751            cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
8752
8753        let (panel_1, panel_2) = workspace.update_in(cx, |workspace, window, cx| {
8754            let panel_1 = cx.new(|cx| TestPanel::new(DockPosition::Left, cx));
8755            workspace.add_panel(panel_1.clone(), window, cx);
8756            workspace.toggle_dock(DockPosition::Left, window, cx);
8757            let panel_2 = cx.new(|cx| TestPanel::new(DockPosition::Right, cx));
8758            workspace.add_panel(panel_2.clone(), window, cx);
8759            workspace.toggle_dock(DockPosition::Right, window, cx);
8760
8761            let left_dock = workspace.left_dock();
8762            assert_eq!(
8763                left_dock.read(cx).visible_panel().unwrap().panel_id(),
8764                panel_1.panel_id()
8765            );
8766            assert_eq!(
8767                left_dock.read(cx).active_panel_size(window, cx).unwrap(),
8768                panel_1.size(window, cx)
8769            );
8770
8771            left_dock.update(cx, |left_dock, cx| {
8772                left_dock.resize_active_panel(Some(px(1337.)), window, cx)
8773            });
8774            assert_eq!(
8775                workspace
8776                    .right_dock()
8777                    .read(cx)
8778                    .visible_panel()
8779                    .unwrap()
8780                    .panel_id(),
8781                panel_2.panel_id(),
8782            );
8783
8784            (panel_1, panel_2)
8785        });
8786
8787        // Move panel_1 to the right
8788        panel_1.update_in(cx, |panel_1, window, cx| {
8789            panel_1.set_position(DockPosition::Right, window, cx)
8790        });
8791
8792        workspace.update_in(cx, |workspace, window, cx| {
8793            // Since panel_1 was visible on the left, it should now be visible now that it's been moved to the right.
8794            // Since it was the only panel on the left, the left dock should now be closed.
8795            assert!(!workspace.left_dock().read(cx).is_open());
8796            assert!(workspace.left_dock().read(cx).visible_panel().is_none());
8797            let right_dock = workspace.right_dock();
8798            assert_eq!(
8799                right_dock.read(cx).visible_panel().unwrap().panel_id(),
8800                panel_1.panel_id()
8801            );
8802            assert_eq!(
8803                right_dock.read(cx).active_panel_size(window, cx).unwrap(),
8804                px(1337.)
8805            );
8806
8807            // Now we move panel_2 to the left
8808            panel_2.set_position(DockPosition::Left, window, cx);
8809        });
8810
8811        workspace.update(cx, |workspace, cx| {
8812            // Since panel_2 was not visible on the right, we don't open the left dock.
8813            assert!(!workspace.left_dock().read(cx).is_open());
8814            // And the right dock is unaffected in its displaying of panel_1
8815            assert!(workspace.right_dock().read(cx).is_open());
8816            assert_eq!(
8817                workspace
8818                    .right_dock()
8819                    .read(cx)
8820                    .visible_panel()
8821                    .unwrap()
8822                    .panel_id(),
8823                panel_1.panel_id(),
8824            );
8825        });
8826
8827        // Move panel_1 back to the left
8828        panel_1.update_in(cx, |panel_1, window, cx| {
8829            panel_1.set_position(DockPosition::Left, window, cx)
8830        });
8831
8832        workspace.update_in(cx, |workspace, window, cx| {
8833            // Since panel_1 was visible on the right, we open the left dock and make panel_1 active.
8834            let left_dock = workspace.left_dock();
8835            assert!(left_dock.read(cx).is_open());
8836            assert_eq!(
8837                left_dock.read(cx).visible_panel().unwrap().panel_id(),
8838                panel_1.panel_id()
8839            );
8840            assert_eq!(
8841                left_dock.read(cx).active_panel_size(window, cx).unwrap(),
8842                px(1337.)
8843            );
8844            // And the right dock should be closed as it no longer has any panels.
8845            assert!(!workspace.right_dock().read(cx).is_open());
8846
8847            // Now we move panel_1 to the bottom
8848            panel_1.set_position(DockPosition::Bottom, window, cx);
8849        });
8850
8851        workspace.update_in(cx, |workspace, window, cx| {
8852            // Since panel_1 was visible on the left, we close the left dock.
8853            assert!(!workspace.left_dock().read(cx).is_open());
8854            // The bottom dock is sized based on the panel's default size,
8855            // since the panel orientation changed from vertical to horizontal.
8856            let bottom_dock = workspace.bottom_dock();
8857            assert_eq!(
8858                bottom_dock.read(cx).active_panel_size(window, cx).unwrap(),
8859                panel_1.size(window, cx),
8860            );
8861            // Close bottom dock and move panel_1 back to the left.
8862            bottom_dock.update(cx, |bottom_dock, cx| {
8863                bottom_dock.set_open(false, window, cx)
8864            });
8865            panel_1.set_position(DockPosition::Left, window, cx);
8866        });
8867
8868        // Emit activated event on panel 1
8869        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::Activate));
8870
8871        // Now the left dock is open and panel_1 is active and focused.
8872        workspace.update_in(cx, |workspace, window, cx| {
8873            let left_dock = workspace.left_dock();
8874            assert!(left_dock.read(cx).is_open());
8875            assert_eq!(
8876                left_dock.read(cx).visible_panel().unwrap().panel_id(),
8877                panel_1.panel_id(),
8878            );
8879            assert!(panel_1.focus_handle(cx).is_focused(window));
8880        });
8881
8882        // Emit closed event on panel 2, which is not active
8883        panel_2.update(cx, |_, cx| cx.emit(PanelEvent::Close));
8884
8885        // Wo don't close the left dock, because panel_2 wasn't the active panel
8886        workspace.update(cx, |workspace, cx| {
8887            let left_dock = workspace.left_dock();
8888            assert!(left_dock.read(cx).is_open());
8889            assert_eq!(
8890                left_dock.read(cx).visible_panel().unwrap().panel_id(),
8891                panel_1.panel_id(),
8892            );
8893        });
8894
8895        // Emitting a ZoomIn event shows the panel as zoomed.
8896        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::ZoomIn));
8897        workspace.read_with(cx, |workspace, _| {
8898            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
8899            assert_eq!(workspace.zoomed_position, Some(DockPosition::Left));
8900        });
8901
8902        // Move panel to another dock while it is zoomed
8903        panel_1.update_in(cx, |panel, window, cx| {
8904            panel.set_position(DockPosition::Right, window, cx)
8905        });
8906        workspace.read_with(cx, |workspace, _| {
8907            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
8908
8909            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
8910        });
8911
8912        // This is a helper for getting a:
8913        // - valid focus on an element,
8914        // - that isn't a part of the panes and panels system of the Workspace,
8915        // - and doesn't trigger the 'on_focus_lost' API.
8916        let focus_other_view = {
8917            let workspace = workspace.clone();
8918            move |cx: &mut VisualTestContext| {
8919                workspace.update_in(cx, |workspace, window, cx| {
8920                    if let Some(_) = workspace.active_modal::<TestModal>(cx) {
8921                        workspace.toggle_modal(window, cx, TestModal::new);
8922                        workspace.toggle_modal(window, cx, TestModal::new);
8923                    } else {
8924                        workspace.toggle_modal(window, cx, TestModal::new);
8925                    }
8926                })
8927            }
8928        };
8929
8930        // If focus is transferred to another view that's not a panel or another pane, we still show
8931        // the panel as zoomed.
8932        focus_other_view(cx);
8933        workspace.read_with(cx, |workspace, _| {
8934            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
8935            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
8936        });
8937
8938        // If focus is transferred elsewhere in the workspace, the panel is no longer zoomed.
8939        workspace.update_in(cx, |_workspace, window, cx| {
8940            cx.focus_self(window);
8941        });
8942        workspace.read_with(cx, |workspace, _| {
8943            assert_eq!(workspace.zoomed, None);
8944            assert_eq!(workspace.zoomed_position, None);
8945        });
8946
8947        // If focus is transferred again to another view that's not a panel or a pane, we won't
8948        // show the panel as zoomed because it wasn't zoomed before.
8949        focus_other_view(cx);
8950        workspace.read_with(cx, |workspace, _| {
8951            assert_eq!(workspace.zoomed, None);
8952            assert_eq!(workspace.zoomed_position, None);
8953        });
8954
8955        // When the panel is activated, it is zoomed again.
8956        cx.dispatch_action(ToggleRightDock);
8957        workspace.read_with(cx, |workspace, _| {
8958            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
8959            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
8960        });
8961
8962        // Emitting a ZoomOut event unzooms the panel.
8963        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::ZoomOut));
8964        workspace.read_with(cx, |workspace, _| {
8965            assert_eq!(workspace.zoomed, None);
8966            assert_eq!(workspace.zoomed_position, None);
8967        });
8968
8969        // Emit closed event on panel 1, which is active
8970        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::Close));
8971
8972        // Now the left dock is closed, because panel_1 was the active panel
8973        workspace.update(cx, |workspace, cx| {
8974            let right_dock = workspace.right_dock();
8975            assert!(!right_dock.read(cx).is_open());
8976        });
8977    }
8978
8979    #[gpui::test]
8980    async fn test_no_save_prompt_when_multi_buffer_dirty_items_closed(cx: &mut TestAppContext) {
8981        init_test(cx);
8982
8983        let fs = FakeFs::new(cx.background_executor.clone());
8984        let project = Project::test(fs, [], cx).await;
8985        let (workspace, cx) =
8986            cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
8987        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
8988
8989        let dirty_regular_buffer = cx.new(|cx| {
8990            TestItem::new(cx)
8991                .with_dirty(true)
8992                .with_label("1.txt")
8993                .with_project_items(&[dirty_project_item(1, "1.txt", cx)])
8994        });
8995        let dirty_regular_buffer_2 = cx.new(|cx| {
8996            TestItem::new(cx)
8997                .with_dirty(true)
8998                .with_label("2.txt")
8999                .with_project_items(&[dirty_project_item(2, "2.txt", cx)])
9000        });
9001        let dirty_multi_buffer_with_both = cx.new(|cx| {
9002            TestItem::new(cx)
9003                .with_dirty(true)
9004                .with_singleton(false)
9005                .with_label("Fake Project Search")
9006                .with_project_items(&[
9007                    dirty_regular_buffer.read(cx).project_items[0].clone(),
9008                    dirty_regular_buffer_2.read(cx).project_items[0].clone(),
9009                ])
9010        });
9011        let multi_buffer_with_both_files_id = dirty_multi_buffer_with_both.item_id();
9012        workspace.update_in(cx, |workspace, window, cx| {
9013            workspace.add_item(
9014                pane.clone(),
9015                Box::new(dirty_regular_buffer.clone()),
9016                None,
9017                false,
9018                false,
9019                window,
9020                cx,
9021            );
9022            workspace.add_item(
9023                pane.clone(),
9024                Box::new(dirty_regular_buffer_2.clone()),
9025                None,
9026                false,
9027                false,
9028                window,
9029                cx,
9030            );
9031            workspace.add_item(
9032                pane.clone(),
9033                Box::new(dirty_multi_buffer_with_both.clone()),
9034                None,
9035                false,
9036                false,
9037                window,
9038                cx,
9039            );
9040        });
9041
9042        pane.update_in(cx, |pane, window, cx| {
9043            pane.activate_item(2, true, true, window, cx);
9044            assert_eq!(
9045                pane.active_item().unwrap().item_id(),
9046                multi_buffer_with_both_files_id,
9047                "Should select the multi buffer in the pane"
9048            );
9049        });
9050        let close_all_but_multi_buffer_task = pane.update_in(cx, |pane, window, cx| {
9051            pane.close_inactive_items(
9052                &CloseInactiveItems {
9053                    save_intent: Some(SaveIntent::Save),
9054                    close_pinned: true,
9055                },
9056                window,
9057                cx,
9058            )
9059        });
9060        cx.background_executor.run_until_parked();
9061        assert!(!cx.has_pending_prompt());
9062        close_all_but_multi_buffer_task
9063            .await
9064            .expect("Closing all buffers but the multi buffer failed");
9065        pane.update(cx, |pane, cx| {
9066            assert_eq!(dirty_regular_buffer.read(cx).save_count, 1);
9067            assert_eq!(dirty_multi_buffer_with_both.read(cx).save_count, 0);
9068            assert_eq!(dirty_regular_buffer_2.read(cx).save_count, 1);
9069            assert_eq!(pane.items_len(), 1);
9070            assert_eq!(
9071                pane.active_item().unwrap().item_id(),
9072                multi_buffer_with_both_files_id,
9073                "Should have only the multi buffer left in the pane"
9074            );
9075            assert!(
9076                dirty_multi_buffer_with_both.read(cx).is_dirty,
9077                "The multi buffer containing the unsaved buffer should still be dirty"
9078            );
9079        });
9080
9081        dirty_regular_buffer.update(cx, |buffer, cx| {
9082            buffer.project_items[0].update(cx, |pi, _| pi.is_dirty = true)
9083        });
9084
9085        let close_multi_buffer_task = pane.update_in(cx, |pane, window, cx| {
9086            pane.close_active_item(
9087                &CloseActiveItem {
9088                    save_intent: Some(SaveIntent::Close),
9089                    close_pinned: false,
9090                },
9091                window,
9092                cx,
9093            )
9094        });
9095        cx.background_executor.run_until_parked();
9096        assert!(
9097            cx.has_pending_prompt(),
9098            "Dirty multi buffer should prompt a save dialog"
9099        );
9100        cx.simulate_prompt_answer("Save");
9101        cx.background_executor.run_until_parked();
9102        close_multi_buffer_task
9103            .await
9104            .expect("Closing the multi buffer failed");
9105        pane.update(cx, |pane, cx| {
9106            assert_eq!(
9107                dirty_multi_buffer_with_both.read(cx).save_count,
9108                1,
9109                "Multi buffer item should get be saved"
9110            );
9111            // Test impl does not save inner items, so we do not assert them
9112            assert_eq!(
9113                pane.items_len(),
9114                0,
9115                "No more items should be left in the pane"
9116            );
9117            assert!(pane.active_item().is_none());
9118        });
9119    }
9120
9121    #[gpui::test]
9122    async fn test_save_prompt_when_dirty_multi_buffer_closed_with_some_of_its_dirty_items_not_present_in_the_pane(
9123        cx: &mut TestAppContext,
9124    ) {
9125        init_test(cx);
9126
9127        let fs = FakeFs::new(cx.background_executor.clone());
9128        let project = Project::test(fs, [], cx).await;
9129        let (workspace, cx) =
9130            cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
9131        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
9132
9133        let dirty_regular_buffer = cx.new(|cx| {
9134            TestItem::new(cx)
9135                .with_dirty(true)
9136                .with_label("1.txt")
9137                .with_project_items(&[dirty_project_item(1, "1.txt", cx)])
9138        });
9139        let dirty_regular_buffer_2 = cx.new(|cx| {
9140            TestItem::new(cx)
9141                .with_dirty(true)
9142                .with_label("2.txt")
9143                .with_project_items(&[dirty_project_item(2, "2.txt", cx)])
9144        });
9145        let clear_regular_buffer = cx.new(|cx| {
9146            TestItem::new(cx)
9147                .with_label("3.txt")
9148                .with_project_items(&[TestProjectItem::new(3, "3.txt", cx)])
9149        });
9150
9151        let dirty_multi_buffer_with_both = cx.new(|cx| {
9152            TestItem::new(cx)
9153                .with_dirty(true)
9154                .with_singleton(false)
9155                .with_label("Fake Project Search")
9156                .with_project_items(&[
9157                    dirty_regular_buffer.read(cx).project_items[0].clone(),
9158                    dirty_regular_buffer_2.read(cx).project_items[0].clone(),
9159                    clear_regular_buffer.read(cx).project_items[0].clone(),
9160                ])
9161        });
9162        let multi_buffer_with_both_files_id = dirty_multi_buffer_with_both.item_id();
9163        workspace.update_in(cx, |workspace, window, cx| {
9164            workspace.add_item(
9165                pane.clone(),
9166                Box::new(dirty_regular_buffer.clone()),
9167                None,
9168                false,
9169                false,
9170                window,
9171                cx,
9172            );
9173            workspace.add_item(
9174                pane.clone(),
9175                Box::new(dirty_multi_buffer_with_both.clone()),
9176                None,
9177                false,
9178                false,
9179                window,
9180                cx,
9181            );
9182        });
9183
9184        pane.update_in(cx, |pane, window, cx| {
9185            pane.activate_item(1, true, true, window, cx);
9186            assert_eq!(
9187                pane.active_item().unwrap().item_id(),
9188                multi_buffer_with_both_files_id,
9189                "Should select the multi buffer in the pane"
9190            );
9191        });
9192        let _close_multi_buffer_task = pane.update_in(cx, |pane, window, cx| {
9193            pane.close_active_item(
9194                &CloseActiveItem {
9195                    save_intent: None,
9196                    close_pinned: false,
9197                },
9198                window,
9199                cx,
9200            )
9201        });
9202        cx.background_executor.run_until_parked();
9203        assert!(
9204            cx.has_pending_prompt(),
9205            "With one dirty item from the multi buffer not being in the pane, a save prompt should be shown"
9206        );
9207    }
9208
9209    #[gpui::test]
9210    async fn test_no_save_prompt_when_dirty_multi_buffer_closed_with_all_of_its_dirty_items_present_in_the_pane(
9211        cx: &mut TestAppContext,
9212    ) {
9213        init_test(cx);
9214
9215        let fs = FakeFs::new(cx.background_executor.clone());
9216        let project = Project::test(fs, [], cx).await;
9217        let (workspace, cx) =
9218            cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
9219        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
9220
9221        let dirty_regular_buffer = cx.new(|cx| {
9222            TestItem::new(cx)
9223                .with_dirty(true)
9224                .with_label("1.txt")
9225                .with_project_items(&[dirty_project_item(1, "1.txt", cx)])
9226        });
9227        let dirty_regular_buffer_2 = cx.new(|cx| {
9228            TestItem::new(cx)
9229                .with_dirty(true)
9230                .with_label("2.txt")
9231                .with_project_items(&[dirty_project_item(2, "2.txt", cx)])
9232        });
9233        let clear_regular_buffer = cx.new(|cx| {
9234            TestItem::new(cx)
9235                .with_label("3.txt")
9236                .with_project_items(&[TestProjectItem::new(3, "3.txt", cx)])
9237        });
9238
9239        let dirty_multi_buffer = cx.new(|cx| {
9240            TestItem::new(cx)
9241                .with_dirty(true)
9242                .with_singleton(false)
9243                .with_label("Fake Project Search")
9244                .with_project_items(&[
9245                    dirty_regular_buffer.read(cx).project_items[0].clone(),
9246                    dirty_regular_buffer_2.read(cx).project_items[0].clone(),
9247                    clear_regular_buffer.read(cx).project_items[0].clone(),
9248                ])
9249        });
9250        workspace.update_in(cx, |workspace, window, cx| {
9251            workspace.add_item(
9252                pane.clone(),
9253                Box::new(dirty_regular_buffer.clone()),
9254                None,
9255                false,
9256                false,
9257                window,
9258                cx,
9259            );
9260            workspace.add_item(
9261                pane.clone(),
9262                Box::new(dirty_regular_buffer_2.clone()),
9263                None,
9264                false,
9265                false,
9266                window,
9267                cx,
9268            );
9269            workspace.add_item(
9270                pane.clone(),
9271                Box::new(dirty_multi_buffer.clone()),
9272                None,
9273                false,
9274                false,
9275                window,
9276                cx,
9277            );
9278        });
9279
9280        pane.update_in(cx, |pane, window, cx| {
9281            pane.activate_item(2, true, true, window, cx);
9282            assert_eq!(
9283                pane.active_item().unwrap().item_id(),
9284                dirty_multi_buffer.item_id(),
9285                "Should select the multi buffer in the pane"
9286            );
9287        });
9288        let close_multi_buffer_task = pane.update_in(cx, |pane, window, cx| {
9289            pane.close_active_item(
9290                &CloseActiveItem {
9291                    save_intent: None,
9292                    close_pinned: false,
9293                },
9294                window,
9295                cx,
9296            )
9297        });
9298        cx.background_executor.run_until_parked();
9299        assert!(
9300            !cx.has_pending_prompt(),
9301            "All dirty items from the multi buffer are in the pane still, no save prompts should be shown"
9302        );
9303        close_multi_buffer_task
9304            .await
9305            .expect("Closing multi buffer failed");
9306        pane.update(cx, |pane, cx| {
9307            assert_eq!(dirty_regular_buffer.read(cx).save_count, 0);
9308            assert_eq!(dirty_multi_buffer.read(cx).save_count, 0);
9309            assert_eq!(dirty_regular_buffer_2.read(cx).save_count, 0);
9310            assert_eq!(
9311                pane.items()
9312                    .map(|item| item.item_id())
9313                    .sorted()
9314                    .collect::<Vec<_>>(),
9315                vec![
9316                    dirty_regular_buffer.item_id(),
9317                    dirty_regular_buffer_2.item_id(),
9318                ],
9319                "Should have no multi buffer left in the pane"
9320            );
9321            assert!(dirty_regular_buffer.read(cx).is_dirty);
9322            assert!(dirty_regular_buffer_2.read(cx).is_dirty);
9323        });
9324    }
9325
9326    #[gpui::test]
9327    async fn test_move_focused_panel_to_next_position(cx: &mut gpui::TestAppContext) {
9328        init_test(cx);
9329        let fs = FakeFs::new(cx.executor());
9330        let project = Project::test(fs, [], cx).await;
9331        let (workspace, cx) =
9332            cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
9333
9334        // Add a new panel to the right dock, opening the dock and setting the
9335        // focus to the new panel.
9336        let panel = workspace.update_in(cx, |workspace, window, cx| {
9337            let panel = cx.new(|cx| TestPanel::new(DockPosition::Right, cx));
9338            workspace.add_panel(panel.clone(), window, cx);
9339
9340            workspace
9341                .right_dock()
9342                .update(cx, |right_dock, cx| right_dock.set_open(true, window, cx));
9343
9344            workspace.toggle_panel_focus::<TestPanel>(window, cx);
9345
9346            panel
9347        });
9348
9349        // Dispatch the `MoveFocusedPanelToNextPosition` action, moving the
9350        // panel to the next valid position which, in this case, is the left
9351        // dock.
9352        cx.dispatch_action(MoveFocusedPanelToNextPosition);
9353        workspace.update(cx, |workspace, cx| {
9354            assert!(workspace.left_dock().read(cx).is_open());
9355            assert_eq!(panel.read(cx).position, DockPosition::Left);
9356        });
9357
9358        // Dispatch the `MoveFocusedPanelToNextPosition` action, moving the
9359        // panel to the next valid position which, in this case, is the bottom
9360        // dock.
9361        cx.dispatch_action(MoveFocusedPanelToNextPosition);
9362        workspace.update(cx, |workspace, cx| {
9363            assert!(workspace.bottom_dock().read(cx).is_open());
9364            assert_eq!(panel.read(cx).position, DockPosition::Bottom);
9365        });
9366
9367        // Dispatch the `MoveFocusedPanelToNextPosition` action again, this time
9368        // around moving the panel to its initial position, the right dock.
9369        cx.dispatch_action(MoveFocusedPanelToNextPosition);
9370        workspace.update(cx, |workspace, cx| {
9371            assert!(workspace.right_dock().read(cx).is_open());
9372            assert_eq!(panel.read(cx).position, DockPosition::Right);
9373        });
9374
9375        // Remove focus from the panel, ensuring that, if the panel is not
9376        // focused, the `MoveFocusedPanelToNextPosition` action does not update
9377        // the panel's position, so the panel is still in the right dock.
9378        workspace.update_in(cx, |workspace, window, cx| {
9379            workspace.toggle_panel_focus::<TestPanel>(window, cx);
9380        });
9381
9382        cx.dispatch_action(MoveFocusedPanelToNextPosition);
9383        workspace.update(cx, |workspace, cx| {
9384            assert!(workspace.right_dock().read(cx).is_open());
9385            assert_eq!(panel.read(cx).position, DockPosition::Right);
9386        });
9387    }
9388
9389    #[gpui::test]
9390    async fn test_moving_items_create_panes(cx: &mut TestAppContext) {
9391        init_test(cx);
9392
9393        let fs = FakeFs::new(cx.executor());
9394        let project = Project::test(fs, [], cx).await;
9395        let (workspace, cx) =
9396            cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
9397
9398        let item_1 = cx.new(|cx| {
9399            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "first.txt", cx)])
9400        });
9401        workspace.update_in(cx, |workspace, window, cx| {
9402            workspace.add_item_to_active_pane(Box::new(item_1), None, true, window, cx);
9403            workspace.move_item_to_pane_in_direction(
9404                &MoveItemToPaneInDirection {
9405                    direction: SplitDirection::Right,
9406                    focus: true,
9407                },
9408                window,
9409                cx,
9410            );
9411            workspace.move_item_to_pane_at_index(
9412                &MoveItemToPane {
9413                    destination: 3,
9414                    focus: true,
9415                },
9416                window,
9417                cx,
9418            );
9419
9420            assert_eq!(workspace.panes.len(), 1, "No new panes were created");
9421            assert_eq!(
9422                pane_items_paths(&workspace.active_pane, cx),
9423                vec!["first.txt".to_string()],
9424                "Single item was not moved anywhere"
9425            );
9426        });
9427
9428        let item_2 = cx.new(|cx| {
9429            TestItem::new(cx).with_project_items(&[TestProjectItem::new(2, "second.txt", cx)])
9430        });
9431        workspace.update_in(cx, |workspace, window, cx| {
9432            workspace.add_item_to_active_pane(Box::new(item_2), None, true, window, cx);
9433            assert_eq!(
9434                pane_items_paths(&workspace.panes[0], cx),
9435                vec!["first.txt".to_string(), "second.txt".to_string()],
9436            );
9437            workspace.move_item_to_pane_in_direction(
9438                &MoveItemToPaneInDirection {
9439                    direction: SplitDirection::Right,
9440                    focus: true,
9441                },
9442                window,
9443                cx,
9444            );
9445
9446            assert_eq!(workspace.panes.len(), 2, "A new pane should be created");
9447            assert_eq!(
9448                pane_items_paths(&workspace.panes[0], cx),
9449                vec!["first.txt".to_string()],
9450                "After moving, one item should be left in the original pane"
9451            );
9452            assert_eq!(
9453                pane_items_paths(&workspace.panes[1], cx),
9454                vec!["second.txt".to_string()],
9455                "New item should have been moved to the new pane"
9456            );
9457        });
9458
9459        let item_3 = cx.new(|cx| {
9460            TestItem::new(cx).with_project_items(&[TestProjectItem::new(3, "third.txt", cx)])
9461        });
9462        workspace.update_in(cx, |workspace, window, cx| {
9463            let original_pane = workspace.panes[0].clone();
9464            workspace.set_active_pane(&original_pane, window, cx);
9465            workspace.add_item_to_active_pane(Box::new(item_3), None, true, window, cx);
9466            assert_eq!(workspace.panes.len(), 2, "No new panes were created");
9467            assert_eq!(
9468                pane_items_paths(&workspace.active_pane, cx),
9469                vec!["first.txt".to_string(), "third.txt".to_string()],
9470                "New pane should be ready to move one item out"
9471            );
9472
9473            workspace.move_item_to_pane_at_index(
9474                &MoveItemToPane {
9475                    destination: 3,
9476                    focus: true,
9477                },
9478                window,
9479                cx,
9480            );
9481            assert_eq!(workspace.panes.len(), 3, "A new pane should be created");
9482            assert_eq!(
9483                pane_items_paths(&workspace.active_pane, cx),
9484                vec!["first.txt".to_string()],
9485                "After moving, one item should be left in the original pane"
9486            );
9487            assert_eq!(
9488                pane_items_paths(&workspace.panes[1], cx),
9489                vec!["second.txt".to_string()],
9490                "Previously created pane should be unchanged"
9491            );
9492            assert_eq!(
9493                pane_items_paths(&workspace.panes[2], cx),
9494                vec!["third.txt".to_string()],
9495                "New item should have been moved to the new pane"
9496            );
9497        });
9498    }
9499
9500    mod register_project_item_tests {
9501
9502        use super::*;
9503
9504        // View
9505        struct TestPngItemView {
9506            focus_handle: FocusHandle,
9507        }
9508        // Model
9509        struct TestPngItem {}
9510
9511        impl project::ProjectItem for TestPngItem {
9512            fn try_open(
9513                _project: &Entity<Project>,
9514                path: &ProjectPath,
9515                cx: &mut App,
9516            ) -> Option<Task<anyhow::Result<Entity<Self>>>> {
9517                if path.path.extension().unwrap() == "png" {
9518                    Some(cx.spawn(async move |cx| cx.new(|_| TestPngItem {})))
9519                } else {
9520                    None
9521                }
9522            }
9523
9524            fn entry_id(&self, _: &App) -> Option<ProjectEntryId> {
9525                None
9526            }
9527
9528            fn project_path(&self, _: &App) -> Option<ProjectPath> {
9529                None
9530            }
9531
9532            fn is_dirty(&self) -> bool {
9533                false
9534            }
9535        }
9536
9537        impl Item for TestPngItemView {
9538            type Event = ();
9539            fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString {
9540                "".into()
9541            }
9542        }
9543        impl EventEmitter<()> for TestPngItemView {}
9544        impl Focusable for TestPngItemView {
9545            fn focus_handle(&self, _cx: &App) -> FocusHandle {
9546                self.focus_handle.clone()
9547            }
9548        }
9549
9550        impl Render for TestPngItemView {
9551            fn render(
9552                &mut self,
9553                _window: &mut Window,
9554                _cx: &mut Context<Self>,
9555            ) -> impl IntoElement {
9556                Empty
9557            }
9558        }
9559
9560        impl ProjectItem for TestPngItemView {
9561            type Item = TestPngItem;
9562
9563            fn for_project_item(
9564                _project: Entity<Project>,
9565                _pane: Option<&Pane>,
9566                _item: Entity<Self::Item>,
9567                _: &mut Window,
9568                cx: &mut Context<Self>,
9569            ) -> Self
9570            where
9571                Self: Sized,
9572            {
9573                Self {
9574                    focus_handle: cx.focus_handle(),
9575                }
9576            }
9577        }
9578
9579        // View
9580        struct TestIpynbItemView {
9581            focus_handle: FocusHandle,
9582        }
9583        // Model
9584        struct TestIpynbItem {}
9585
9586        impl project::ProjectItem for TestIpynbItem {
9587            fn try_open(
9588                _project: &Entity<Project>,
9589                path: &ProjectPath,
9590                cx: &mut App,
9591            ) -> Option<Task<anyhow::Result<Entity<Self>>>> {
9592                if path.path.extension().unwrap() == "ipynb" {
9593                    Some(cx.spawn(async move |cx| cx.new(|_| TestIpynbItem {})))
9594                } else {
9595                    None
9596                }
9597            }
9598
9599            fn entry_id(&self, _: &App) -> Option<ProjectEntryId> {
9600                None
9601            }
9602
9603            fn project_path(&self, _: &App) -> Option<ProjectPath> {
9604                None
9605            }
9606
9607            fn is_dirty(&self) -> bool {
9608                false
9609            }
9610        }
9611
9612        impl Item for TestIpynbItemView {
9613            type Event = ();
9614            fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString {
9615                "".into()
9616            }
9617        }
9618        impl EventEmitter<()> for TestIpynbItemView {}
9619        impl Focusable for TestIpynbItemView {
9620            fn focus_handle(&self, _cx: &App) -> FocusHandle {
9621                self.focus_handle.clone()
9622            }
9623        }
9624
9625        impl Render for TestIpynbItemView {
9626            fn render(
9627                &mut self,
9628                _window: &mut Window,
9629                _cx: &mut Context<Self>,
9630            ) -> impl IntoElement {
9631                Empty
9632            }
9633        }
9634
9635        impl ProjectItem for TestIpynbItemView {
9636            type Item = TestIpynbItem;
9637
9638            fn for_project_item(
9639                _project: Entity<Project>,
9640                _pane: Option<&Pane>,
9641                _item: Entity<Self::Item>,
9642                _: &mut Window,
9643                cx: &mut Context<Self>,
9644            ) -> Self
9645            where
9646                Self: Sized,
9647            {
9648                Self {
9649                    focus_handle: cx.focus_handle(),
9650                }
9651            }
9652        }
9653
9654        struct TestAlternatePngItemView {
9655            focus_handle: FocusHandle,
9656        }
9657
9658        impl Item for TestAlternatePngItemView {
9659            type Event = ();
9660            fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString {
9661                "".into()
9662            }
9663        }
9664
9665        impl EventEmitter<()> for TestAlternatePngItemView {}
9666        impl Focusable for TestAlternatePngItemView {
9667            fn focus_handle(&self, _cx: &App) -> FocusHandle {
9668                self.focus_handle.clone()
9669            }
9670        }
9671
9672        impl Render for TestAlternatePngItemView {
9673            fn render(
9674                &mut self,
9675                _window: &mut Window,
9676                _cx: &mut Context<Self>,
9677            ) -> impl IntoElement {
9678                Empty
9679            }
9680        }
9681
9682        impl ProjectItem for TestAlternatePngItemView {
9683            type Item = TestPngItem;
9684
9685            fn for_project_item(
9686                _project: Entity<Project>,
9687                _pane: Option<&Pane>,
9688                _item: Entity<Self::Item>,
9689                _: &mut Window,
9690                cx: &mut Context<Self>,
9691            ) -> Self
9692            where
9693                Self: Sized,
9694            {
9695                Self {
9696                    focus_handle: cx.focus_handle(),
9697                }
9698            }
9699        }
9700
9701        #[gpui::test]
9702        async fn test_register_project_item(cx: &mut TestAppContext) {
9703            init_test(cx);
9704
9705            cx.update(|cx| {
9706                register_project_item::<TestPngItemView>(cx);
9707                register_project_item::<TestIpynbItemView>(cx);
9708            });
9709
9710            let fs = FakeFs::new(cx.executor());
9711            fs.insert_tree(
9712                "/root1",
9713                json!({
9714                    "one.png": "BINARYDATAHERE",
9715                    "two.ipynb": "{ totally a notebook }",
9716                    "three.txt": "editing text, sure why not?"
9717                }),
9718            )
9719            .await;
9720
9721            let project = Project::test(fs, ["root1".as_ref()], cx).await;
9722            let (workspace, cx) =
9723                cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
9724
9725            let worktree_id = project.update(cx, |project, cx| {
9726                project.worktrees(cx).next().unwrap().read(cx).id()
9727            });
9728
9729            let handle = workspace
9730                .update_in(cx, |workspace, window, cx| {
9731                    let project_path = (worktree_id, "one.png");
9732                    workspace.open_path(project_path, None, true, window, cx)
9733                })
9734                .await
9735                .unwrap();
9736
9737            // Now we can check if the handle we got back errored or not
9738            assert_eq!(
9739                handle.to_any().entity_type(),
9740                TypeId::of::<TestPngItemView>()
9741            );
9742
9743            let handle = workspace
9744                .update_in(cx, |workspace, window, cx| {
9745                    let project_path = (worktree_id, "two.ipynb");
9746                    workspace.open_path(project_path, None, true, window, cx)
9747                })
9748                .await
9749                .unwrap();
9750
9751            assert_eq!(
9752                handle.to_any().entity_type(),
9753                TypeId::of::<TestIpynbItemView>()
9754            );
9755
9756            let handle = workspace
9757                .update_in(cx, |workspace, window, cx| {
9758                    let project_path = (worktree_id, "three.txt");
9759                    workspace.open_path(project_path, None, true, window, cx)
9760                })
9761                .await;
9762            assert!(handle.is_err());
9763        }
9764
9765        #[gpui::test]
9766        async fn test_register_project_item_two_enter_one_leaves(cx: &mut TestAppContext) {
9767            init_test(cx);
9768
9769            cx.update(|cx| {
9770                register_project_item::<TestPngItemView>(cx);
9771                register_project_item::<TestAlternatePngItemView>(cx);
9772            });
9773
9774            let fs = FakeFs::new(cx.executor());
9775            fs.insert_tree(
9776                "/root1",
9777                json!({
9778                    "one.png": "BINARYDATAHERE",
9779                    "two.ipynb": "{ totally a notebook }",
9780                    "three.txt": "editing text, sure why not?"
9781                }),
9782            )
9783            .await;
9784            let project = Project::test(fs, ["root1".as_ref()], cx).await;
9785            let (workspace, cx) =
9786                cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
9787            let worktree_id = project.update(cx, |project, cx| {
9788                project.worktrees(cx).next().unwrap().read(cx).id()
9789            });
9790
9791            let handle = workspace
9792                .update_in(cx, |workspace, window, cx| {
9793                    let project_path = (worktree_id, "one.png");
9794                    workspace.open_path(project_path, None, true, window, cx)
9795                })
9796                .await
9797                .unwrap();
9798
9799            // This _must_ be the second item registered
9800            assert_eq!(
9801                handle.to_any().entity_type(),
9802                TypeId::of::<TestAlternatePngItemView>()
9803            );
9804
9805            let handle = workspace
9806                .update_in(cx, |workspace, window, cx| {
9807                    let project_path = (worktree_id, "three.txt");
9808                    workspace.open_path(project_path, None, true, window, cx)
9809                })
9810                .await;
9811            assert!(handle.is_err());
9812        }
9813    }
9814
9815    fn pane_items_paths(pane: &Entity<Pane>, cx: &App) -> Vec<String> {
9816        pane.read(cx)
9817            .items()
9818            .flat_map(|item| {
9819                item.project_paths(cx)
9820                    .into_iter()
9821                    .map(|path| path.path.to_string_lossy().to_string())
9822            })
9823            .collect()
9824    }
9825
9826    pub fn init_test(cx: &mut TestAppContext) {
9827        cx.update(|cx| {
9828            let settings_store = SettingsStore::test(cx);
9829            cx.set_global(settings_store);
9830            theme::init(theme::LoadThemes::JustBase, cx);
9831            language::init(cx);
9832            crate::init_settings(cx);
9833            Project::init_settings(cx);
9834        });
9835    }
9836
9837    fn dirty_project_item(id: u64, path: &str, cx: &mut App) -> Entity<TestProjectItem> {
9838        let item = TestProjectItem::new(id, path, cx);
9839        item.update(cx, |item, _| {
9840            item.is_dirty = true;
9841        });
9842        item
9843    }
9844}