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