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