workspace.rs

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