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