workspace.rs

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