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