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        })
7765        .await
7766        .unwrap();
7767        assert_eq!(cx.window_title().as_deref(), Some("root1 — one.txt"));
7768        project.update(cx, |project, cx| {
7769            assert_eq!(
7770                project.active_entry(),
7771                project
7772                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
7773                    .map(|e| e.id)
7774            );
7775        });
7776
7777        // Add a project folder
7778        project
7779            .update(cx, |project, cx| {
7780                project.find_or_create_worktree("root2", true, cx)
7781            })
7782            .await
7783            .unwrap();
7784        assert_eq!(cx.window_title().as_deref(), Some("root1, root2 — one.txt"));
7785
7786        // Remove a project folder
7787        project.update(cx, |project, cx| project.remove_worktree(worktree_id, cx));
7788        assert_eq!(cx.window_title().as_deref(), Some("root2 — one.txt"));
7789    }
7790
7791    #[gpui::test]
7792    async fn test_close_window(cx: &mut TestAppContext) {
7793        init_test(cx);
7794
7795        let fs = FakeFs::new(cx.executor());
7796        fs.insert_tree("/root", json!({ "one": "" })).await;
7797
7798        let project = Project::test(fs, ["root".as_ref()], cx).await;
7799        let (workspace, cx) =
7800            cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
7801
7802        // When there are no dirty items, there's nothing to do.
7803        let item1 = cx.new(TestItem::new);
7804        workspace.update_in(cx, |w, window, cx| {
7805            w.add_item_to_active_pane(Box::new(item1.clone()), None, true, window, cx)
7806        });
7807        let task = workspace.update_in(cx, |w, window, cx| {
7808            w.prepare_to_close(CloseIntent::CloseWindow, window, cx)
7809        });
7810        assert!(task.await.unwrap());
7811
7812        // When there are dirty untitled items, prompt to save each one. If the user
7813        // cancels any prompt, then abort.
7814        let item2 = cx.new(|cx| TestItem::new(cx).with_dirty(true));
7815        let item3 = cx.new(|cx| {
7816            TestItem::new(cx)
7817                .with_dirty(true)
7818                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
7819        });
7820        workspace.update_in(cx, |w, window, cx| {
7821            w.add_item_to_active_pane(Box::new(item2.clone()), None, true, window, cx);
7822            w.add_item_to_active_pane(Box::new(item3.clone()), None, true, window, cx);
7823        });
7824        let task = workspace.update_in(cx, |w, window, cx| {
7825            w.prepare_to_close(CloseIntent::CloseWindow, window, cx)
7826        });
7827        cx.executor().run_until_parked();
7828        cx.simulate_prompt_answer("Cancel"); // cancel save all
7829        cx.executor().run_until_parked();
7830        assert!(!cx.has_pending_prompt());
7831        assert!(!task.await.unwrap());
7832    }
7833
7834    #[gpui::test]
7835    async fn test_close_window_with_serializable_items(cx: &mut TestAppContext) {
7836        init_test(cx);
7837
7838        // Register TestItem as a serializable item
7839        cx.update(|cx| {
7840            register_serializable_item::<TestItem>(cx);
7841        });
7842
7843        let fs = FakeFs::new(cx.executor());
7844        fs.insert_tree("/root", json!({ "one": "" })).await;
7845
7846        let project = Project::test(fs, ["root".as_ref()], cx).await;
7847        let (workspace, cx) =
7848            cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
7849
7850        // When there are dirty untitled items, but they can serialize, then there is no prompt.
7851        let item1 = cx.new(|cx| {
7852            TestItem::new(cx)
7853                .with_dirty(true)
7854                .with_serialize(|| Some(Task::ready(Ok(()))))
7855        });
7856        let item2 = cx.new(|cx| {
7857            TestItem::new(cx)
7858                .with_dirty(true)
7859                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
7860                .with_serialize(|| Some(Task::ready(Ok(()))))
7861        });
7862        workspace.update_in(cx, |w, window, cx| {
7863            w.add_item_to_active_pane(Box::new(item1.clone()), None, true, window, cx);
7864            w.add_item_to_active_pane(Box::new(item2.clone()), None, true, window, cx);
7865        });
7866        let task = workspace.update_in(cx, |w, window, cx| {
7867            w.prepare_to_close(CloseIntent::CloseWindow, window, cx)
7868        });
7869        assert!(task.await.unwrap());
7870    }
7871
7872    #[gpui::test]
7873    async fn test_close_pane_items(cx: &mut TestAppContext) {
7874        init_test(cx);
7875
7876        let fs = FakeFs::new(cx.executor());
7877
7878        let project = Project::test(fs, None, cx).await;
7879        let (workspace, cx) =
7880            cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
7881
7882        let item1 = cx.new(|cx| {
7883            TestItem::new(cx)
7884                .with_dirty(true)
7885                .with_project_items(&[dirty_project_item(1, "1.txt", cx)])
7886        });
7887        let item2 = cx.new(|cx| {
7888            TestItem::new(cx)
7889                .with_dirty(true)
7890                .with_conflict(true)
7891                .with_project_items(&[dirty_project_item(2, "2.txt", cx)])
7892        });
7893        let item3 = cx.new(|cx| {
7894            TestItem::new(cx)
7895                .with_dirty(true)
7896                .with_conflict(true)
7897                .with_project_items(&[dirty_project_item(3, "3.txt", cx)])
7898        });
7899        let item4 = cx.new(|cx| {
7900            TestItem::new(cx).with_dirty(true).with_project_items(&[{
7901                let project_item = TestProjectItem::new_untitled(cx);
7902                project_item.update(cx, |project_item, _| project_item.is_dirty = true);
7903                project_item
7904            }])
7905        });
7906        let pane = workspace.update_in(cx, |workspace, window, cx| {
7907            workspace.add_item_to_active_pane(Box::new(item1.clone()), None, true, window, cx);
7908            workspace.add_item_to_active_pane(Box::new(item2.clone()), None, true, window, cx);
7909            workspace.add_item_to_active_pane(Box::new(item3.clone()), None, true, window, cx);
7910            workspace.add_item_to_active_pane(Box::new(item4.clone()), None, true, window, cx);
7911            workspace.active_pane().clone()
7912        });
7913
7914        let close_items = pane.update_in(cx, |pane, window, cx| {
7915            pane.activate_item(1, true, true, window, cx);
7916            assert_eq!(pane.active_item().unwrap().item_id(), item2.item_id());
7917            let item1_id = item1.item_id();
7918            let item3_id = item3.item_id();
7919            let item4_id = item4.item_id();
7920            pane.close_items(window, cx, SaveIntent::Close, move |id| {
7921                [item1_id, item3_id, item4_id].contains(&id)
7922            })
7923        });
7924        cx.executor().run_until_parked();
7925
7926        assert!(cx.has_pending_prompt());
7927        cx.simulate_prompt_answer("Save all");
7928
7929        cx.executor().run_until_parked();
7930
7931        // Item 1 is saved. There's a prompt to save item 3.
7932        pane.update(cx, |pane, cx| {
7933            assert_eq!(item1.read(cx).save_count, 1);
7934            assert_eq!(item1.read(cx).save_as_count, 0);
7935            assert_eq!(item1.read(cx).reload_count, 0);
7936            assert_eq!(pane.items_len(), 3);
7937            assert_eq!(pane.active_item().unwrap().item_id(), item3.item_id());
7938        });
7939        assert!(cx.has_pending_prompt());
7940
7941        // Cancel saving item 3.
7942        cx.simulate_prompt_answer("Discard");
7943        cx.executor().run_until_parked();
7944
7945        // Item 3 is reloaded. There's a prompt to save item 4.
7946        pane.update(cx, |pane, cx| {
7947            assert_eq!(item3.read(cx).save_count, 0);
7948            assert_eq!(item3.read(cx).save_as_count, 0);
7949            assert_eq!(item3.read(cx).reload_count, 1);
7950            assert_eq!(pane.items_len(), 2);
7951            assert_eq!(pane.active_item().unwrap().item_id(), item4.item_id());
7952        });
7953
7954        // There's a prompt for a path for item 4.
7955        cx.simulate_new_path_selection(|_| Some(Default::default()));
7956        close_items.await.unwrap();
7957
7958        // The requested items are closed.
7959        pane.update(cx, |pane, cx| {
7960            assert_eq!(item4.read(cx).save_count, 0);
7961            assert_eq!(item4.read(cx).save_as_count, 1);
7962            assert_eq!(item4.read(cx).reload_count, 0);
7963            assert_eq!(pane.items_len(), 1);
7964            assert_eq!(pane.active_item().unwrap().item_id(), item2.item_id());
7965        });
7966    }
7967
7968    #[gpui::test]
7969    async fn test_prompting_to_save_only_on_last_item_for_entry(cx: &mut TestAppContext) {
7970        init_test(cx);
7971
7972        let fs = FakeFs::new(cx.executor());
7973        let project = Project::test(fs, [], cx).await;
7974        let (workspace, cx) =
7975            cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
7976
7977        // Create several workspace items with single project entries, and two
7978        // workspace items with multiple project entries.
7979        let single_entry_items = (0..=4)
7980            .map(|project_entry_id| {
7981                cx.new(|cx| {
7982                    TestItem::new(cx)
7983                        .with_dirty(true)
7984                        .with_project_items(&[dirty_project_item(
7985                            project_entry_id,
7986                            &format!("{project_entry_id}.txt"),
7987                            cx,
7988                        )])
7989                })
7990            })
7991            .collect::<Vec<_>>();
7992        let item_2_3 = cx.new(|cx| {
7993            TestItem::new(cx)
7994                .with_dirty(true)
7995                .with_singleton(false)
7996                .with_project_items(&[
7997                    single_entry_items[2].read(cx).project_items[0].clone(),
7998                    single_entry_items[3].read(cx).project_items[0].clone(),
7999                ])
8000        });
8001        let item_3_4 = cx.new(|cx| {
8002            TestItem::new(cx)
8003                .with_dirty(true)
8004                .with_singleton(false)
8005                .with_project_items(&[
8006                    single_entry_items[3].read(cx).project_items[0].clone(),
8007                    single_entry_items[4].read(cx).project_items[0].clone(),
8008                ])
8009        });
8010
8011        // Create two panes that contain the following project entries:
8012        //   left pane:
8013        //     multi-entry items:   (2, 3)
8014        //     single-entry items:  0, 2, 3, 4
8015        //   right pane:
8016        //     single-entry items:  4, 1
8017        //     multi-entry items:   (3, 4)
8018        let (left_pane, right_pane) = workspace.update_in(cx, |workspace, window, cx| {
8019            let left_pane = workspace.active_pane().clone();
8020            workspace.add_item_to_active_pane(Box::new(item_2_3.clone()), None, true, window, cx);
8021            workspace.add_item_to_active_pane(
8022                single_entry_items[0].boxed_clone(),
8023                None,
8024                true,
8025                window,
8026                cx,
8027            );
8028            workspace.add_item_to_active_pane(
8029                single_entry_items[2].boxed_clone(),
8030                None,
8031                true,
8032                window,
8033                cx,
8034            );
8035            workspace.add_item_to_active_pane(
8036                single_entry_items[3].boxed_clone(),
8037                None,
8038                true,
8039                window,
8040                cx,
8041            );
8042            workspace.add_item_to_active_pane(
8043                single_entry_items[4].boxed_clone(),
8044                None,
8045                true,
8046                window,
8047                cx,
8048            );
8049
8050            let right_pane = workspace
8051                .split_and_clone(left_pane.clone(), SplitDirection::Right, window, cx)
8052                .unwrap();
8053
8054            right_pane.update(cx, |pane, cx| {
8055                pane.add_item(
8056                    single_entry_items[1].boxed_clone(),
8057                    true,
8058                    true,
8059                    None,
8060                    window,
8061                    cx,
8062                );
8063                pane.add_item(Box::new(item_3_4.clone()), true, true, None, window, cx);
8064            });
8065
8066            (left_pane, right_pane)
8067        });
8068
8069        cx.focus(&right_pane);
8070
8071        let mut close = right_pane.update_in(cx, |pane, window, cx| {
8072            pane.close_all_items(&CloseAllItems::default(), window, cx)
8073                .unwrap()
8074        });
8075        cx.executor().run_until_parked();
8076
8077        let msg = cx.pending_prompt().unwrap().0;
8078        assert!(msg.contains("1.txt"));
8079        assert!(!msg.contains("2.txt"));
8080        assert!(!msg.contains("3.txt"));
8081        assert!(!msg.contains("4.txt"));
8082
8083        cx.simulate_prompt_answer("Cancel");
8084        close.await;
8085
8086        left_pane
8087            .update_in(cx, |left_pane, window, cx| {
8088                left_pane.close_item_by_id(
8089                    single_entry_items[3].entity_id(),
8090                    SaveIntent::Skip,
8091                    window,
8092                    cx,
8093                )
8094            })
8095            .await
8096            .unwrap();
8097
8098        close = right_pane.update_in(cx, |pane, window, cx| {
8099            pane.close_all_items(&CloseAllItems::default(), window, cx)
8100                .unwrap()
8101        });
8102        cx.executor().run_until_parked();
8103
8104        let details = cx.pending_prompt().unwrap().1;
8105        assert!(details.contains("1.txt"));
8106        assert!(!details.contains("2.txt"));
8107        assert!(details.contains("3.txt"));
8108        // ideally this assertion could be made, but today we can only
8109        // save whole items not project items, so the orphaned item 3 causes
8110        // 4 to be saved too.
8111        // assert!(!details.contains("4.txt"));
8112
8113        cx.simulate_prompt_answer("Save all");
8114
8115        cx.executor().run_until_parked();
8116        close.await;
8117        right_pane.read_with(cx, |pane, _| {
8118            assert_eq!(pane.items_len(), 0);
8119        });
8120    }
8121
8122    #[gpui::test]
8123    async fn test_autosave(cx: &mut gpui::TestAppContext) {
8124        init_test(cx);
8125
8126        let fs = FakeFs::new(cx.executor());
8127        let project = Project::test(fs, [], cx).await;
8128        let (workspace, cx) =
8129            cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
8130        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
8131
8132        let item = cx.new(|cx| {
8133            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
8134        });
8135        let item_id = item.entity_id();
8136        workspace.update_in(cx, |workspace, window, cx| {
8137            workspace.add_item_to_active_pane(Box::new(item.clone()), None, true, window, cx);
8138        });
8139
8140        // Autosave on window change.
8141        item.update(cx, |item, cx| {
8142            SettingsStore::update_global(cx, |settings, cx| {
8143                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
8144                    settings.autosave = Some(AutosaveSetting::OnWindowChange);
8145                })
8146            });
8147            item.is_dirty = true;
8148        });
8149
8150        // Deactivating the window saves the file.
8151        cx.deactivate_window();
8152        item.read_with(cx, |item, _| assert_eq!(item.save_count, 1));
8153
8154        // Re-activating the window doesn't save the file.
8155        cx.update(|window, _| window.activate_window());
8156        cx.executor().run_until_parked();
8157        item.read_with(cx, |item, _| assert_eq!(item.save_count, 1));
8158
8159        // Autosave on focus change.
8160        item.update_in(cx, |item, window, cx| {
8161            cx.focus_self(window);
8162            SettingsStore::update_global(cx, |settings, cx| {
8163                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
8164                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
8165                })
8166            });
8167            item.is_dirty = true;
8168        });
8169
8170        // Blurring the item saves the file.
8171        item.update_in(cx, |_, window, _| window.blur());
8172        cx.executor().run_until_parked();
8173        item.read_with(cx, |item, _| assert_eq!(item.save_count, 2));
8174
8175        // Deactivating the window still saves the file.
8176        item.update_in(cx, |item, window, cx| {
8177            cx.focus_self(window);
8178            item.is_dirty = true;
8179        });
8180        cx.deactivate_window();
8181        item.update(cx, |item, _| assert_eq!(item.save_count, 3));
8182
8183        // Autosave after delay.
8184        item.update(cx, |item, cx| {
8185            SettingsStore::update_global(cx, |settings, cx| {
8186                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
8187                    settings.autosave = Some(AutosaveSetting::AfterDelay { milliseconds: 500 });
8188                })
8189            });
8190            item.is_dirty = true;
8191            cx.emit(ItemEvent::Edit);
8192        });
8193
8194        // Delay hasn't fully expired, so the file is still dirty and unsaved.
8195        cx.executor().advance_clock(Duration::from_millis(250));
8196        item.read_with(cx, |item, _| assert_eq!(item.save_count, 3));
8197
8198        // After delay expires, the file is saved.
8199        cx.executor().advance_clock(Duration::from_millis(250));
8200        item.read_with(cx, |item, _| assert_eq!(item.save_count, 4));
8201
8202        // Autosave on focus change, ensuring closing the tab counts as such.
8203        item.update(cx, |item, cx| {
8204            SettingsStore::update_global(cx, |settings, cx| {
8205                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
8206                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
8207                })
8208            });
8209            item.is_dirty = true;
8210            for project_item in &mut item.project_items {
8211                project_item.update(cx, |project_item, _| project_item.is_dirty = true);
8212            }
8213        });
8214
8215        pane.update_in(cx, |pane, window, cx| {
8216            pane.close_items(window, cx, SaveIntent::Close, move |id| id == item_id)
8217        })
8218        .await
8219        .unwrap();
8220        assert!(!cx.has_pending_prompt());
8221        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
8222
8223        // Add the item again, ensuring autosave is prevented if the underlying file has been deleted.
8224        workspace.update_in(cx, |workspace, window, cx| {
8225            workspace.add_item_to_active_pane(Box::new(item.clone()), None, true, window, cx);
8226        });
8227        item.update_in(cx, |item, window, cx| {
8228            item.project_items[0].update(cx, |item, _| {
8229                item.entry_id = None;
8230            });
8231            item.is_dirty = true;
8232            window.blur();
8233        });
8234        cx.run_until_parked();
8235        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
8236
8237        // Ensure autosave is prevented for deleted files also when closing the buffer.
8238        let _close_items = pane.update_in(cx, |pane, window, cx| {
8239            pane.close_items(window, cx, SaveIntent::Close, move |id| id == item_id)
8240        });
8241        cx.run_until_parked();
8242        assert!(cx.has_pending_prompt());
8243        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
8244    }
8245
8246    #[gpui::test]
8247    async fn test_pane_navigation(cx: &mut gpui::TestAppContext) {
8248        init_test(cx);
8249
8250        let fs = FakeFs::new(cx.executor());
8251
8252        let project = Project::test(fs, [], cx).await;
8253        let (workspace, cx) =
8254            cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
8255
8256        let item = cx.new(|cx| {
8257            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
8258        });
8259        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
8260        let toolbar = pane.read_with(cx, |pane, _| pane.toolbar().clone());
8261        let toolbar_notify_count = Rc::new(RefCell::new(0));
8262
8263        workspace.update_in(cx, |workspace, window, cx| {
8264            workspace.add_item_to_active_pane(Box::new(item.clone()), None, true, window, cx);
8265            let toolbar_notification_count = toolbar_notify_count.clone();
8266            cx.observe_in(&toolbar, window, move |_, _, _, _| {
8267                *toolbar_notification_count.borrow_mut() += 1
8268            })
8269            .detach();
8270        });
8271
8272        pane.read_with(cx, |pane, _| {
8273            assert!(!pane.can_navigate_backward());
8274            assert!(!pane.can_navigate_forward());
8275        });
8276
8277        item.update_in(cx, |item, _, cx| {
8278            item.set_state("one".to_string(), cx);
8279        });
8280
8281        // Toolbar must be notified to re-render the navigation buttons
8282        assert_eq!(*toolbar_notify_count.borrow(), 1);
8283
8284        pane.read_with(cx, |pane, _| {
8285            assert!(pane.can_navigate_backward());
8286            assert!(!pane.can_navigate_forward());
8287        });
8288
8289        workspace
8290            .update_in(cx, |workspace, window, cx| {
8291                workspace.go_back(pane.downgrade(), window, cx)
8292            })
8293            .await
8294            .unwrap();
8295
8296        assert_eq!(*toolbar_notify_count.borrow(), 2);
8297        pane.read_with(cx, |pane, _| {
8298            assert!(!pane.can_navigate_backward());
8299            assert!(pane.can_navigate_forward());
8300        });
8301    }
8302
8303    #[gpui::test]
8304    async fn test_toggle_docks_and_panels(cx: &mut gpui::TestAppContext) {
8305        init_test(cx);
8306        let fs = FakeFs::new(cx.executor());
8307
8308        let project = Project::test(fs, [], cx).await;
8309        let (workspace, cx) =
8310            cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
8311
8312        let panel = workspace.update_in(cx, |workspace, window, cx| {
8313            let panel = cx.new(|cx| TestPanel::new(DockPosition::Right, cx));
8314            workspace.add_panel(panel.clone(), window, cx);
8315
8316            workspace
8317                .right_dock()
8318                .update(cx, |right_dock, cx| right_dock.set_open(true, window, cx));
8319
8320            panel
8321        });
8322
8323        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
8324        pane.update_in(cx, |pane, window, cx| {
8325            let item = cx.new(TestItem::new);
8326            pane.add_item(Box::new(item), true, true, None, window, cx);
8327        });
8328
8329        // Transfer focus from center to panel
8330        workspace.update_in(cx, |workspace, window, cx| {
8331            workspace.toggle_panel_focus::<TestPanel>(window, cx);
8332        });
8333
8334        workspace.update_in(cx, |workspace, window, cx| {
8335            assert!(workspace.right_dock().read(cx).is_open());
8336            assert!(!panel.is_zoomed(window, cx));
8337            assert!(panel.read(cx).focus_handle(cx).contains_focused(window, cx));
8338        });
8339
8340        // Transfer focus from panel to center
8341        workspace.update_in(cx, |workspace, window, cx| {
8342            workspace.toggle_panel_focus::<TestPanel>(window, cx);
8343        });
8344
8345        workspace.update_in(cx, |workspace, window, cx| {
8346            assert!(workspace.right_dock().read(cx).is_open());
8347            assert!(!panel.is_zoomed(window, cx));
8348            assert!(!panel.read(cx).focus_handle(cx).contains_focused(window, cx));
8349        });
8350
8351        // Close the dock
8352        workspace.update_in(cx, |workspace, window, cx| {
8353            workspace.toggle_dock(DockPosition::Right, window, cx);
8354        });
8355
8356        workspace.update_in(cx, |workspace, window, cx| {
8357            assert!(!workspace.right_dock().read(cx).is_open());
8358            assert!(!panel.is_zoomed(window, cx));
8359            assert!(!panel.read(cx).focus_handle(cx).contains_focused(window, cx));
8360        });
8361
8362        // Open the dock
8363        workspace.update_in(cx, |workspace, window, cx| {
8364            workspace.toggle_dock(DockPosition::Right, window, cx);
8365        });
8366
8367        workspace.update_in(cx, |workspace, window, cx| {
8368            assert!(workspace.right_dock().read(cx).is_open());
8369            assert!(!panel.is_zoomed(window, cx));
8370            assert!(panel.read(cx).focus_handle(cx).contains_focused(window, cx));
8371        });
8372
8373        // Focus and zoom panel
8374        panel.update_in(cx, |panel, window, cx| {
8375            cx.focus_self(window);
8376            panel.set_zoomed(true, window, cx)
8377        });
8378
8379        workspace.update_in(cx, |workspace, window, cx| {
8380            assert!(workspace.right_dock().read(cx).is_open());
8381            assert!(panel.is_zoomed(window, cx));
8382            assert!(panel.read(cx).focus_handle(cx).contains_focused(window, cx));
8383        });
8384
8385        // Transfer focus to the center closes the dock
8386        workspace.update_in(cx, |workspace, window, cx| {
8387            workspace.toggle_panel_focus::<TestPanel>(window, cx);
8388        });
8389
8390        workspace.update_in(cx, |workspace, window, cx| {
8391            assert!(!workspace.right_dock().read(cx).is_open());
8392            assert!(panel.is_zoomed(window, cx));
8393            assert!(!panel.read(cx).focus_handle(cx).contains_focused(window, cx));
8394        });
8395
8396        // Transferring focus back to the panel keeps it zoomed
8397        workspace.update_in(cx, |workspace, window, cx| {
8398            workspace.toggle_panel_focus::<TestPanel>(window, cx);
8399        });
8400
8401        workspace.update_in(cx, |workspace, window, cx| {
8402            assert!(workspace.right_dock().read(cx).is_open());
8403            assert!(panel.is_zoomed(window, cx));
8404            assert!(panel.read(cx).focus_handle(cx).contains_focused(window, cx));
8405        });
8406
8407        // Close the dock while it is zoomed
8408        workspace.update_in(cx, |workspace, window, cx| {
8409            workspace.toggle_dock(DockPosition::Right, window, cx)
8410        });
8411
8412        workspace.update_in(cx, |workspace, window, cx| {
8413            assert!(!workspace.right_dock().read(cx).is_open());
8414            assert!(panel.is_zoomed(window, cx));
8415            assert!(workspace.zoomed.is_none());
8416            assert!(!panel.read(cx).focus_handle(cx).contains_focused(window, cx));
8417        });
8418
8419        // Opening the dock, when it's zoomed, retains focus
8420        workspace.update_in(cx, |workspace, window, cx| {
8421            workspace.toggle_dock(DockPosition::Right, window, cx)
8422        });
8423
8424        workspace.update_in(cx, |workspace, window, cx| {
8425            assert!(workspace.right_dock().read(cx).is_open());
8426            assert!(panel.is_zoomed(window, cx));
8427            assert!(workspace.zoomed.is_some());
8428            assert!(panel.read(cx).focus_handle(cx).contains_focused(window, cx));
8429        });
8430
8431        // Unzoom and close the panel, zoom the active pane.
8432        panel.update_in(cx, |panel, window, cx| panel.set_zoomed(false, window, cx));
8433        workspace.update_in(cx, |workspace, window, cx| {
8434            workspace.toggle_dock(DockPosition::Right, window, cx)
8435        });
8436        pane.update_in(cx, |pane, window, cx| {
8437            pane.toggle_zoom(&Default::default(), window, cx)
8438        });
8439
8440        // Opening a dock unzooms the pane.
8441        workspace.update_in(cx, |workspace, window, cx| {
8442            workspace.toggle_dock(DockPosition::Right, window, cx)
8443        });
8444        workspace.update_in(cx, |workspace, window, cx| {
8445            let pane = pane.read(cx);
8446            assert!(!pane.is_zoomed());
8447            assert!(!pane.focus_handle(cx).is_focused(window));
8448            assert!(workspace.right_dock().read(cx).is_open());
8449            assert!(workspace.zoomed.is_none());
8450        });
8451    }
8452
8453    #[gpui::test]
8454    async fn test_join_pane_into_next(cx: &mut gpui::TestAppContext) {
8455        init_test(cx);
8456
8457        let fs = FakeFs::new(cx.executor());
8458
8459        let project = Project::test(fs, None, cx).await;
8460        let (workspace, cx) =
8461            cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
8462
8463        // Let's arrange the panes like this:
8464        //
8465        // +-----------------------+
8466        // |         top           |
8467        // +------+--------+-------+
8468        // | left | center | right |
8469        // +------+--------+-------+
8470        // |        bottom         |
8471        // +-----------------------+
8472
8473        let top_item = cx.new(|cx| {
8474            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "top.txt", cx)])
8475        });
8476        let bottom_item = cx.new(|cx| {
8477            TestItem::new(cx).with_project_items(&[TestProjectItem::new(2, "bottom.txt", cx)])
8478        });
8479        let left_item = cx.new(|cx| {
8480            TestItem::new(cx).with_project_items(&[TestProjectItem::new(3, "left.txt", cx)])
8481        });
8482        let right_item = cx.new(|cx| {
8483            TestItem::new(cx).with_project_items(&[TestProjectItem::new(4, "right.txt", cx)])
8484        });
8485        let center_item = cx.new(|cx| {
8486            TestItem::new(cx).with_project_items(&[TestProjectItem::new(5, "center.txt", cx)])
8487        });
8488
8489        let top_pane_id = workspace.update_in(cx, |workspace, window, cx| {
8490            let top_pane_id = workspace.active_pane().entity_id();
8491            workspace.add_item_to_active_pane(Box::new(top_item.clone()), None, false, window, cx);
8492            workspace.split_pane(
8493                workspace.active_pane().clone(),
8494                SplitDirection::Down,
8495                window,
8496                cx,
8497            );
8498            top_pane_id
8499        });
8500        let bottom_pane_id = workspace.update_in(cx, |workspace, window, cx| {
8501            let bottom_pane_id = workspace.active_pane().entity_id();
8502            workspace.add_item_to_active_pane(
8503                Box::new(bottom_item.clone()),
8504                None,
8505                false,
8506                window,
8507                cx,
8508            );
8509            workspace.split_pane(
8510                workspace.active_pane().clone(),
8511                SplitDirection::Up,
8512                window,
8513                cx,
8514            );
8515            bottom_pane_id
8516        });
8517        let left_pane_id = workspace.update_in(cx, |workspace, window, cx| {
8518            let left_pane_id = workspace.active_pane().entity_id();
8519            workspace.add_item_to_active_pane(Box::new(left_item.clone()), None, false, window, cx);
8520            workspace.split_pane(
8521                workspace.active_pane().clone(),
8522                SplitDirection::Right,
8523                window,
8524                cx,
8525            );
8526            left_pane_id
8527        });
8528        let right_pane_id = workspace.update_in(cx, |workspace, window, cx| {
8529            let right_pane_id = workspace.active_pane().entity_id();
8530            workspace.add_item_to_active_pane(
8531                Box::new(right_item.clone()),
8532                None,
8533                false,
8534                window,
8535                cx,
8536            );
8537            workspace.split_pane(
8538                workspace.active_pane().clone(),
8539                SplitDirection::Left,
8540                window,
8541                cx,
8542            );
8543            right_pane_id
8544        });
8545        let center_pane_id = workspace.update_in(cx, |workspace, window, cx| {
8546            let center_pane_id = workspace.active_pane().entity_id();
8547            workspace.add_item_to_active_pane(
8548                Box::new(center_item.clone()),
8549                None,
8550                false,
8551                window,
8552                cx,
8553            );
8554            center_pane_id
8555        });
8556        cx.executor().run_until_parked();
8557
8558        workspace.update_in(cx, |workspace, window, cx| {
8559            assert_eq!(center_pane_id, workspace.active_pane().entity_id());
8560
8561            // Join into next from center pane into right
8562            workspace.join_pane_into_next(workspace.active_pane().clone(), window, cx);
8563        });
8564
8565        workspace.update_in(cx, |workspace, window, cx| {
8566            let active_pane = workspace.active_pane();
8567            assert_eq!(right_pane_id, active_pane.entity_id());
8568            assert_eq!(2, active_pane.read(cx).items_len());
8569            let item_ids_in_pane =
8570                HashSet::from_iter(active_pane.read(cx).items().map(|item| item.item_id()));
8571            assert!(item_ids_in_pane.contains(&center_item.item_id()));
8572            assert!(item_ids_in_pane.contains(&right_item.item_id()));
8573
8574            // Join into next from right pane into bottom
8575            workspace.join_pane_into_next(workspace.active_pane().clone(), window, cx);
8576        });
8577
8578        workspace.update_in(cx, |workspace, window, cx| {
8579            let active_pane = workspace.active_pane();
8580            assert_eq!(bottom_pane_id, active_pane.entity_id());
8581            assert_eq!(3, active_pane.read(cx).items_len());
8582            let item_ids_in_pane =
8583                HashSet::from_iter(active_pane.read(cx).items().map(|item| item.item_id()));
8584            assert!(item_ids_in_pane.contains(&center_item.item_id()));
8585            assert!(item_ids_in_pane.contains(&right_item.item_id()));
8586            assert!(item_ids_in_pane.contains(&bottom_item.item_id()));
8587
8588            // Join into next from bottom pane into left
8589            workspace.join_pane_into_next(workspace.active_pane().clone(), window, cx);
8590        });
8591
8592        workspace.update_in(cx, |workspace, window, cx| {
8593            let active_pane = workspace.active_pane();
8594            assert_eq!(left_pane_id, active_pane.entity_id());
8595            assert_eq!(4, active_pane.read(cx).items_len());
8596            let item_ids_in_pane =
8597                HashSet::from_iter(active_pane.read(cx).items().map(|item| item.item_id()));
8598            assert!(item_ids_in_pane.contains(&center_item.item_id()));
8599            assert!(item_ids_in_pane.contains(&right_item.item_id()));
8600            assert!(item_ids_in_pane.contains(&bottom_item.item_id()));
8601            assert!(item_ids_in_pane.contains(&left_item.item_id()));
8602
8603            // Join into next from left pane into top
8604            workspace.join_pane_into_next(workspace.active_pane().clone(), window, cx);
8605        });
8606
8607        workspace.update_in(cx, |workspace, window, cx| {
8608            let active_pane = workspace.active_pane();
8609            assert_eq!(top_pane_id, active_pane.entity_id());
8610            assert_eq!(5, active_pane.read(cx).items_len());
8611            let item_ids_in_pane =
8612                HashSet::from_iter(active_pane.read(cx).items().map(|item| item.item_id()));
8613            assert!(item_ids_in_pane.contains(&center_item.item_id()));
8614            assert!(item_ids_in_pane.contains(&right_item.item_id()));
8615            assert!(item_ids_in_pane.contains(&bottom_item.item_id()));
8616            assert!(item_ids_in_pane.contains(&left_item.item_id()));
8617            assert!(item_ids_in_pane.contains(&top_item.item_id()));
8618
8619            // Single pane left: no-op
8620            workspace.join_pane_into_next(workspace.active_pane().clone(), window, cx)
8621        });
8622
8623        workspace.update(cx, |workspace, _cx| {
8624            let active_pane = workspace.active_pane();
8625            assert_eq!(top_pane_id, active_pane.entity_id());
8626        });
8627    }
8628
8629    fn add_an_item_to_active_pane(
8630        cx: &mut VisualTestContext,
8631        workspace: &Entity<Workspace>,
8632        item_id: u64,
8633    ) -> Entity<TestItem> {
8634        let item = cx.new(|cx| {
8635            TestItem::new(cx).with_project_items(&[TestProjectItem::new(
8636                item_id,
8637                "item{item_id}.txt",
8638                cx,
8639            )])
8640        });
8641        workspace.update_in(cx, |workspace, window, cx| {
8642            workspace.add_item_to_active_pane(Box::new(item.clone()), None, false, window, cx);
8643        });
8644        return item;
8645    }
8646
8647    fn split_pane(cx: &mut VisualTestContext, workspace: &Entity<Workspace>) -> Entity<Pane> {
8648        return workspace.update_in(cx, |workspace, window, cx| {
8649            let new_pane = workspace.split_pane(
8650                workspace.active_pane().clone(),
8651                SplitDirection::Right,
8652                window,
8653                cx,
8654            );
8655            new_pane
8656        });
8657    }
8658
8659    #[gpui::test]
8660    async fn test_join_all_panes(cx: &mut gpui::TestAppContext) {
8661        init_test(cx);
8662        let fs = FakeFs::new(cx.executor());
8663        let project = Project::test(fs, None, cx).await;
8664        let (workspace, cx) =
8665            cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
8666
8667        add_an_item_to_active_pane(cx, &workspace, 1);
8668        split_pane(cx, &workspace);
8669        add_an_item_to_active_pane(cx, &workspace, 2);
8670        split_pane(cx, &workspace); // empty pane
8671        split_pane(cx, &workspace);
8672        let last_item = add_an_item_to_active_pane(cx, &workspace, 3);
8673
8674        cx.executor().run_until_parked();
8675
8676        workspace.update(cx, |workspace, cx| {
8677            let num_panes = workspace.panes().len();
8678            let num_items_in_current_pane = workspace.active_pane().read(cx).items().count();
8679            let active_item = workspace
8680                .active_pane()
8681                .read(cx)
8682                .active_item()
8683                .expect("item is in focus");
8684
8685            assert_eq!(num_panes, 4);
8686            assert_eq!(num_items_in_current_pane, 1);
8687            assert_eq!(active_item.item_id(), last_item.item_id());
8688        });
8689
8690        workspace.update_in(cx, |workspace, window, cx| {
8691            workspace.join_all_panes(window, cx);
8692        });
8693
8694        workspace.update(cx, |workspace, cx| {
8695            let num_panes = workspace.panes().len();
8696            let num_items_in_current_pane = workspace.active_pane().read(cx).items().count();
8697            let active_item = workspace
8698                .active_pane()
8699                .read(cx)
8700                .active_item()
8701                .expect("item is in focus");
8702
8703            assert_eq!(num_panes, 1);
8704            assert_eq!(num_items_in_current_pane, 3);
8705            assert_eq!(active_item.item_id(), last_item.item_id());
8706        });
8707    }
8708    struct TestModal(FocusHandle);
8709
8710    impl TestModal {
8711        fn new(_: &mut Window, cx: &mut Context<Self>) -> Self {
8712            Self(cx.focus_handle())
8713        }
8714    }
8715
8716    impl EventEmitter<DismissEvent> for TestModal {}
8717
8718    impl Focusable for TestModal {
8719        fn focus_handle(&self, _cx: &App) -> FocusHandle {
8720            self.0.clone()
8721        }
8722    }
8723
8724    impl ModalView for TestModal {}
8725
8726    impl Render for TestModal {
8727        fn render(
8728            &mut self,
8729            _window: &mut Window,
8730            _cx: &mut Context<TestModal>,
8731        ) -> impl IntoElement {
8732            div().track_focus(&self.0)
8733        }
8734    }
8735
8736    #[gpui::test]
8737    async fn test_panels(cx: &mut gpui::TestAppContext) {
8738        init_test(cx);
8739        let fs = FakeFs::new(cx.executor());
8740
8741        let project = Project::test(fs, [], cx).await;
8742        let (workspace, cx) =
8743            cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
8744
8745        let (panel_1, panel_2) = workspace.update_in(cx, |workspace, window, cx| {
8746            let panel_1 = cx.new(|cx| TestPanel::new(DockPosition::Left, cx));
8747            workspace.add_panel(panel_1.clone(), window, cx);
8748            workspace.toggle_dock(DockPosition::Left, window, cx);
8749            let panel_2 = cx.new(|cx| TestPanel::new(DockPosition::Right, cx));
8750            workspace.add_panel(panel_2.clone(), window, cx);
8751            workspace.toggle_dock(DockPosition::Right, window, cx);
8752
8753            let left_dock = workspace.left_dock();
8754            assert_eq!(
8755                left_dock.read(cx).visible_panel().unwrap().panel_id(),
8756                panel_1.panel_id()
8757            );
8758            assert_eq!(
8759                left_dock.read(cx).active_panel_size(window, cx).unwrap(),
8760                panel_1.size(window, cx)
8761            );
8762
8763            left_dock.update(cx, |left_dock, cx| {
8764                left_dock.resize_active_panel(Some(px(1337.)), window, cx)
8765            });
8766            assert_eq!(
8767                workspace
8768                    .right_dock()
8769                    .read(cx)
8770                    .visible_panel()
8771                    .unwrap()
8772                    .panel_id(),
8773                panel_2.panel_id(),
8774            );
8775
8776            (panel_1, panel_2)
8777        });
8778
8779        // Move panel_1 to the right
8780        panel_1.update_in(cx, |panel_1, window, cx| {
8781            panel_1.set_position(DockPosition::Right, window, cx)
8782        });
8783
8784        workspace.update_in(cx, |workspace, window, cx| {
8785            // Since panel_1 was visible on the left, it should now be visible now that it's been moved to the right.
8786            // Since it was the only panel on the left, the left dock should now be closed.
8787            assert!(!workspace.left_dock().read(cx).is_open());
8788            assert!(workspace.left_dock().read(cx).visible_panel().is_none());
8789            let right_dock = workspace.right_dock();
8790            assert_eq!(
8791                right_dock.read(cx).visible_panel().unwrap().panel_id(),
8792                panel_1.panel_id()
8793            );
8794            assert_eq!(
8795                right_dock.read(cx).active_panel_size(window, cx).unwrap(),
8796                px(1337.)
8797            );
8798
8799            // Now we move panel_2 to the left
8800            panel_2.set_position(DockPosition::Left, window, cx);
8801        });
8802
8803        workspace.update(cx, |workspace, cx| {
8804            // Since panel_2 was not visible on the right, we don't open the left dock.
8805            assert!(!workspace.left_dock().read(cx).is_open());
8806            // And the right dock is unaffected in its displaying of panel_1
8807            assert!(workspace.right_dock().read(cx).is_open());
8808            assert_eq!(
8809                workspace
8810                    .right_dock()
8811                    .read(cx)
8812                    .visible_panel()
8813                    .unwrap()
8814                    .panel_id(),
8815                panel_1.panel_id(),
8816            );
8817        });
8818
8819        // Move panel_1 back to the left
8820        panel_1.update_in(cx, |panel_1, window, cx| {
8821            panel_1.set_position(DockPosition::Left, window, cx)
8822        });
8823
8824        workspace.update_in(cx, |workspace, window, cx| {
8825            // Since panel_1 was visible on the right, we open the left dock and make panel_1 active.
8826            let left_dock = workspace.left_dock();
8827            assert!(left_dock.read(cx).is_open());
8828            assert_eq!(
8829                left_dock.read(cx).visible_panel().unwrap().panel_id(),
8830                panel_1.panel_id()
8831            );
8832            assert_eq!(
8833                left_dock.read(cx).active_panel_size(window, cx).unwrap(),
8834                px(1337.)
8835            );
8836            // And the right dock should be closed as it no longer has any panels.
8837            assert!(!workspace.right_dock().read(cx).is_open());
8838
8839            // Now we move panel_1 to the bottom
8840            panel_1.set_position(DockPosition::Bottom, window, cx);
8841        });
8842
8843        workspace.update_in(cx, |workspace, window, cx| {
8844            // Since panel_1 was visible on the left, we close the left dock.
8845            assert!(!workspace.left_dock().read(cx).is_open());
8846            // The bottom dock is sized based on the panel's default size,
8847            // since the panel orientation changed from vertical to horizontal.
8848            let bottom_dock = workspace.bottom_dock();
8849            assert_eq!(
8850                bottom_dock.read(cx).active_panel_size(window, cx).unwrap(),
8851                panel_1.size(window, cx),
8852            );
8853            // Close bottom dock and move panel_1 back to the left.
8854            bottom_dock.update(cx, |bottom_dock, cx| {
8855                bottom_dock.set_open(false, window, cx)
8856            });
8857            panel_1.set_position(DockPosition::Left, window, cx);
8858        });
8859
8860        // Emit activated event on panel 1
8861        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::Activate));
8862
8863        // Now the left dock is open and panel_1 is active and focused.
8864        workspace.update_in(cx, |workspace, window, cx| {
8865            let left_dock = workspace.left_dock();
8866            assert!(left_dock.read(cx).is_open());
8867            assert_eq!(
8868                left_dock.read(cx).visible_panel().unwrap().panel_id(),
8869                panel_1.panel_id(),
8870            );
8871            assert!(panel_1.focus_handle(cx).is_focused(window));
8872        });
8873
8874        // Emit closed event on panel 2, which is not active
8875        panel_2.update(cx, |_, cx| cx.emit(PanelEvent::Close));
8876
8877        // Wo don't close the left dock, because panel_2 wasn't the active panel
8878        workspace.update(cx, |workspace, cx| {
8879            let left_dock = workspace.left_dock();
8880            assert!(left_dock.read(cx).is_open());
8881            assert_eq!(
8882                left_dock.read(cx).visible_panel().unwrap().panel_id(),
8883                panel_1.panel_id(),
8884            );
8885        });
8886
8887        // Emitting a ZoomIn event shows the panel as zoomed.
8888        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::ZoomIn));
8889        workspace.read_with(cx, |workspace, _| {
8890            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
8891            assert_eq!(workspace.zoomed_position, Some(DockPosition::Left));
8892        });
8893
8894        // Move panel to another dock while it is zoomed
8895        panel_1.update_in(cx, |panel, window, cx| {
8896            panel.set_position(DockPosition::Right, window, cx)
8897        });
8898        workspace.read_with(cx, |workspace, _| {
8899            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
8900
8901            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
8902        });
8903
8904        // This is a helper for getting a:
8905        // - valid focus on an element,
8906        // - that isn't a part of the panes and panels system of the Workspace,
8907        // - and doesn't trigger the 'on_focus_lost' API.
8908        let focus_other_view = {
8909            let workspace = workspace.clone();
8910            move |cx: &mut VisualTestContext| {
8911                workspace.update_in(cx, |workspace, window, cx| {
8912                    if let Some(_) = workspace.active_modal::<TestModal>(cx) {
8913                        workspace.toggle_modal(window, cx, TestModal::new);
8914                        workspace.toggle_modal(window, cx, TestModal::new);
8915                    } else {
8916                        workspace.toggle_modal(window, cx, TestModal::new);
8917                    }
8918                })
8919            }
8920        };
8921
8922        // If focus is transferred to another view that's not a panel or another pane, we still show
8923        // the panel as zoomed.
8924        focus_other_view(cx);
8925        workspace.read_with(cx, |workspace, _| {
8926            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
8927            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
8928        });
8929
8930        // If focus is transferred elsewhere in the workspace, the panel is no longer zoomed.
8931        workspace.update_in(cx, |_workspace, window, cx| {
8932            cx.focus_self(window);
8933        });
8934        workspace.read_with(cx, |workspace, _| {
8935            assert_eq!(workspace.zoomed, None);
8936            assert_eq!(workspace.zoomed_position, None);
8937        });
8938
8939        // If focus is transferred again to another view that's not a panel or a pane, we won't
8940        // show the panel as zoomed because it wasn't zoomed before.
8941        focus_other_view(cx);
8942        workspace.read_with(cx, |workspace, _| {
8943            assert_eq!(workspace.zoomed, None);
8944            assert_eq!(workspace.zoomed_position, None);
8945        });
8946
8947        // When the panel is activated, it is zoomed again.
8948        cx.dispatch_action(ToggleRightDock);
8949        workspace.read_with(cx, |workspace, _| {
8950            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
8951            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
8952        });
8953
8954        // Emitting a ZoomOut event unzooms the panel.
8955        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::ZoomOut));
8956        workspace.read_with(cx, |workspace, _| {
8957            assert_eq!(workspace.zoomed, None);
8958            assert_eq!(workspace.zoomed_position, None);
8959        });
8960
8961        // Emit closed event on panel 1, which is active
8962        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::Close));
8963
8964        // Now the left dock is closed, because panel_1 was the active panel
8965        workspace.update(cx, |workspace, cx| {
8966            let right_dock = workspace.right_dock();
8967            assert!(!right_dock.read(cx).is_open());
8968        });
8969    }
8970
8971    #[gpui::test]
8972    async fn test_no_save_prompt_when_multi_buffer_dirty_items_closed(cx: &mut TestAppContext) {
8973        init_test(cx);
8974
8975        let fs = FakeFs::new(cx.background_executor.clone());
8976        let project = Project::test(fs, [], cx).await;
8977        let (workspace, cx) =
8978            cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
8979        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
8980
8981        let dirty_regular_buffer = cx.new(|cx| {
8982            TestItem::new(cx)
8983                .with_dirty(true)
8984                .with_label("1.txt")
8985                .with_project_items(&[dirty_project_item(1, "1.txt", cx)])
8986        });
8987        let dirty_regular_buffer_2 = cx.new(|cx| {
8988            TestItem::new(cx)
8989                .with_dirty(true)
8990                .with_label("2.txt")
8991                .with_project_items(&[dirty_project_item(2, "2.txt", cx)])
8992        });
8993        let dirty_multi_buffer_with_both = cx.new(|cx| {
8994            TestItem::new(cx)
8995                .with_dirty(true)
8996                .with_singleton(false)
8997                .with_label("Fake Project Search")
8998                .with_project_items(&[
8999                    dirty_regular_buffer.read(cx).project_items[0].clone(),
9000                    dirty_regular_buffer_2.read(cx).project_items[0].clone(),
9001                ])
9002        });
9003        let multi_buffer_with_both_files_id = dirty_multi_buffer_with_both.item_id();
9004        workspace.update_in(cx, |workspace, window, cx| {
9005            workspace.add_item(
9006                pane.clone(),
9007                Box::new(dirty_regular_buffer.clone()),
9008                None,
9009                false,
9010                false,
9011                window,
9012                cx,
9013            );
9014            workspace.add_item(
9015                pane.clone(),
9016                Box::new(dirty_regular_buffer_2.clone()),
9017                None,
9018                false,
9019                false,
9020                window,
9021                cx,
9022            );
9023            workspace.add_item(
9024                pane.clone(),
9025                Box::new(dirty_multi_buffer_with_both.clone()),
9026                None,
9027                false,
9028                false,
9029                window,
9030                cx,
9031            );
9032        });
9033
9034        pane.update_in(cx, |pane, window, cx| {
9035            pane.activate_item(2, true, true, window, cx);
9036            assert_eq!(
9037                pane.active_item().unwrap().item_id(),
9038                multi_buffer_with_both_files_id,
9039                "Should select the multi buffer in the pane"
9040            );
9041        });
9042        let close_all_but_multi_buffer_task = pane.update_in(cx, |pane, window, cx| {
9043            pane.close_inactive_items(
9044                &CloseInactiveItems {
9045                    save_intent: Some(SaveIntent::Save),
9046                    close_pinned: true,
9047                },
9048                window,
9049                cx,
9050            )
9051        });
9052        cx.background_executor.run_until_parked();
9053        assert!(!cx.has_pending_prompt());
9054        close_all_but_multi_buffer_task
9055            .await
9056            .expect("Closing all buffers but the multi buffer failed");
9057        pane.update(cx, |pane, cx| {
9058            assert_eq!(dirty_regular_buffer.read(cx).save_count, 1);
9059            assert_eq!(dirty_multi_buffer_with_both.read(cx).save_count, 0);
9060            assert_eq!(dirty_regular_buffer_2.read(cx).save_count, 1);
9061            assert_eq!(pane.items_len(), 1);
9062            assert_eq!(
9063                pane.active_item().unwrap().item_id(),
9064                multi_buffer_with_both_files_id,
9065                "Should have only the multi buffer left in the pane"
9066            );
9067            assert!(
9068                dirty_multi_buffer_with_both.read(cx).is_dirty,
9069                "The multi buffer containing the unsaved buffer should still be dirty"
9070            );
9071        });
9072
9073        dirty_regular_buffer.update(cx, |buffer, cx| {
9074            buffer.project_items[0].update(cx, |pi, _| pi.is_dirty = true)
9075        });
9076
9077        let close_multi_buffer_task = pane.update_in(cx, |pane, window, cx| {
9078            pane.close_active_item(
9079                &CloseActiveItem {
9080                    save_intent: Some(SaveIntent::Close),
9081                    close_pinned: false,
9082                },
9083                window,
9084                cx,
9085            )
9086        });
9087        cx.background_executor.run_until_parked();
9088        assert!(
9089            cx.has_pending_prompt(),
9090            "Dirty multi buffer should prompt a save dialog"
9091        );
9092        cx.simulate_prompt_answer("Save");
9093        cx.background_executor.run_until_parked();
9094        close_multi_buffer_task
9095            .await
9096            .expect("Closing the multi buffer failed");
9097        pane.update(cx, |pane, cx| {
9098            assert_eq!(
9099                dirty_multi_buffer_with_both.read(cx).save_count,
9100                1,
9101                "Multi buffer item should get be saved"
9102            );
9103            // Test impl does not save inner items, so we do not assert them
9104            assert_eq!(
9105                pane.items_len(),
9106                0,
9107                "No more items should be left in the pane"
9108            );
9109            assert!(pane.active_item().is_none());
9110        });
9111    }
9112
9113    #[gpui::test]
9114    async fn test_save_prompt_when_dirty_multi_buffer_closed_with_some_of_its_dirty_items_not_present_in_the_pane(
9115        cx: &mut TestAppContext,
9116    ) {
9117        init_test(cx);
9118
9119        let fs = FakeFs::new(cx.background_executor.clone());
9120        let project = Project::test(fs, [], cx).await;
9121        let (workspace, cx) =
9122            cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
9123        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
9124
9125        let dirty_regular_buffer = cx.new(|cx| {
9126            TestItem::new(cx)
9127                .with_dirty(true)
9128                .with_label("1.txt")
9129                .with_project_items(&[dirty_project_item(1, "1.txt", cx)])
9130        });
9131        let dirty_regular_buffer_2 = cx.new(|cx| {
9132            TestItem::new(cx)
9133                .with_dirty(true)
9134                .with_label("2.txt")
9135                .with_project_items(&[dirty_project_item(2, "2.txt", cx)])
9136        });
9137        let clear_regular_buffer = cx.new(|cx| {
9138            TestItem::new(cx)
9139                .with_label("3.txt")
9140                .with_project_items(&[TestProjectItem::new(3, "3.txt", cx)])
9141        });
9142
9143        let dirty_multi_buffer_with_both = cx.new(|cx| {
9144            TestItem::new(cx)
9145                .with_dirty(true)
9146                .with_singleton(false)
9147                .with_label("Fake Project Search")
9148                .with_project_items(&[
9149                    dirty_regular_buffer.read(cx).project_items[0].clone(),
9150                    dirty_regular_buffer_2.read(cx).project_items[0].clone(),
9151                    clear_regular_buffer.read(cx).project_items[0].clone(),
9152                ])
9153        });
9154        let multi_buffer_with_both_files_id = dirty_multi_buffer_with_both.item_id();
9155        workspace.update_in(cx, |workspace, window, cx| {
9156            workspace.add_item(
9157                pane.clone(),
9158                Box::new(dirty_regular_buffer.clone()),
9159                None,
9160                false,
9161                false,
9162                window,
9163                cx,
9164            );
9165            workspace.add_item(
9166                pane.clone(),
9167                Box::new(dirty_multi_buffer_with_both.clone()),
9168                None,
9169                false,
9170                false,
9171                window,
9172                cx,
9173            );
9174        });
9175
9176        pane.update_in(cx, |pane, window, cx| {
9177            pane.activate_item(1, true, true, window, cx);
9178            assert_eq!(
9179                pane.active_item().unwrap().item_id(),
9180                multi_buffer_with_both_files_id,
9181                "Should select the multi buffer in the pane"
9182            );
9183        });
9184        let _close_multi_buffer_task = pane.update_in(cx, |pane, window, cx| {
9185            pane.close_active_item(
9186                &CloseActiveItem {
9187                    save_intent: None,
9188                    close_pinned: false,
9189                },
9190                window,
9191                cx,
9192            )
9193        });
9194        cx.background_executor.run_until_parked();
9195        assert!(
9196            cx.has_pending_prompt(),
9197            "With one dirty item from the multi buffer not being in the pane, a save prompt should be shown"
9198        );
9199    }
9200
9201    #[gpui::test]
9202    async fn test_no_save_prompt_when_dirty_multi_buffer_closed_with_all_of_its_dirty_items_present_in_the_pane(
9203        cx: &mut TestAppContext,
9204    ) {
9205        init_test(cx);
9206
9207        let fs = FakeFs::new(cx.background_executor.clone());
9208        let project = Project::test(fs, [], cx).await;
9209        let (workspace, cx) =
9210            cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
9211        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
9212
9213        let dirty_regular_buffer = cx.new(|cx| {
9214            TestItem::new(cx)
9215                .with_dirty(true)
9216                .with_label("1.txt")
9217                .with_project_items(&[dirty_project_item(1, "1.txt", cx)])
9218        });
9219        let dirty_regular_buffer_2 = cx.new(|cx| {
9220            TestItem::new(cx)
9221                .with_dirty(true)
9222                .with_label("2.txt")
9223                .with_project_items(&[dirty_project_item(2, "2.txt", cx)])
9224        });
9225        let clear_regular_buffer = cx.new(|cx| {
9226            TestItem::new(cx)
9227                .with_label("3.txt")
9228                .with_project_items(&[TestProjectItem::new(3, "3.txt", cx)])
9229        });
9230
9231        let dirty_multi_buffer = cx.new(|cx| {
9232            TestItem::new(cx)
9233                .with_dirty(true)
9234                .with_singleton(false)
9235                .with_label("Fake Project Search")
9236                .with_project_items(&[
9237                    dirty_regular_buffer.read(cx).project_items[0].clone(),
9238                    dirty_regular_buffer_2.read(cx).project_items[0].clone(),
9239                    clear_regular_buffer.read(cx).project_items[0].clone(),
9240                ])
9241        });
9242        workspace.update_in(cx, |workspace, window, cx| {
9243            workspace.add_item(
9244                pane.clone(),
9245                Box::new(dirty_regular_buffer.clone()),
9246                None,
9247                false,
9248                false,
9249                window,
9250                cx,
9251            );
9252            workspace.add_item(
9253                pane.clone(),
9254                Box::new(dirty_regular_buffer_2.clone()),
9255                None,
9256                false,
9257                false,
9258                window,
9259                cx,
9260            );
9261            workspace.add_item(
9262                pane.clone(),
9263                Box::new(dirty_multi_buffer.clone()),
9264                None,
9265                false,
9266                false,
9267                window,
9268                cx,
9269            );
9270        });
9271
9272        pane.update_in(cx, |pane, window, cx| {
9273            pane.activate_item(2, true, true, window, cx);
9274            assert_eq!(
9275                pane.active_item().unwrap().item_id(),
9276                dirty_multi_buffer.item_id(),
9277                "Should select the multi buffer in the pane"
9278            );
9279        });
9280        let close_multi_buffer_task = pane.update_in(cx, |pane, window, cx| {
9281            pane.close_active_item(
9282                &CloseActiveItem {
9283                    save_intent: None,
9284                    close_pinned: false,
9285                },
9286                window,
9287                cx,
9288            )
9289        });
9290        cx.background_executor.run_until_parked();
9291        assert!(
9292            !cx.has_pending_prompt(),
9293            "All dirty items from the multi buffer are in the pane still, no save prompts should be shown"
9294        );
9295        close_multi_buffer_task
9296            .await
9297            .expect("Closing multi buffer failed");
9298        pane.update(cx, |pane, cx| {
9299            assert_eq!(dirty_regular_buffer.read(cx).save_count, 0);
9300            assert_eq!(dirty_multi_buffer.read(cx).save_count, 0);
9301            assert_eq!(dirty_regular_buffer_2.read(cx).save_count, 0);
9302            assert_eq!(
9303                pane.items()
9304                    .map(|item| item.item_id())
9305                    .sorted()
9306                    .collect::<Vec<_>>(),
9307                vec![
9308                    dirty_regular_buffer.item_id(),
9309                    dirty_regular_buffer_2.item_id(),
9310                ],
9311                "Should have no multi buffer left in the pane"
9312            );
9313            assert!(dirty_regular_buffer.read(cx).is_dirty);
9314            assert!(dirty_regular_buffer_2.read(cx).is_dirty);
9315        });
9316    }
9317
9318    #[gpui::test]
9319    async fn test_move_focused_panel_to_next_position(cx: &mut gpui::TestAppContext) {
9320        init_test(cx);
9321        let fs = FakeFs::new(cx.executor());
9322        let project = Project::test(fs, [], cx).await;
9323        let (workspace, cx) =
9324            cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
9325
9326        // Add a new panel to the right dock, opening the dock and setting the
9327        // focus to the new panel.
9328        let panel = workspace.update_in(cx, |workspace, window, cx| {
9329            let panel = cx.new(|cx| TestPanel::new(DockPosition::Right, cx));
9330            workspace.add_panel(panel.clone(), window, cx);
9331
9332            workspace
9333                .right_dock()
9334                .update(cx, |right_dock, cx| right_dock.set_open(true, window, cx));
9335
9336            workspace.toggle_panel_focus::<TestPanel>(window, cx);
9337
9338            panel
9339        });
9340
9341        // Dispatch the `MoveFocusedPanelToNextPosition` action, moving the
9342        // panel to the next valid position which, in this case, is the left
9343        // dock.
9344        cx.dispatch_action(MoveFocusedPanelToNextPosition);
9345        workspace.update(cx, |workspace, cx| {
9346            assert!(workspace.left_dock().read(cx).is_open());
9347            assert_eq!(panel.read(cx).position, DockPosition::Left);
9348        });
9349
9350        // Dispatch the `MoveFocusedPanelToNextPosition` action, moving the
9351        // panel to the next valid position which, in this case, is the bottom
9352        // dock.
9353        cx.dispatch_action(MoveFocusedPanelToNextPosition);
9354        workspace.update(cx, |workspace, cx| {
9355            assert!(workspace.bottom_dock().read(cx).is_open());
9356            assert_eq!(panel.read(cx).position, DockPosition::Bottom);
9357        });
9358
9359        // Dispatch the `MoveFocusedPanelToNextPosition` action again, this time
9360        // around moving the panel to its initial position, the right dock.
9361        cx.dispatch_action(MoveFocusedPanelToNextPosition);
9362        workspace.update(cx, |workspace, cx| {
9363            assert!(workspace.right_dock().read(cx).is_open());
9364            assert_eq!(panel.read(cx).position, DockPosition::Right);
9365        });
9366
9367        // Remove focus from the panel, ensuring that, if the panel is not
9368        // focused, the `MoveFocusedPanelToNextPosition` action does not update
9369        // the panel's position, so the panel is still in the right dock.
9370        workspace.update_in(cx, |workspace, window, cx| {
9371            workspace.toggle_panel_focus::<TestPanel>(window, cx);
9372        });
9373
9374        cx.dispatch_action(MoveFocusedPanelToNextPosition);
9375        workspace.update(cx, |workspace, cx| {
9376            assert!(workspace.right_dock().read(cx).is_open());
9377            assert_eq!(panel.read(cx).position, DockPosition::Right);
9378        });
9379    }
9380
9381    #[gpui::test]
9382    async fn test_moving_items_create_panes(cx: &mut TestAppContext) {
9383        init_test(cx);
9384
9385        let fs = FakeFs::new(cx.executor());
9386        let project = Project::test(fs, [], cx).await;
9387        let (workspace, cx) =
9388            cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
9389
9390        let item_1 = cx.new(|cx| {
9391            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "first.txt", cx)])
9392        });
9393        workspace.update_in(cx, |workspace, window, cx| {
9394            workspace.add_item_to_active_pane(Box::new(item_1), None, true, window, cx);
9395            workspace.move_item_to_pane_in_direction(
9396                &MoveItemToPaneInDirection {
9397                    direction: SplitDirection::Right,
9398                    focus: true,
9399                },
9400                window,
9401                cx,
9402            );
9403            workspace.move_item_to_pane_at_index(
9404                &MoveItemToPane {
9405                    destination: 3,
9406                    focus: true,
9407                },
9408                window,
9409                cx,
9410            );
9411
9412            assert_eq!(workspace.panes.len(), 1, "No new panes were created");
9413            assert_eq!(
9414                pane_items_paths(&workspace.active_pane, cx),
9415                vec!["first.txt".to_string()],
9416                "Single item was not moved anywhere"
9417            );
9418        });
9419
9420        let item_2 = cx.new(|cx| {
9421            TestItem::new(cx).with_project_items(&[TestProjectItem::new(2, "second.txt", cx)])
9422        });
9423        workspace.update_in(cx, |workspace, window, cx| {
9424            workspace.add_item_to_active_pane(Box::new(item_2), None, true, window, cx);
9425            assert_eq!(
9426                pane_items_paths(&workspace.panes[0], cx),
9427                vec!["first.txt".to_string(), "second.txt".to_string()],
9428            );
9429            workspace.move_item_to_pane_in_direction(
9430                &MoveItemToPaneInDirection {
9431                    direction: SplitDirection::Right,
9432                    focus: true,
9433                },
9434                window,
9435                cx,
9436            );
9437
9438            assert_eq!(workspace.panes.len(), 2, "A new pane should be created");
9439            assert_eq!(
9440                pane_items_paths(&workspace.panes[0], cx),
9441                vec!["first.txt".to_string()],
9442                "After moving, one item should be left in the original pane"
9443            );
9444            assert_eq!(
9445                pane_items_paths(&workspace.panes[1], cx),
9446                vec!["second.txt".to_string()],
9447                "New item should have been moved to the new pane"
9448            );
9449        });
9450
9451        let item_3 = cx.new(|cx| {
9452            TestItem::new(cx).with_project_items(&[TestProjectItem::new(3, "third.txt", cx)])
9453        });
9454        workspace.update_in(cx, |workspace, window, cx| {
9455            let original_pane = workspace.panes[0].clone();
9456            workspace.set_active_pane(&original_pane, window, cx);
9457            workspace.add_item_to_active_pane(Box::new(item_3), None, true, window, cx);
9458            assert_eq!(workspace.panes.len(), 2, "No new panes were created");
9459            assert_eq!(
9460                pane_items_paths(&workspace.active_pane, cx),
9461                vec!["first.txt".to_string(), "third.txt".to_string()],
9462                "New pane should be ready to move one item out"
9463            );
9464
9465            workspace.move_item_to_pane_at_index(
9466                &MoveItemToPane {
9467                    destination: 3,
9468                    focus: true,
9469                },
9470                window,
9471                cx,
9472            );
9473            assert_eq!(workspace.panes.len(), 3, "A new pane should be created");
9474            assert_eq!(
9475                pane_items_paths(&workspace.active_pane, cx),
9476                vec!["first.txt".to_string()],
9477                "After moving, one item should be left in the original pane"
9478            );
9479            assert_eq!(
9480                pane_items_paths(&workspace.panes[1], cx),
9481                vec!["second.txt".to_string()],
9482                "Previously created pane should be unchanged"
9483            );
9484            assert_eq!(
9485                pane_items_paths(&workspace.panes[2], cx),
9486                vec!["third.txt".to_string()],
9487                "New item should have been moved to the new pane"
9488            );
9489        });
9490    }
9491
9492    mod register_project_item_tests {
9493
9494        use super::*;
9495
9496        // View
9497        struct TestPngItemView {
9498            focus_handle: FocusHandle,
9499        }
9500        // Model
9501        struct TestPngItem {}
9502
9503        impl project::ProjectItem for TestPngItem {
9504            fn try_open(
9505                _project: &Entity<Project>,
9506                path: &ProjectPath,
9507                cx: &mut App,
9508            ) -> Option<Task<anyhow::Result<Entity<Self>>>> {
9509                if path.path.extension().unwrap() == "png" {
9510                    Some(cx.spawn(async move |cx| cx.new(|_| TestPngItem {})))
9511                } else {
9512                    None
9513                }
9514            }
9515
9516            fn entry_id(&self, _: &App) -> Option<ProjectEntryId> {
9517                None
9518            }
9519
9520            fn project_path(&self, _: &App) -> Option<ProjectPath> {
9521                None
9522            }
9523
9524            fn is_dirty(&self) -> bool {
9525                false
9526            }
9527        }
9528
9529        impl Item for TestPngItemView {
9530            type Event = ();
9531            fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString {
9532                "".into()
9533            }
9534        }
9535        impl EventEmitter<()> for TestPngItemView {}
9536        impl Focusable for TestPngItemView {
9537            fn focus_handle(&self, _cx: &App) -> FocusHandle {
9538                self.focus_handle.clone()
9539            }
9540        }
9541
9542        impl Render for TestPngItemView {
9543            fn render(
9544                &mut self,
9545                _window: &mut Window,
9546                _cx: &mut Context<Self>,
9547            ) -> impl IntoElement {
9548                Empty
9549            }
9550        }
9551
9552        impl ProjectItem for TestPngItemView {
9553            type Item = TestPngItem;
9554
9555            fn for_project_item(
9556                _project: Entity<Project>,
9557                _pane: Option<&Pane>,
9558                _item: Entity<Self::Item>,
9559                _: &mut Window,
9560                cx: &mut Context<Self>,
9561            ) -> Self
9562            where
9563                Self: Sized,
9564            {
9565                Self {
9566                    focus_handle: cx.focus_handle(),
9567                }
9568            }
9569        }
9570
9571        // View
9572        struct TestIpynbItemView {
9573            focus_handle: FocusHandle,
9574        }
9575        // Model
9576        struct TestIpynbItem {}
9577
9578        impl project::ProjectItem for TestIpynbItem {
9579            fn try_open(
9580                _project: &Entity<Project>,
9581                path: &ProjectPath,
9582                cx: &mut App,
9583            ) -> Option<Task<anyhow::Result<Entity<Self>>>> {
9584                if path.path.extension().unwrap() == "ipynb" {
9585                    Some(cx.spawn(async move |cx| cx.new(|_| TestIpynbItem {})))
9586                } else {
9587                    None
9588                }
9589            }
9590
9591            fn entry_id(&self, _: &App) -> Option<ProjectEntryId> {
9592                None
9593            }
9594
9595            fn project_path(&self, _: &App) -> Option<ProjectPath> {
9596                None
9597            }
9598
9599            fn is_dirty(&self) -> bool {
9600                false
9601            }
9602        }
9603
9604        impl Item for TestIpynbItemView {
9605            type Event = ();
9606            fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString {
9607                "".into()
9608            }
9609        }
9610        impl EventEmitter<()> for TestIpynbItemView {}
9611        impl Focusable for TestIpynbItemView {
9612            fn focus_handle(&self, _cx: &App) -> FocusHandle {
9613                self.focus_handle.clone()
9614            }
9615        }
9616
9617        impl Render for TestIpynbItemView {
9618            fn render(
9619                &mut self,
9620                _window: &mut Window,
9621                _cx: &mut Context<Self>,
9622            ) -> impl IntoElement {
9623                Empty
9624            }
9625        }
9626
9627        impl ProjectItem for TestIpynbItemView {
9628            type Item = TestIpynbItem;
9629
9630            fn for_project_item(
9631                _project: Entity<Project>,
9632                _pane: Option<&Pane>,
9633                _item: Entity<Self::Item>,
9634                _: &mut Window,
9635                cx: &mut Context<Self>,
9636            ) -> Self
9637            where
9638                Self: Sized,
9639            {
9640                Self {
9641                    focus_handle: cx.focus_handle(),
9642                }
9643            }
9644        }
9645
9646        struct TestAlternatePngItemView {
9647            focus_handle: FocusHandle,
9648        }
9649
9650        impl Item for TestAlternatePngItemView {
9651            type Event = ();
9652            fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString {
9653                "".into()
9654            }
9655        }
9656
9657        impl EventEmitter<()> for TestAlternatePngItemView {}
9658        impl Focusable for TestAlternatePngItemView {
9659            fn focus_handle(&self, _cx: &App) -> FocusHandle {
9660                self.focus_handle.clone()
9661            }
9662        }
9663
9664        impl Render for TestAlternatePngItemView {
9665            fn render(
9666                &mut self,
9667                _window: &mut Window,
9668                _cx: &mut Context<Self>,
9669            ) -> impl IntoElement {
9670                Empty
9671            }
9672        }
9673
9674        impl ProjectItem for TestAlternatePngItemView {
9675            type Item = TestPngItem;
9676
9677            fn for_project_item(
9678                _project: Entity<Project>,
9679                _pane: Option<&Pane>,
9680                _item: Entity<Self::Item>,
9681                _: &mut Window,
9682                cx: &mut Context<Self>,
9683            ) -> Self
9684            where
9685                Self: Sized,
9686            {
9687                Self {
9688                    focus_handle: cx.focus_handle(),
9689                }
9690            }
9691        }
9692
9693        #[gpui::test]
9694        async fn test_register_project_item(cx: &mut TestAppContext) {
9695            init_test(cx);
9696
9697            cx.update(|cx| {
9698                register_project_item::<TestPngItemView>(cx);
9699                register_project_item::<TestIpynbItemView>(cx);
9700            });
9701
9702            let fs = FakeFs::new(cx.executor());
9703            fs.insert_tree(
9704                "/root1",
9705                json!({
9706                    "one.png": "BINARYDATAHERE",
9707                    "two.ipynb": "{ totally a notebook }",
9708                    "three.txt": "editing text, sure why not?"
9709                }),
9710            )
9711            .await;
9712
9713            let project = Project::test(fs, ["root1".as_ref()], cx).await;
9714            let (workspace, cx) =
9715                cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
9716
9717            let worktree_id = project.update(cx, |project, cx| {
9718                project.worktrees(cx).next().unwrap().read(cx).id()
9719            });
9720
9721            let handle = workspace
9722                .update_in(cx, |workspace, window, cx| {
9723                    let project_path = (worktree_id, "one.png");
9724                    workspace.open_path(project_path, None, true, window, cx)
9725                })
9726                .await
9727                .unwrap();
9728
9729            // Now we can check if the handle we got back errored or not
9730            assert_eq!(
9731                handle.to_any().entity_type(),
9732                TypeId::of::<TestPngItemView>()
9733            );
9734
9735            let handle = workspace
9736                .update_in(cx, |workspace, window, cx| {
9737                    let project_path = (worktree_id, "two.ipynb");
9738                    workspace.open_path(project_path, None, true, window, cx)
9739                })
9740                .await
9741                .unwrap();
9742
9743            assert_eq!(
9744                handle.to_any().entity_type(),
9745                TypeId::of::<TestIpynbItemView>()
9746            );
9747
9748            let handle = workspace
9749                .update_in(cx, |workspace, window, cx| {
9750                    let project_path = (worktree_id, "three.txt");
9751                    workspace.open_path(project_path, None, true, window, cx)
9752                })
9753                .await;
9754            assert!(handle.is_err());
9755        }
9756
9757        #[gpui::test]
9758        async fn test_register_project_item_two_enter_one_leaves(cx: &mut TestAppContext) {
9759            init_test(cx);
9760
9761            cx.update(|cx| {
9762                register_project_item::<TestPngItemView>(cx);
9763                register_project_item::<TestAlternatePngItemView>(cx);
9764            });
9765
9766            let fs = FakeFs::new(cx.executor());
9767            fs.insert_tree(
9768                "/root1",
9769                json!({
9770                    "one.png": "BINARYDATAHERE",
9771                    "two.ipynb": "{ totally a notebook }",
9772                    "three.txt": "editing text, sure why not?"
9773                }),
9774            )
9775            .await;
9776            let project = Project::test(fs, ["root1".as_ref()], cx).await;
9777            let (workspace, cx) =
9778                cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
9779            let worktree_id = project.update(cx, |project, cx| {
9780                project.worktrees(cx).next().unwrap().read(cx).id()
9781            });
9782
9783            let handle = workspace
9784                .update_in(cx, |workspace, window, cx| {
9785                    let project_path = (worktree_id, "one.png");
9786                    workspace.open_path(project_path, None, true, window, cx)
9787                })
9788                .await
9789                .unwrap();
9790
9791            // This _must_ be the second item registered
9792            assert_eq!(
9793                handle.to_any().entity_type(),
9794                TypeId::of::<TestAlternatePngItemView>()
9795            );
9796
9797            let handle = workspace
9798                .update_in(cx, |workspace, window, cx| {
9799                    let project_path = (worktree_id, "three.txt");
9800                    workspace.open_path(project_path, None, true, window, cx)
9801                })
9802                .await;
9803            assert!(handle.is_err());
9804        }
9805    }
9806
9807    fn pane_items_paths(pane: &Entity<Pane>, cx: &App) -> Vec<String> {
9808        pane.read(cx)
9809            .items()
9810            .flat_map(|item| {
9811                item.project_paths(cx)
9812                    .into_iter()
9813                    .map(|path| path.path.to_string_lossy().to_string())
9814            })
9815            .collect()
9816    }
9817
9818    pub fn init_test(cx: &mut TestAppContext) {
9819        cx.update(|cx| {
9820            let settings_store = SettingsStore::test(cx);
9821            cx.set_global(settings_store);
9822            theme::init(theme::LoadThemes::JustBase, cx);
9823            language::init(cx);
9824            crate::init_settings(cx);
9825            Project::init_settings(cx);
9826        });
9827    }
9828
9829    fn dirty_project_item(id: u64, path: &str, cx: &mut App) -> Entity<TestProjectItem> {
9830        let item = TestProjectItem::new(id, path, cx);
9831        item.update(cx, |item, _| {
9832            item.is_dirty = true;
9833        });
9834        item
9835    }
9836}