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