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