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.emit(Event::ClearActivityIndicator);
 5621            cx.propagate();
 5622        }
 5623    }
 5624}
 5625
 5626fn leader_border_for_pane(
 5627    follower_states: &HashMap<CollaboratorId, FollowerState>,
 5628    pane: &Entity<Pane>,
 5629    _: &Window,
 5630    cx: &App,
 5631) -> Option<Div> {
 5632    let (leader_id, _follower_state) = follower_states.iter().find_map(|(leader_id, state)| {
 5633        if state.pane() == pane {
 5634            Some((*leader_id, state))
 5635        } else {
 5636            None
 5637        }
 5638    })?;
 5639
 5640    let mut leader_color = match leader_id {
 5641        CollaboratorId::PeerId(leader_peer_id) => {
 5642            let room = ActiveCall::try_global(cx)?.read(cx).room()?.read(cx);
 5643            let leader = room.remote_participant_for_peer_id(leader_peer_id)?;
 5644
 5645            cx.theme()
 5646                .players()
 5647                .color_for_participant(leader.participant_index.0)
 5648                .cursor
 5649        }
 5650        CollaboratorId::Agent => cx.theme().players().agent().cursor,
 5651    };
 5652    leader_color.fade_out(0.3);
 5653    Some(
 5654        div()
 5655            .absolute()
 5656            .size_full()
 5657            .left_0()
 5658            .top_0()
 5659            .border_2()
 5660            .border_color(leader_color),
 5661    )
 5662}
 5663
 5664fn window_bounds_env_override() -> Option<Bounds<Pixels>> {
 5665    ZED_WINDOW_POSITION
 5666        .zip(*ZED_WINDOW_SIZE)
 5667        .map(|(position, size)| Bounds {
 5668            origin: position,
 5669            size,
 5670        })
 5671}
 5672
 5673fn open_items(
 5674    serialized_workspace: Option<SerializedWorkspace>,
 5675    mut project_paths_to_open: Vec<(PathBuf, Option<ProjectPath>)>,
 5676    window: &mut Window,
 5677    cx: &mut Context<Workspace>,
 5678) -> impl 'static + Future<Output = Result<Vec<Option<Result<Box<dyn ItemHandle>>>>>> + use<> {
 5679    let restored_items = serialized_workspace.map(|serialized_workspace| {
 5680        Workspace::load_workspace(
 5681            serialized_workspace,
 5682            project_paths_to_open
 5683                .iter()
 5684                .map(|(_, project_path)| project_path)
 5685                .cloned()
 5686                .collect(),
 5687            window,
 5688            cx,
 5689        )
 5690    });
 5691
 5692    cx.spawn_in(window, async move |workspace, cx| {
 5693        let mut opened_items = Vec::with_capacity(project_paths_to_open.len());
 5694
 5695        if let Some(restored_items) = restored_items {
 5696            let restored_items = restored_items.await?;
 5697
 5698            let restored_project_paths = restored_items
 5699                .iter()
 5700                .filter_map(|item| {
 5701                    cx.update(|_, cx| item.as_ref()?.project_path(cx))
 5702                        .ok()
 5703                        .flatten()
 5704                })
 5705                .collect::<HashSet<_>>();
 5706
 5707            for restored_item in restored_items {
 5708                opened_items.push(restored_item.map(Ok));
 5709            }
 5710
 5711            project_paths_to_open
 5712                .iter_mut()
 5713                .for_each(|(_, project_path)| {
 5714                    if let Some(project_path_to_open) = project_path {
 5715                        if restored_project_paths.contains(project_path_to_open) {
 5716                            *project_path = None;
 5717                        }
 5718                    }
 5719                });
 5720        } else {
 5721            for _ in 0..project_paths_to_open.len() {
 5722                opened_items.push(None);
 5723            }
 5724        }
 5725        assert!(opened_items.len() == project_paths_to_open.len());
 5726
 5727        let tasks =
 5728            project_paths_to_open
 5729                .into_iter()
 5730                .enumerate()
 5731                .map(|(ix, (abs_path, project_path))| {
 5732                    let workspace = workspace.clone();
 5733                    cx.spawn(async move |cx| {
 5734                        let file_project_path = project_path?;
 5735                        let abs_path_task = workspace.update(cx, |workspace, cx| {
 5736                            workspace.project().update(cx, |project, cx| {
 5737                                project.resolve_abs_path(abs_path.to_string_lossy().as_ref(), cx)
 5738                            })
 5739                        });
 5740
 5741                        // We only want to open file paths here. If one of the items
 5742                        // here is a directory, it was already opened further above
 5743                        // with a `find_or_create_worktree`.
 5744                        if let Ok(task) = abs_path_task {
 5745                            if task.await.map_or(true, |p| p.is_file()) {
 5746                                return Some((
 5747                                    ix,
 5748                                    workspace
 5749                                        .update_in(cx, |workspace, window, cx| {
 5750                                            workspace.open_path(
 5751                                                file_project_path,
 5752                                                None,
 5753                                                true,
 5754                                                window,
 5755                                                cx,
 5756                                            )
 5757                                        })
 5758                                        .log_err()?
 5759                                        .await,
 5760                                ));
 5761                            }
 5762                        }
 5763                        None
 5764                    })
 5765                });
 5766
 5767        let tasks = tasks.collect::<Vec<_>>();
 5768
 5769        let tasks = futures::future::join_all(tasks);
 5770        for (ix, path_open_result) in tasks.await.into_iter().flatten() {
 5771            opened_items[ix] = Some(path_open_result);
 5772        }
 5773
 5774        Ok(opened_items)
 5775    })
 5776}
 5777
 5778enum ActivateInDirectionTarget {
 5779    Pane(Entity<Pane>),
 5780    Dock(Entity<Dock>),
 5781}
 5782
 5783fn notify_if_database_failed(workspace: WindowHandle<Workspace>, cx: &mut AsyncApp) {
 5784    workspace
 5785        .update(cx, |workspace, _, cx| {
 5786            if (*db::ALL_FILE_DB_FAILED).load(std::sync::atomic::Ordering::Acquire) {
 5787                struct DatabaseFailedNotification;
 5788
 5789                workspace.show_notification(
 5790                    NotificationId::unique::<DatabaseFailedNotification>(),
 5791                    cx,
 5792                    |cx| {
 5793                        cx.new(|cx| {
 5794                            MessageNotification::new("Failed to load the database file.", cx)
 5795                                .primary_message("File an Issue")
 5796                                .primary_icon(IconName::Plus)
 5797                                .primary_on_click(|window, cx| {
 5798                                    window.dispatch_action(Box::new(FileBugReport), cx)
 5799                                })
 5800                        })
 5801                    },
 5802                );
 5803            }
 5804        })
 5805        .log_err();
 5806}
 5807
 5808impl Focusable for Workspace {
 5809    fn focus_handle(&self, cx: &App) -> FocusHandle {
 5810        self.active_pane.focus_handle(cx)
 5811    }
 5812}
 5813
 5814#[derive(Clone)]
 5815struct DraggedDock(DockPosition);
 5816
 5817impl Render for DraggedDock {
 5818    fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
 5819        gpui::Empty
 5820    }
 5821}
 5822
 5823impl Render for Workspace {
 5824    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
 5825        let mut context = KeyContext::new_with_defaults();
 5826        context.add("Workspace");
 5827        context.set("keyboard_layout", cx.keyboard_layout().name().to_string());
 5828        if let Some(status) = self
 5829            .debugger_provider
 5830            .as_ref()
 5831            .and_then(|provider| provider.active_thread_state(cx))
 5832        {
 5833            match status {
 5834                ThreadStatus::Running | ThreadStatus::Stepping => {
 5835                    context.add("debugger_running");
 5836                }
 5837                ThreadStatus::Stopped => context.add("debugger_stopped"),
 5838                ThreadStatus::Exited | ThreadStatus::Ended => {}
 5839            }
 5840        }
 5841
 5842        let centered_layout = self.centered_layout
 5843            && self.center.panes().len() == 1
 5844            && self.active_item(cx).is_some();
 5845        let render_padding = |size| {
 5846            (size > 0.0).then(|| {
 5847                div()
 5848                    .h_full()
 5849                    .w(relative(size))
 5850                    .bg(cx.theme().colors().editor_background)
 5851                    .border_color(cx.theme().colors().pane_group_border)
 5852            })
 5853        };
 5854        let paddings = if centered_layout {
 5855            let settings = WorkspaceSettings::get_global(cx).centered_layout;
 5856            (
 5857                render_padding(Self::adjust_padding(settings.left_padding)),
 5858                render_padding(Self::adjust_padding(settings.right_padding)),
 5859            )
 5860        } else {
 5861            (None, None)
 5862        };
 5863        let ui_font = theme::setup_ui_font(window, cx);
 5864
 5865        let theme = cx.theme().clone();
 5866        let colors = theme.colors();
 5867        let notification_entities = self
 5868            .notifications
 5869            .iter()
 5870            .map(|(_, notification)| notification.entity_id())
 5871            .collect::<Vec<_>>();
 5872
 5873        client_side_decorations(
 5874            self.actions(div(), window, cx)
 5875                .key_context(context)
 5876                .relative()
 5877                .size_full()
 5878                .flex()
 5879                .flex_col()
 5880                .font(ui_font)
 5881                .gap_0()
 5882                .justify_start()
 5883                .items_start()
 5884                .text_color(colors.text)
 5885                .overflow_hidden()
 5886                .children(self.titlebar_item.clone())
 5887                .on_modifiers_changed(move |_, _, cx| {
 5888                    for &id in &notification_entities {
 5889                        cx.notify(id);
 5890                    }
 5891                })
 5892                .child(
 5893                    div()
 5894                        .size_full()
 5895                        .relative()
 5896                        .flex_1()
 5897                        .flex()
 5898                        .flex_col()
 5899                        .child(
 5900                            div()
 5901                                .id("workspace")
 5902                                .bg(colors.background)
 5903                                .relative()
 5904                                .flex_1()
 5905                                .w_full()
 5906                                .flex()
 5907                                .flex_col()
 5908                                .overflow_hidden()
 5909                                .border_t_1()
 5910                                .border_b_1()
 5911                                .border_color(colors.border)
 5912                                .child({
 5913                                    let this = cx.entity().clone();
 5914                                    canvas(
 5915                                        move |bounds, window, cx| {
 5916                                            this.update(cx, |this, cx| {
 5917                                                let bounds_changed = this.bounds != bounds;
 5918                                                this.bounds = bounds;
 5919
 5920                                                if bounds_changed {
 5921                                                    this.left_dock.update(cx, |dock, cx| {
 5922                                                        dock.clamp_panel_size(
 5923                                                            bounds.size.width,
 5924                                                            window,
 5925                                                            cx,
 5926                                                        )
 5927                                                    });
 5928
 5929                                                    this.right_dock.update(cx, |dock, cx| {
 5930                                                        dock.clamp_panel_size(
 5931                                                            bounds.size.width,
 5932                                                            window,
 5933                                                            cx,
 5934                                                        )
 5935                                                    });
 5936
 5937                                                    this.bottom_dock.update(cx, |dock, cx| {
 5938                                                        dock.clamp_panel_size(
 5939                                                            bounds.size.height,
 5940                                                            window,
 5941                                                            cx,
 5942                                                        )
 5943                                                    });
 5944                                                }
 5945                                            })
 5946                                        },
 5947                                        |_, _, _, _| {},
 5948                                    )
 5949                                    .absolute()
 5950                                    .size_full()
 5951                                })
 5952                                .when(self.zoomed.is_none(), |this| {
 5953                                    this.on_drag_move(cx.listener(
 5954                                        move |workspace,
 5955                                              e: &DragMoveEvent<DraggedDock>,
 5956                                              window,
 5957                                              cx| {
 5958                                            if workspace.previous_dock_drag_coordinates
 5959                                                != Some(e.event.position)
 5960                                            {
 5961                                                workspace.previous_dock_drag_coordinates =
 5962                                                    Some(e.event.position);
 5963                                                match e.drag(cx).0 {
 5964                                                    DockPosition::Left => {
 5965                                                        resize_left_dock(
 5966                                                            e.event.position.x
 5967                                                                - workspace.bounds.left(),
 5968                                                            workspace,
 5969                                                            window,
 5970                                                            cx,
 5971                                                        );
 5972                                                    }
 5973                                                    DockPosition::Right => {
 5974                                                        resize_right_dock(
 5975                                                            workspace.bounds.right()
 5976                                                                - e.event.position.x,
 5977                                                            workspace,
 5978                                                            window,
 5979                                                            cx,
 5980                                                        );
 5981                                                    }
 5982                                                    DockPosition::Bottom => {
 5983                                                        resize_bottom_dock(
 5984                                                            workspace.bounds.bottom()
 5985                                                                - e.event.position.y,
 5986                                                            workspace,
 5987                                                            window,
 5988                                                            cx,
 5989                                                        );
 5990                                                    }
 5991                                                };
 5992                                                workspace.serialize_workspace(window, cx);
 5993                                            }
 5994                                        },
 5995                                    ))
 5996                                })
 5997                                .child({
 5998                                    match self.bottom_dock_layout {
 5999                                        BottomDockLayout::Full => div()
 6000                                            .flex()
 6001                                            .flex_col()
 6002                                            .h_full()
 6003                                            .child(
 6004                                                div()
 6005                                                    .flex()
 6006                                                    .flex_row()
 6007                                                    .flex_1()
 6008                                                    .overflow_hidden()
 6009                                                    .children(self.render_dock(
 6010                                                        DockPosition::Left,
 6011                                                        &self.left_dock,
 6012                                                        window,
 6013                                                        cx,
 6014                                                    ))
 6015                                                    .child(
 6016                                                        div()
 6017                                                            .flex()
 6018                                                            .flex_col()
 6019                                                            .flex_1()
 6020                                                            .overflow_hidden()
 6021                                                            .child(
 6022                                                                h_flex()
 6023                                                                    .flex_1()
 6024                                                                    .when_some(
 6025                                                                        paddings.0,
 6026                                                                        |this, p| {
 6027                                                                            this.child(
 6028                                                                                p.border_r_1(),
 6029                                                                            )
 6030                                                                        },
 6031                                                                    )
 6032                                                                    .child(self.center.render(
 6033                                                                        self.zoomed.as_ref(),
 6034                                                                        &PaneRenderContext {
 6035                                                                            follower_states:
 6036                                                                                &self.follower_states,
 6037                                                                            active_call: self.active_call(),
 6038                                                                            active_pane: &self.active_pane,
 6039                                                                            app_state: &self.app_state,
 6040                                                                            project: &self.project,
 6041                                                                            workspace: &self.weak_self,
 6042                                                                        },
 6043                                                                        window,
 6044                                                                        cx,
 6045                                                                    ))
 6046                                                                    .when_some(
 6047                                                                        paddings.1,
 6048                                                                        |this, p| {
 6049                                                                            this.child(
 6050                                                                                p.border_l_1(),
 6051                                                                            )
 6052                                                                        },
 6053                                                                    ),
 6054                                                            ),
 6055                                                    )
 6056                                                    .children(self.render_dock(
 6057                                                        DockPosition::Right,
 6058                                                        &self.right_dock,
 6059                                                        window,
 6060                                                        cx,
 6061                                                    )),
 6062                                            )
 6063                                            .child(div().w_full().children(self.render_dock(
 6064                                                DockPosition::Bottom,
 6065                                                &self.bottom_dock,
 6066                                                window,
 6067                                                cx
 6068                                            ))),
 6069
 6070                                        BottomDockLayout::LeftAligned => div()
 6071                                            .flex()
 6072                                            .flex_row()
 6073                                            .h_full()
 6074                                            .child(
 6075                                                div()
 6076                                                    .flex()
 6077                                                    .flex_col()
 6078                                                    .flex_1()
 6079                                                    .h_full()
 6080                                                    .child(
 6081                                                        div()
 6082                                                            .flex()
 6083                                                            .flex_row()
 6084                                                            .flex_1()
 6085                                                            .children(self.render_dock(DockPosition::Left, &self.left_dock, window, cx))
 6086                                                            .child(
 6087                                                                div()
 6088                                                                    .flex()
 6089                                                                    .flex_col()
 6090                                                                    .flex_1()
 6091                                                                    .overflow_hidden()
 6092                                                                    .child(
 6093                                                                        h_flex()
 6094                                                                            .flex_1()
 6095                                                                            .when_some(paddings.0, |this, p| this.child(p.border_r_1()))
 6096                                                                            .child(self.center.render(
 6097                                                                                self.zoomed.as_ref(),
 6098                                                                                &PaneRenderContext {
 6099                                                                                    follower_states:
 6100                                                                                        &self.follower_states,
 6101                                                                                    active_call: self.active_call(),
 6102                                                                                    active_pane: &self.active_pane,
 6103                                                                                    app_state: &self.app_state,
 6104                                                                                    project: &self.project,
 6105                                                                                    workspace: &self.weak_self,
 6106                                                                                },
 6107                                                                                window,
 6108                                                                                cx,
 6109                                                                            ))
 6110                                                                            .when_some(paddings.1, |this, p| this.child(p.border_l_1())),
 6111                                                                    )
 6112                                                            )
 6113                                                    )
 6114                                                    .child(
 6115                                                        div()
 6116                                                            .w_full()
 6117                                                            .children(self.render_dock(DockPosition::Bottom, &self.bottom_dock, window, cx))
 6118                                                    ),
 6119                                            )
 6120                                            .children(self.render_dock(
 6121                                                DockPosition::Right,
 6122                                                &self.right_dock,
 6123                                                window,
 6124                                                cx,
 6125                                            )),
 6126
 6127                                        BottomDockLayout::RightAligned => div()
 6128                                            .flex()
 6129                                            .flex_row()
 6130                                            .h_full()
 6131                                            .children(self.render_dock(
 6132                                                DockPosition::Left,
 6133                                                &self.left_dock,
 6134                                                window,
 6135                                                cx,
 6136                                            ))
 6137                                            .child(
 6138                                                div()
 6139                                                    .flex()
 6140                                                    .flex_col()
 6141                                                    .flex_1()
 6142                                                    .h_full()
 6143                                                    .child(
 6144                                                        div()
 6145                                                            .flex()
 6146                                                            .flex_row()
 6147                                                            .flex_1()
 6148                                                            .child(
 6149                                                                div()
 6150                                                                    .flex()
 6151                                                                    .flex_col()
 6152                                                                    .flex_1()
 6153                                                                    .overflow_hidden()
 6154                                                                    .child(
 6155                                                                        h_flex()
 6156                                                                            .flex_1()
 6157                                                                            .when_some(paddings.0, |this, p| this.child(p.border_r_1()))
 6158                                                                            .child(self.center.render(
 6159                                                                                self.zoomed.as_ref(),
 6160                                                                                &PaneRenderContext {
 6161                                                                                    follower_states:
 6162                                                                                        &self.follower_states,
 6163                                                                                    active_call: self.active_call(),
 6164                                                                                    active_pane: &self.active_pane,
 6165                                                                                    app_state: &self.app_state,
 6166                                                                                    project: &self.project,
 6167                                                                                    workspace: &self.weak_self,
 6168                                                                                },
 6169                                                                                window,
 6170                                                                                cx,
 6171                                                                            ))
 6172                                                                            .when_some(paddings.1, |this, p| this.child(p.border_l_1())),
 6173                                                                    )
 6174                                                            )
 6175                                                            .children(self.render_dock(DockPosition::Right, &self.right_dock, window, cx))
 6176                                                    )
 6177                                                    .child(
 6178                                                        div()
 6179                                                            .w_full()
 6180                                                            .children(self.render_dock(DockPosition::Bottom, &self.bottom_dock, window, cx))
 6181                                                    ),
 6182                                            ),
 6183
 6184                                        BottomDockLayout::Contained => div()
 6185                                            .flex()
 6186                                            .flex_row()
 6187                                            .h_full()
 6188                                            .children(self.render_dock(
 6189                                                DockPosition::Left,
 6190                                                &self.left_dock,
 6191                                                window,
 6192                                                cx,
 6193                                            ))
 6194                                            .child(
 6195                                                div()
 6196                                                    .flex()
 6197                                                    .flex_col()
 6198                                                    .flex_1()
 6199                                                    .overflow_hidden()
 6200                                                    .child(
 6201                                                        h_flex()
 6202                                                            .flex_1()
 6203                                                            .when_some(paddings.0, |this, p| {
 6204                                                                this.child(p.border_r_1())
 6205                                                            })
 6206                                                            .child(self.center.render(
 6207                                                                self.zoomed.as_ref(),
 6208                                                                &PaneRenderContext {
 6209                                                                    follower_states:
 6210                                                                        &self.follower_states,
 6211                                                                    active_call: self.active_call(),
 6212                                                                    active_pane: &self.active_pane,
 6213                                                                    app_state: &self.app_state,
 6214                                                                    project: &self.project,
 6215                                                                    workspace: &self.weak_self,
 6216                                                                },
 6217                                                                window,
 6218                                                                cx,
 6219                                                            ))
 6220                                                            .when_some(paddings.1, |this, p| {
 6221                                                                this.child(p.border_l_1())
 6222                                                            }),
 6223                                                    )
 6224                                                    .children(self.render_dock(
 6225                                                        DockPosition::Bottom,
 6226                                                        &self.bottom_dock,
 6227                                                        window,
 6228                                                        cx,
 6229                                                    )),
 6230                                            )
 6231                                            .children(self.render_dock(
 6232                                                DockPosition::Right,
 6233                                                &self.right_dock,
 6234                                                window,
 6235                                                cx,
 6236                                            )),
 6237                                    }
 6238                                })
 6239                                .children(self.zoomed.as_ref().and_then(|view| {
 6240                                    let zoomed_view = view.upgrade()?;
 6241                                    let div = div()
 6242                                        .occlude()
 6243                                        .absolute()
 6244                                        .overflow_hidden()
 6245                                        .border_color(colors.border)
 6246                                        .bg(colors.background)
 6247                                        .child(zoomed_view)
 6248                                        .inset_0()
 6249                                        .shadow_lg();
 6250
 6251                                    Some(match self.zoomed_position {
 6252                                        Some(DockPosition::Left) => div.right_2().border_r_1(),
 6253                                        Some(DockPosition::Right) => div.left_2().border_l_1(),
 6254                                        Some(DockPosition::Bottom) => div.top_2().border_t_1(),
 6255                                        None => {
 6256                                            div.top_2().bottom_2().left_2().right_2().border_1()
 6257                                        }
 6258                                    })
 6259                                }))
 6260                                .children(self.render_notifications(window, cx)),
 6261                        )
 6262                        .child(self.status_bar.clone())
 6263                        .child(self.modal_layer.clone())
 6264                        .child(self.toast_layer.clone()),
 6265                ),
 6266            window,
 6267            cx,
 6268        )
 6269    }
 6270}
 6271
 6272fn resize_bottom_dock(
 6273    new_size: Pixels,
 6274    workspace: &mut Workspace,
 6275    window: &mut Window,
 6276    cx: &mut App,
 6277) {
 6278    let size =
 6279        new_size.min(workspace.bounds.bottom() - RESIZE_HANDLE_SIZE - workspace.bounds.top());
 6280    workspace.bottom_dock.update(cx, |bottom_dock, cx| {
 6281        if WorkspaceSettings::get_global(cx)
 6282            .resize_all_panels_in_dock
 6283            .contains(&DockPosition::Bottom)
 6284        {
 6285            bottom_dock.resize_all_panels(Some(size), window, cx);
 6286        } else {
 6287            bottom_dock.resize_active_panel(Some(size), window, cx);
 6288        }
 6289    });
 6290}
 6291
 6292fn resize_right_dock(
 6293    new_size: Pixels,
 6294    workspace: &mut Workspace,
 6295    window: &mut Window,
 6296    cx: &mut App,
 6297) {
 6298    let mut size = new_size.max(workspace.bounds.left() - RESIZE_HANDLE_SIZE);
 6299    workspace.left_dock.read_with(cx, |left_dock, cx| {
 6300        let left_dock_size = left_dock
 6301            .active_panel_size(window, cx)
 6302            .unwrap_or(Pixels(0.0));
 6303        if left_dock_size + size > workspace.bounds.right() {
 6304            size = workspace.bounds.right() - left_dock_size
 6305        }
 6306    });
 6307    workspace.right_dock.update(cx, |right_dock, cx| {
 6308        if WorkspaceSettings::get_global(cx)
 6309            .resize_all_panels_in_dock
 6310            .contains(&DockPosition::Right)
 6311        {
 6312            right_dock.resize_all_panels(Some(size), window, cx);
 6313        } else {
 6314            right_dock.resize_active_panel(Some(size), window, cx);
 6315        }
 6316    });
 6317}
 6318
 6319fn resize_left_dock(
 6320    new_size: Pixels,
 6321    workspace: &mut Workspace,
 6322    window: &mut Window,
 6323    cx: &mut App,
 6324) {
 6325    let size = new_size.min(workspace.bounds.right() - RESIZE_HANDLE_SIZE);
 6326
 6327    workspace.left_dock.update(cx, |left_dock, cx| {
 6328        if WorkspaceSettings::get_global(cx)
 6329            .resize_all_panels_in_dock
 6330            .contains(&DockPosition::Left)
 6331        {
 6332            left_dock.resize_all_panels(Some(size), window, cx);
 6333        } else {
 6334            left_dock.resize_active_panel(Some(size), window, cx);
 6335        }
 6336    });
 6337}
 6338
 6339impl WorkspaceStore {
 6340    pub fn new(client: Arc<Client>, cx: &mut Context<Self>) -> Self {
 6341        Self {
 6342            workspaces: Default::default(),
 6343            _subscriptions: vec![
 6344                client.add_request_handler(cx.weak_entity(), Self::handle_follow),
 6345                client.add_message_handler(cx.weak_entity(), Self::handle_update_followers),
 6346            ],
 6347            client,
 6348        }
 6349    }
 6350
 6351    pub fn update_followers(
 6352        &self,
 6353        project_id: Option<u64>,
 6354        update: proto::update_followers::Variant,
 6355        cx: &App,
 6356    ) -> Option<()> {
 6357        let active_call = ActiveCall::try_global(cx)?;
 6358        let room_id = active_call.read(cx).room()?.read(cx).id();
 6359        self.client
 6360            .send(proto::UpdateFollowers {
 6361                room_id,
 6362                project_id,
 6363                variant: Some(update),
 6364            })
 6365            .log_err()
 6366    }
 6367
 6368    pub async fn handle_follow(
 6369        this: Entity<Self>,
 6370        envelope: TypedEnvelope<proto::Follow>,
 6371        mut cx: AsyncApp,
 6372    ) -> Result<proto::FollowResponse> {
 6373        this.update(&mut cx, |this, cx| {
 6374            let follower = Follower {
 6375                project_id: envelope.payload.project_id,
 6376                peer_id: envelope.original_sender_id()?,
 6377            };
 6378
 6379            let mut response = proto::FollowResponse::default();
 6380            this.workspaces.retain(|workspace| {
 6381                workspace
 6382                    .update(cx, |workspace, window, cx| {
 6383                        let handler_response =
 6384                            workspace.handle_follow(follower.project_id, window, cx);
 6385                        if let Some(active_view) = handler_response.active_view.clone() {
 6386                            if workspace.project.read(cx).remote_id() == follower.project_id {
 6387                                response.active_view = Some(active_view)
 6388                            }
 6389                        }
 6390                    })
 6391                    .is_ok()
 6392            });
 6393
 6394            Ok(response)
 6395        })?
 6396    }
 6397
 6398    async fn handle_update_followers(
 6399        this: Entity<Self>,
 6400        envelope: TypedEnvelope<proto::UpdateFollowers>,
 6401        mut cx: AsyncApp,
 6402    ) -> Result<()> {
 6403        let leader_id = envelope.original_sender_id()?;
 6404        let update = envelope.payload;
 6405
 6406        this.update(&mut cx, |this, cx| {
 6407            this.workspaces.retain(|workspace| {
 6408                workspace
 6409                    .update(cx, |workspace, window, cx| {
 6410                        let project_id = workspace.project.read(cx).remote_id();
 6411                        if update.project_id != project_id && update.project_id.is_some() {
 6412                            return;
 6413                        }
 6414                        workspace.handle_update_followers(leader_id, update.clone(), window, cx);
 6415                    })
 6416                    .is_ok()
 6417            });
 6418            Ok(())
 6419        })?
 6420    }
 6421}
 6422
 6423impl ViewId {
 6424    pub(crate) fn from_proto(message: proto::ViewId) -> Result<Self> {
 6425        Ok(Self {
 6426            creator: message
 6427                .creator
 6428                .map(CollaboratorId::PeerId)
 6429                .context("creator is missing")?,
 6430            id: message.id,
 6431        })
 6432    }
 6433
 6434    pub(crate) fn to_proto(self) -> Option<proto::ViewId> {
 6435        if let CollaboratorId::PeerId(peer_id) = self.creator {
 6436            Some(proto::ViewId {
 6437                creator: Some(peer_id),
 6438                id: self.id,
 6439            })
 6440        } else {
 6441            None
 6442        }
 6443    }
 6444}
 6445
 6446impl FollowerState {
 6447    fn pane(&self) -> &Entity<Pane> {
 6448        self.dock_pane.as_ref().unwrap_or(&self.center_pane)
 6449    }
 6450}
 6451
 6452pub trait WorkspaceHandle {
 6453    fn file_project_paths(&self, cx: &App) -> Vec<ProjectPath>;
 6454}
 6455
 6456impl WorkspaceHandle for Entity<Workspace> {
 6457    fn file_project_paths(&self, cx: &App) -> Vec<ProjectPath> {
 6458        self.read(cx)
 6459            .worktrees(cx)
 6460            .flat_map(|worktree| {
 6461                let worktree_id = worktree.read(cx).id();
 6462                worktree.read(cx).files(true, 0).map(move |f| ProjectPath {
 6463                    worktree_id,
 6464                    path: f.path.clone(),
 6465                })
 6466            })
 6467            .collect::<Vec<_>>()
 6468    }
 6469}
 6470
 6471impl std::fmt::Debug for OpenPaths {
 6472    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 6473        f.debug_struct("OpenPaths")
 6474            .field("paths", &self.paths)
 6475            .finish()
 6476    }
 6477}
 6478
 6479pub async fn last_opened_workspace_location() -> Option<SerializedWorkspaceLocation> {
 6480    DB.last_workspace().await.log_err().flatten()
 6481}
 6482
 6483pub fn last_session_workspace_locations(
 6484    last_session_id: &str,
 6485    last_session_window_stack: Option<Vec<WindowId>>,
 6486) -> Option<Vec<SerializedWorkspaceLocation>> {
 6487    DB.last_session_workspace_locations(last_session_id, last_session_window_stack)
 6488        .log_err()
 6489}
 6490
 6491actions!(
 6492    collab,
 6493    [
 6494        /// Opens the channel notes for the current call.
 6495        ///
 6496        /// If you want to open a specific channel, use `zed::OpenZedUrl` with a channel notes URL -
 6497        /// can be copied via "Copy link to section" in the context menu of the channel notes
 6498        /// buffer. These URLs look like `https://zed.dev/channel/channel-name-CHANNEL_ID/notes`.
 6499        OpenChannelNotes,
 6500        Mute,
 6501        Deafen,
 6502        LeaveCall,
 6503        ShareProject,
 6504        ScreenShare
 6505    ]
 6506);
 6507actions!(zed, [OpenLog]);
 6508
 6509async fn join_channel_internal(
 6510    channel_id: ChannelId,
 6511    app_state: &Arc<AppState>,
 6512    requesting_window: Option<WindowHandle<Workspace>>,
 6513    active_call: &Entity<ActiveCall>,
 6514    cx: &mut AsyncApp,
 6515) -> Result<bool> {
 6516    let (should_prompt, open_room) = active_call.update(cx, |active_call, cx| {
 6517        let Some(room) = active_call.room().map(|room| room.read(cx)) else {
 6518            return (false, None);
 6519        };
 6520
 6521        let already_in_channel = room.channel_id() == Some(channel_id);
 6522        let should_prompt = room.is_sharing_project()
 6523            && !room.remote_participants().is_empty()
 6524            && !already_in_channel;
 6525        let open_room = if already_in_channel {
 6526            active_call.room().cloned()
 6527        } else {
 6528            None
 6529        };
 6530        (should_prompt, open_room)
 6531    })?;
 6532
 6533    if let Some(room) = open_room {
 6534        let task = room.update(cx, |room, cx| {
 6535            if let Some((project, host)) = room.most_active_project(cx) {
 6536                return Some(join_in_room_project(project, host, app_state.clone(), cx));
 6537            }
 6538
 6539            None
 6540        })?;
 6541        if let Some(task) = task {
 6542            task.await?;
 6543        }
 6544        return anyhow::Ok(true);
 6545    }
 6546
 6547    if should_prompt {
 6548        if let Some(workspace) = requesting_window {
 6549            let answer = workspace
 6550                .update(cx, |_, window, cx| {
 6551                    window.prompt(
 6552                        PromptLevel::Warning,
 6553                        "Do you want to switch channels?",
 6554                        Some("Leaving this call will unshare your current project."),
 6555                        &["Yes, Join Channel", "Cancel"],
 6556                        cx,
 6557                    )
 6558                })?
 6559                .await;
 6560
 6561            if answer == Ok(1) {
 6562                return Ok(false);
 6563            }
 6564        } else {
 6565            return Ok(false); // unreachable!() hopefully
 6566        }
 6567    }
 6568
 6569    let client = cx.update(|cx| active_call.read(cx).client())?;
 6570
 6571    let mut client_status = client.status();
 6572
 6573    // this loop will terminate within client::CONNECTION_TIMEOUT seconds.
 6574    'outer: loop {
 6575        let Some(status) = client_status.recv().await else {
 6576            anyhow::bail!("error connecting");
 6577        };
 6578
 6579        match status {
 6580            Status::Connecting
 6581            | Status::Authenticating
 6582            | Status::Reconnecting
 6583            | Status::Reauthenticating => continue,
 6584            Status::Connected { .. } => break 'outer,
 6585            Status::SignedOut => return Err(ErrorCode::SignedOut.into()),
 6586            Status::UpgradeRequired => return Err(ErrorCode::UpgradeRequired.into()),
 6587            Status::ConnectionError | Status::ConnectionLost | Status::ReconnectionError { .. } => {
 6588                return Err(ErrorCode::Disconnected.into());
 6589            }
 6590        }
 6591    }
 6592
 6593    let room = active_call
 6594        .update(cx, |active_call, cx| {
 6595            active_call.join_channel(channel_id, cx)
 6596        })?
 6597        .await?;
 6598
 6599    let Some(room) = room else {
 6600        return anyhow::Ok(true);
 6601    };
 6602
 6603    room.update(cx, |room, _| room.room_update_completed())?
 6604        .await;
 6605
 6606    let task = room.update(cx, |room, cx| {
 6607        if let Some((project, host)) = room.most_active_project(cx) {
 6608            return Some(join_in_room_project(project, host, app_state.clone(), cx));
 6609        }
 6610
 6611        // If you are the first to join a channel, see if you should share your project.
 6612        if room.remote_participants().is_empty() && !room.local_participant_is_guest() {
 6613            if let Some(workspace) = requesting_window {
 6614                let project = workspace.update(cx, |workspace, _, cx| {
 6615                    let project = workspace.project.read(cx);
 6616
 6617                    if !CallSettings::get_global(cx).share_on_join {
 6618                        return None;
 6619                    }
 6620
 6621                    if (project.is_local() || project.is_via_ssh())
 6622                        && project.visible_worktrees(cx).any(|tree| {
 6623                            tree.read(cx)
 6624                                .root_entry()
 6625                                .map_or(false, |entry| entry.is_dir())
 6626                        })
 6627                    {
 6628                        Some(workspace.project.clone())
 6629                    } else {
 6630                        None
 6631                    }
 6632                });
 6633                if let Ok(Some(project)) = project {
 6634                    return Some(cx.spawn(async move |room, cx| {
 6635                        room.update(cx, |room, cx| room.share_project(project, cx))?
 6636                            .await?;
 6637                        Ok(())
 6638                    }));
 6639                }
 6640            }
 6641        }
 6642
 6643        None
 6644    })?;
 6645    if let Some(task) = task {
 6646        task.await?;
 6647        return anyhow::Ok(true);
 6648    }
 6649    anyhow::Ok(false)
 6650}
 6651
 6652pub fn join_channel(
 6653    channel_id: ChannelId,
 6654    app_state: Arc<AppState>,
 6655    requesting_window: Option<WindowHandle<Workspace>>,
 6656    cx: &mut App,
 6657) -> Task<Result<()>> {
 6658    let active_call = ActiveCall::global(cx);
 6659    cx.spawn(async move |cx| {
 6660        let result = join_channel_internal(
 6661            channel_id,
 6662            &app_state,
 6663            requesting_window,
 6664            &active_call,
 6665             cx,
 6666        )
 6667            .await;
 6668
 6669        // join channel succeeded, and opened a window
 6670        if matches!(result, Ok(true)) {
 6671            return anyhow::Ok(());
 6672        }
 6673
 6674        // find an existing workspace to focus and show call controls
 6675        let mut active_window =
 6676            requesting_window.or_else(|| activate_any_workspace_window( cx));
 6677        if active_window.is_none() {
 6678            // no open workspaces, make one to show the error in (blergh)
 6679            let (window_handle, _) = cx
 6680                .update(|cx| {
 6681                    Workspace::new_local(vec![], app_state.clone(), requesting_window, None, cx)
 6682                })?
 6683                .await?;
 6684
 6685            if result.is_ok() {
 6686                cx.update(|cx| {
 6687                    cx.dispatch_action(&OpenChannelNotes);
 6688                }).log_err();
 6689            }
 6690
 6691            active_window = Some(window_handle);
 6692        }
 6693
 6694        if let Err(err) = result {
 6695            log::error!("failed to join channel: {}", err);
 6696            if let Some(active_window) = active_window {
 6697                active_window
 6698                    .update(cx, |_, window, cx| {
 6699                        let detail: SharedString = match err.error_code() {
 6700                            ErrorCode::SignedOut => {
 6701                                "Please sign in to continue.".into()
 6702                            }
 6703                            ErrorCode::UpgradeRequired => {
 6704                                "Your are running an unsupported version of Zed. Please update to continue.".into()
 6705                            }
 6706                            ErrorCode::NoSuchChannel => {
 6707                                "No matching channel was found. Please check the link and try again.".into()
 6708                            }
 6709                            ErrorCode::Forbidden => {
 6710                                "This channel is private, and you do not have access. Please ask someone to add you and try again.".into()
 6711                            }
 6712                            ErrorCode::Disconnected => "Please check your internet connection and try again.".into(),
 6713                            _ => format!("{}\n\nPlease try again.", err).into(),
 6714                        };
 6715                        window.prompt(
 6716                            PromptLevel::Critical,
 6717                            "Failed to join channel",
 6718                            Some(&detail),
 6719                            &["Ok"],
 6720                        cx)
 6721                    })?
 6722                    .await
 6723                    .ok();
 6724            }
 6725        }
 6726
 6727        // return ok, we showed the error to the user.
 6728        anyhow::Ok(())
 6729    })
 6730}
 6731
 6732pub async fn get_any_active_workspace(
 6733    app_state: Arc<AppState>,
 6734    mut cx: AsyncApp,
 6735) -> anyhow::Result<WindowHandle<Workspace>> {
 6736    // find an existing workspace to focus and show call controls
 6737    let active_window = activate_any_workspace_window(&mut cx);
 6738    if active_window.is_none() {
 6739        cx.update(|cx| Workspace::new_local(vec![], app_state.clone(), None, None, cx))?
 6740            .await?;
 6741    }
 6742    activate_any_workspace_window(&mut cx).context("could not open zed")
 6743}
 6744
 6745fn activate_any_workspace_window(cx: &mut AsyncApp) -> Option<WindowHandle<Workspace>> {
 6746    cx.update(|cx| {
 6747        if let Some(workspace_window) = cx
 6748            .active_window()
 6749            .and_then(|window| window.downcast::<Workspace>())
 6750        {
 6751            return Some(workspace_window);
 6752        }
 6753
 6754        for window in cx.windows() {
 6755            if let Some(workspace_window) = window.downcast::<Workspace>() {
 6756                workspace_window
 6757                    .update(cx, |_, window, _| window.activate_window())
 6758                    .ok();
 6759                return Some(workspace_window);
 6760            }
 6761        }
 6762        None
 6763    })
 6764    .ok()
 6765    .flatten()
 6766}
 6767
 6768pub fn local_workspace_windows(cx: &App) -> Vec<WindowHandle<Workspace>> {
 6769    cx.windows()
 6770        .into_iter()
 6771        .filter_map(|window| window.downcast::<Workspace>())
 6772        .filter(|workspace| {
 6773            workspace
 6774                .read(cx)
 6775                .is_ok_and(|workspace| workspace.project.read(cx).is_local())
 6776        })
 6777        .collect()
 6778}
 6779
 6780#[derive(Default)]
 6781pub struct OpenOptions {
 6782    pub visible: Option<OpenVisible>,
 6783    pub focus: Option<bool>,
 6784    pub open_new_workspace: Option<bool>,
 6785    pub replace_window: Option<WindowHandle<Workspace>>,
 6786    pub env: Option<HashMap<String, String>>,
 6787}
 6788
 6789#[allow(clippy::type_complexity)]
 6790pub fn open_paths(
 6791    abs_paths: &[PathBuf],
 6792    app_state: Arc<AppState>,
 6793    open_options: OpenOptions,
 6794    cx: &mut App,
 6795) -> Task<
 6796    anyhow::Result<(
 6797        WindowHandle<Workspace>,
 6798        Vec<Option<anyhow::Result<Box<dyn ItemHandle>>>>,
 6799    )>,
 6800> {
 6801    let abs_paths = abs_paths.to_vec();
 6802    let mut existing = None;
 6803    let mut best_match = None;
 6804    let mut open_visible = OpenVisible::All;
 6805
 6806    cx.spawn(async move |cx| {
 6807        if open_options.open_new_workspace != Some(true) {
 6808            let all_paths = abs_paths.iter().map(|path| app_state.fs.metadata(path));
 6809            let all_metadatas = futures::future::join_all(all_paths)
 6810                .await
 6811                .into_iter()
 6812                .filter_map(|result| result.ok().flatten())
 6813                .collect::<Vec<_>>();
 6814
 6815            cx.update(|cx| {
 6816                for window in local_workspace_windows(&cx) {
 6817                    if let Ok(workspace) = window.read(&cx) {
 6818                        let m = workspace.project.read(&cx).visibility_for_paths(
 6819                            &abs_paths,
 6820                            &all_metadatas,
 6821                            open_options.open_new_workspace == None,
 6822                            cx,
 6823                        );
 6824                        if m > best_match {
 6825                            existing = Some(window);
 6826                            best_match = m;
 6827                        } else if best_match.is_none()
 6828                            && open_options.open_new_workspace == Some(false)
 6829                        {
 6830                            existing = Some(window)
 6831                        }
 6832                    }
 6833                }
 6834            })?;
 6835
 6836            if open_options.open_new_workspace.is_none() && existing.is_none() {
 6837                if all_metadatas.iter().all(|file| !file.is_dir) {
 6838                    cx.update(|cx| {
 6839                        if let Some(window) = cx
 6840                            .active_window()
 6841                            .and_then(|window| window.downcast::<Workspace>())
 6842                        {
 6843                            if let Ok(workspace) = window.read(cx) {
 6844                                let project = workspace.project().read(cx);
 6845                                if project.is_local() && !project.is_via_collab() {
 6846                                    existing = Some(window);
 6847                                    open_visible = OpenVisible::None;
 6848                                    return;
 6849                                }
 6850                            }
 6851                        }
 6852                        for window in local_workspace_windows(cx) {
 6853                            if let Ok(workspace) = window.read(cx) {
 6854                                let project = workspace.project().read(cx);
 6855                                if project.is_via_collab() {
 6856                                    continue;
 6857                                }
 6858                                existing = Some(window);
 6859                                open_visible = OpenVisible::None;
 6860                                break;
 6861                            }
 6862                        }
 6863                    })?;
 6864                }
 6865            }
 6866        }
 6867
 6868        if let Some(existing) = existing {
 6869            let open_task = existing
 6870                .update(cx, |workspace, window, cx| {
 6871                    window.activate_window();
 6872                    workspace.open_paths(
 6873                        abs_paths,
 6874                        OpenOptions {
 6875                            visible: Some(open_visible),
 6876                            ..Default::default()
 6877                        },
 6878                        None,
 6879                        window,
 6880                        cx,
 6881                    )
 6882                })?
 6883                .await;
 6884
 6885            _ = existing.update(cx, |workspace, _, cx| {
 6886                for item in open_task.iter().flatten() {
 6887                    if let Err(e) = item {
 6888                        workspace.show_error(&e, cx);
 6889                    }
 6890                }
 6891            });
 6892
 6893            Ok((existing, open_task))
 6894        } else {
 6895            cx.update(move |cx| {
 6896                Workspace::new_local(
 6897                    abs_paths,
 6898                    app_state.clone(),
 6899                    open_options.replace_window,
 6900                    open_options.env,
 6901                    cx,
 6902                )
 6903            })?
 6904            .await
 6905        }
 6906    })
 6907}
 6908
 6909pub fn open_new(
 6910    open_options: OpenOptions,
 6911    app_state: Arc<AppState>,
 6912    cx: &mut App,
 6913    init: impl FnOnce(&mut Workspace, &mut Window, &mut Context<Workspace>) + 'static + Send,
 6914) -> Task<anyhow::Result<()>> {
 6915    let task = Workspace::new_local(Vec::new(), app_state, None, open_options.env, cx);
 6916    cx.spawn(async move |cx| {
 6917        let (workspace, opened_paths) = task.await?;
 6918        workspace.update(cx, |workspace, window, cx| {
 6919            if opened_paths.is_empty() {
 6920                init(workspace, window, cx)
 6921            }
 6922        })?;
 6923        Ok(())
 6924    })
 6925}
 6926
 6927pub fn create_and_open_local_file(
 6928    path: &'static Path,
 6929    window: &mut Window,
 6930    cx: &mut Context<Workspace>,
 6931    default_content: impl 'static + Send + FnOnce() -> Rope,
 6932) -> Task<Result<Box<dyn ItemHandle>>> {
 6933    cx.spawn_in(window, async move |workspace, cx| {
 6934        let fs = workspace.read_with(cx, |workspace, _| workspace.app_state().fs.clone())?;
 6935        if !fs.is_file(path).await {
 6936            fs.create_file(path, Default::default()).await?;
 6937            fs.save(path, &default_content(), Default::default())
 6938                .await?;
 6939        }
 6940
 6941        let mut items = workspace
 6942            .update_in(cx, |workspace, window, cx| {
 6943                workspace.with_local_workspace(window, cx, |workspace, window, cx| {
 6944                    workspace.open_paths(
 6945                        vec![path.to_path_buf()],
 6946                        OpenOptions {
 6947                            visible: Some(OpenVisible::None),
 6948                            ..Default::default()
 6949                        },
 6950                        None,
 6951                        window,
 6952                        cx,
 6953                    )
 6954                })
 6955            })?
 6956            .await?
 6957            .await;
 6958
 6959        let item = items.pop().flatten();
 6960        item.with_context(|| format!("path {path:?} is not a file"))?
 6961    })
 6962}
 6963
 6964pub fn open_ssh_project_with_new_connection(
 6965    window: WindowHandle<Workspace>,
 6966    connection_options: SshConnectionOptions,
 6967    cancel_rx: oneshot::Receiver<()>,
 6968    delegate: Arc<dyn SshClientDelegate>,
 6969    app_state: Arc<AppState>,
 6970    paths: Vec<PathBuf>,
 6971    cx: &mut App,
 6972) -> Task<Result<()>> {
 6973    cx.spawn(async move |cx| {
 6974        let (serialized_ssh_project, workspace_id, serialized_workspace) =
 6975            serialize_ssh_project(connection_options.clone(), paths.clone(), &cx).await?;
 6976
 6977        let session = match cx
 6978            .update(|cx| {
 6979                remote::SshRemoteClient::new(
 6980                    ConnectionIdentifier::Workspace(workspace_id.0),
 6981                    connection_options,
 6982                    cancel_rx,
 6983                    delegate,
 6984                    cx,
 6985                )
 6986            })?
 6987            .await?
 6988        {
 6989            Some(result) => result,
 6990            None => return Ok(()),
 6991        };
 6992
 6993        let project = cx.update(|cx| {
 6994            project::Project::ssh(
 6995                session,
 6996                app_state.client.clone(),
 6997                app_state.node_runtime.clone(),
 6998                app_state.user_store.clone(),
 6999                app_state.languages.clone(),
 7000                app_state.fs.clone(),
 7001                cx,
 7002            )
 7003        })?;
 7004
 7005        open_ssh_project_inner(
 7006            project,
 7007            paths,
 7008            serialized_ssh_project,
 7009            workspace_id,
 7010            serialized_workspace,
 7011            app_state,
 7012            window,
 7013            cx,
 7014        )
 7015        .await
 7016    })
 7017}
 7018
 7019pub fn open_ssh_project_with_existing_connection(
 7020    connection_options: SshConnectionOptions,
 7021    project: Entity<Project>,
 7022    paths: Vec<PathBuf>,
 7023    app_state: Arc<AppState>,
 7024    window: WindowHandle<Workspace>,
 7025    cx: &mut AsyncApp,
 7026) -> Task<Result<()>> {
 7027    cx.spawn(async move |cx| {
 7028        let (serialized_ssh_project, workspace_id, serialized_workspace) =
 7029            serialize_ssh_project(connection_options.clone(), paths.clone(), &cx).await?;
 7030
 7031        open_ssh_project_inner(
 7032            project,
 7033            paths,
 7034            serialized_ssh_project,
 7035            workspace_id,
 7036            serialized_workspace,
 7037            app_state,
 7038            window,
 7039            cx,
 7040        )
 7041        .await
 7042    })
 7043}
 7044
 7045async fn open_ssh_project_inner(
 7046    project: Entity<Project>,
 7047    paths: Vec<PathBuf>,
 7048    serialized_ssh_project: SerializedSshProject,
 7049    workspace_id: WorkspaceId,
 7050    serialized_workspace: Option<SerializedWorkspace>,
 7051    app_state: Arc<AppState>,
 7052    window: WindowHandle<Workspace>,
 7053    cx: &mut AsyncApp,
 7054) -> Result<()> {
 7055    let toolchains = DB.toolchains(workspace_id).await?;
 7056    for (toolchain, worktree_id, path) in toolchains {
 7057        project
 7058            .update(cx, |this, cx| {
 7059                this.activate_toolchain(ProjectPath { worktree_id, path }, toolchain, cx)
 7060            })?
 7061            .await;
 7062    }
 7063    let mut project_paths_to_open = vec![];
 7064    let mut project_path_errors = vec![];
 7065
 7066    for path in paths {
 7067        let result = cx
 7068            .update(|cx| Workspace::project_path_for_path(project.clone(), &path, true, cx))?
 7069            .await;
 7070        match result {
 7071            Ok((_, project_path)) => {
 7072                project_paths_to_open.push((path.clone(), Some(project_path)));
 7073            }
 7074            Err(error) => {
 7075                project_path_errors.push(error);
 7076            }
 7077        };
 7078    }
 7079
 7080    if project_paths_to_open.is_empty() {
 7081        return Err(project_path_errors.pop().context("no paths given")?);
 7082    }
 7083
 7084    cx.update_window(window.into(), |_, window, cx| {
 7085        window.replace_root(cx, |window, cx| {
 7086            telemetry::event!("SSH Project Opened");
 7087
 7088            let mut workspace =
 7089                Workspace::new(Some(workspace_id), project, app_state.clone(), window, cx);
 7090            workspace.set_serialized_ssh_project(serialized_ssh_project);
 7091            workspace.update_history(cx);
 7092
 7093            if let Some(ref serialized) = serialized_workspace {
 7094                workspace.centered_layout = serialized.centered_layout;
 7095            }
 7096
 7097            workspace
 7098        });
 7099    })?;
 7100
 7101    window
 7102        .update(cx, |_, window, cx| {
 7103            window.activate_window();
 7104            open_items(serialized_workspace, project_paths_to_open, window, cx)
 7105        })?
 7106        .await?;
 7107
 7108    window.update(cx, |workspace, _, cx| {
 7109        for error in project_path_errors {
 7110            if error.error_code() == proto::ErrorCode::DevServerProjectPathDoesNotExist {
 7111                if let Some(path) = error.error_tag("path") {
 7112                    workspace.show_error(&anyhow!("'{path}' does not exist"), cx)
 7113                }
 7114            } else {
 7115                workspace.show_error(&error, cx)
 7116            }
 7117        }
 7118    })?;
 7119
 7120    Ok(())
 7121}
 7122
 7123fn serialize_ssh_project(
 7124    connection_options: SshConnectionOptions,
 7125    paths: Vec<PathBuf>,
 7126    cx: &AsyncApp,
 7127) -> Task<
 7128    Result<(
 7129        SerializedSshProject,
 7130        WorkspaceId,
 7131        Option<SerializedWorkspace>,
 7132    )>,
 7133> {
 7134    cx.background_spawn(async move {
 7135        let serialized_ssh_project = persistence::DB
 7136            .get_or_create_ssh_project(
 7137                connection_options.host.clone(),
 7138                connection_options.port,
 7139                paths
 7140                    .iter()
 7141                    .map(|path| path.to_string_lossy().to_string())
 7142                    .collect::<Vec<_>>(),
 7143                connection_options.username.clone(),
 7144            )
 7145            .await?;
 7146
 7147        let serialized_workspace =
 7148            persistence::DB.workspace_for_ssh_project(&serialized_ssh_project);
 7149
 7150        let workspace_id = if let Some(workspace_id) =
 7151            serialized_workspace.as_ref().map(|workspace| workspace.id)
 7152        {
 7153            workspace_id
 7154        } else {
 7155            persistence::DB.next_id().await?
 7156        };
 7157
 7158        Ok((serialized_ssh_project, workspace_id, serialized_workspace))
 7159    })
 7160}
 7161
 7162pub fn join_in_room_project(
 7163    project_id: u64,
 7164    follow_user_id: u64,
 7165    app_state: Arc<AppState>,
 7166    cx: &mut App,
 7167) -> Task<Result<()>> {
 7168    let windows = cx.windows();
 7169    cx.spawn(async move |cx| {
 7170        let existing_workspace = windows.into_iter().find_map(|window_handle| {
 7171            window_handle
 7172                .downcast::<Workspace>()
 7173                .and_then(|window_handle| {
 7174                    window_handle
 7175                        .update(cx, |workspace, _window, cx| {
 7176                            if workspace.project().read(cx).remote_id() == Some(project_id) {
 7177                                Some(window_handle)
 7178                            } else {
 7179                                None
 7180                            }
 7181                        })
 7182                        .unwrap_or(None)
 7183                })
 7184        });
 7185
 7186        let workspace = if let Some(existing_workspace) = existing_workspace {
 7187            existing_workspace
 7188        } else {
 7189            let active_call = cx.update(|cx| ActiveCall::global(cx))?;
 7190            let room = active_call
 7191                .read_with(cx, |call, _| call.room().cloned())?
 7192                .context("not in a call")?;
 7193            let project = room
 7194                .update(cx, |room, cx| {
 7195                    room.join_project(
 7196                        project_id,
 7197                        app_state.languages.clone(),
 7198                        app_state.fs.clone(),
 7199                        cx,
 7200                    )
 7201                })?
 7202                .await?;
 7203
 7204            let window_bounds_override = window_bounds_env_override();
 7205            cx.update(|cx| {
 7206                let mut options = (app_state.build_window_options)(None, cx);
 7207                options.window_bounds = window_bounds_override.map(WindowBounds::Windowed);
 7208                cx.open_window(options, |window, cx| {
 7209                    cx.new(|cx| {
 7210                        Workspace::new(Default::default(), project, app_state.clone(), window, cx)
 7211                    })
 7212                })
 7213            })??
 7214        };
 7215
 7216        workspace.update(cx, |workspace, window, cx| {
 7217            cx.activate(true);
 7218            window.activate_window();
 7219
 7220            if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
 7221                let follow_peer_id = room
 7222                    .read(cx)
 7223                    .remote_participants()
 7224                    .iter()
 7225                    .find(|(_, participant)| participant.user.id == follow_user_id)
 7226                    .map(|(_, p)| p.peer_id)
 7227                    .or_else(|| {
 7228                        // If we couldn't follow the given user, follow the host instead.
 7229                        let collaborator = workspace
 7230                            .project()
 7231                            .read(cx)
 7232                            .collaborators()
 7233                            .values()
 7234                            .find(|collaborator| collaborator.is_host)?;
 7235                        Some(collaborator.peer_id)
 7236                    });
 7237
 7238                if let Some(follow_peer_id) = follow_peer_id {
 7239                    workspace.follow(follow_peer_id, window, cx);
 7240                }
 7241            }
 7242        })?;
 7243
 7244        anyhow::Ok(())
 7245    })
 7246}
 7247
 7248pub fn reload(reload: &Reload, cx: &mut App) {
 7249    let should_confirm = WorkspaceSettings::get_global(cx).confirm_quit;
 7250    let mut workspace_windows = cx
 7251        .windows()
 7252        .into_iter()
 7253        .filter_map(|window| window.downcast::<Workspace>())
 7254        .collect::<Vec<_>>();
 7255
 7256    // If multiple windows have unsaved changes, and need a save prompt,
 7257    // prompt in the active window before switching to a different window.
 7258    workspace_windows.sort_by_key(|window| window.is_active(cx) == Some(false));
 7259
 7260    let mut prompt = None;
 7261    if let (true, Some(window)) = (should_confirm, workspace_windows.first()) {
 7262        prompt = window
 7263            .update(cx, |_, window, cx| {
 7264                window.prompt(
 7265                    PromptLevel::Info,
 7266                    "Are you sure you want to restart?",
 7267                    None,
 7268                    &["Restart", "Cancel"],
 7269                    cx,
 7270                )
 7271            })
 7272            .ok();
 7273    }
 7274
 7275    let binary_path = reload.binary_path.clone();
 7276    cx.spawn(async move |cx| {
 7277        if let Some(prompt) = prompt {
 7278            let answer = prompt.await?;
 7279            if answer != 0 {
 7280                return Ok(());
 7281            }
 7282        }
 7283
 7284        // If the user cancels any save prompt, then keep the app open.
 7285        for window in workspace_windows {
 7286            if let Ok(should_close) = window.update(cx, |workspace, window, cx| {
 7287                workspace.prepare_to_close(CloseIntent::Quit, window, cx)
 7288            }) {
 7289                if !should_close.await? {
 7290                    return Ok(());
 7291                }
 7292            }
 7293        }
 7294
 7295        cx.update(|cx| cx.restart(binary_path))
 7296    })
 7297    .detach_and_log_err(cx);
 7298}
 7299
 7300fn parse_pixel_position_env_var(value: &str) -> Option<Point<Pixels>> {
 7301    let mut parts = value.split(',');
 7302    let x: usize = parts.next()?.parse().ok()?;
 7303    let y: usize = parts.next()?.parse().ok()?;
 7304    Some(point(px(x as f32), px(y as f32)))
 7305}
 7306
 7307fn parse_pixel_size_env_var(value: &str) -> Option<Size<Pixels>> {
 7308    let mut parts = value.split(',');
 7309    let width: usize = parts.next()?.parse().ok()?;
 7310    let height: usize = parts.next()?.parse().ok()?;
 7311    Some(size(px(width as f32), px(height as f32)))
 7312}
 7313
 7314pub fn client_side_decorations(
 7315    element: impl IntoElement,
 7316    window: &mut Window,
 7317    cx: &mut App,
 7318) -> Stateful<Div> {
 7319    const BORDER_SIZE: Pixels = px(1.0);
 7320    let decorations = window.window_decorations();
 7321
 7322    if matches!(decorations, Decorations::Client { .. }) {
 7323        window.set_client_inset(theme::CLIENT_SIDE_DECORATION_SHADOW);
 7324    }
 7325
 7326    struct GlobalResizeEdge(ResizeEdge);
 7327    impl Global for GlobalResizeEdge {}
 7328
 7329    div()
 7330        .id("window-backdrop")
 7331        .bg(transparent_black())
 7332        .map(|div| match decorations {
 7333            Decorations::Server => div,
 7334            Decorations::Client { tiling, .. } => div
 7335                .when(!(tiling.top || tiling.right), |div| {
 7336                    div.rounded_tr(theme::CLIENT_SIDE_DECORATION_ROUNDING)
 7337                })
 7338                .when(!(tiling.top || tiling.left), |div| {
 7339                    div.rounded_tl(theme::CLIENT_SIDE_DECORATION_ROUNDING)
 7340                })
 7341                .when(!(tiling.bottom || tiling.right), |div| {
 7342                    div.rounded_br(theme::CLIENT_SIDE_DECORATION_ROUNDING)
 7343                })
 7344                .when(!(tiling.bottom || tiling.left), |div| {
 7345                    div.rounded_bl(theme::CLIENT_SIDE_DECORATION_ROUNDING)
 7346                })
 7347                .when(!tiling.top, |div| {
 7348                    div.pt(theme::CLIENT_SIDE_DECORATION_SHADOW)
 7349                })
 7350                .when(!tiling.bottom, |div| {
 7351                    div.pb(theme::CLIENT_SIDE_DECORATION_SHADOW)
 7352                })
 7353                .when(!tiling.left, |div| {
 7354                    div.pl(theme::CLIENT_SIDE_DECORATION_SHADOW)
 7355                })
 7356                .when(!tiling.right, |div| {
 7357                    div.pr(theme::CLIENT_SIDE_DECORATION_SHADOW)
 7358                })
 7359                .on_mouse_move(move |e, window, cx| {
 7360                    let size = window.window_bounds().get_bounds().size;
 7361                    let pos = e.position;
 7362
 7363                    let new_edge =
 7364                        resize_edge(pos, theme::CLIENT_SIDE_DECORATION_SHADOW, size, tiling);
 7365
 7366                    let edge = cx.try_global::<GlobalResizeEdge>();
 7367                    if new_edge != edge.map(|edge| edge.0) {
 7368                        window
 7369                            .window_handle()
 7370                            .update(cx, |workspace, _, cx| {
 7371                                cx.notify(workspace.entity_id());
 7372                            })
 7373                            .ok();
 7374                    }
 7375                })
 7376                .on_mouse_down(MouseButton::Left, move |e, window, _| {
 7377                    let size = window.window_bounds().get_bounds().size;
 7378                    let pos = e.position;
 7379
 7380                    let edge = match resize_edge(
 7381                        pos,
 7382                        theme::CLIENT_SIDE_DECORATION_SHADOW,
 7383                        size,
 7384                        tiling,
 7385                    ) {
 7386                        Some(value) => value,
 7387                        None => return,
 7388                    };
 7389
 7390                    window.start_window_resize(edge);
 7391                }),
 7392        })
 7393        .size_full()
 7394        .child(
 7395            div()
 7396                .cursor(CursorStyle::Arrow)
 7397                .map(|div| match decorations {
 7398                    Decorations::Server => div,
 7399                    Decorations::Client { tiling } => div
 7400                        .border_color(cx.theme().colors().border)
 7401                        .when(!(tiling.top || tiling.right), |div| {
 7402                            div.rounded_tr(theme::CLIENT_SIDE_DECORATION_ROUNDING)
 7403                        })
 7404                        .when(!(tiling.top || tiling.left), |div| {
 7405                            div.rounded_tl(theme::CLIENT_SIDE_DECORATION_ROUNDING)
 7406                        })
 7407                        .when(!(tiling.bottom || tiling.right), |div| {
 7408                            div.rounded_br(theme::CLIENT_SIDE_DECORATION_ROUNDING)
 7409                        })
 7410                        .when(!(tiling.bottom || tiling.left), |div| {
 7411                            div.rounded_bl(theme::CLIENT_SIDE_DECORATION_ROUNDING)
 7412                        })
 7413                        .when(!tiling.top, |div| div.border_t(BORDER_SIZE))
 7414                        .when(!tiling.bottom, |div| div.border_b(BORDER_SIZE))
 7415                        .when(!tiling.left, |div| div.border_l(BORDER_SIZE))
 7416                        .when(!tiling.right, |div| div.border_r(BORDER_SIZE))
 7417                        .when(!tiling.is_tiled(), |div| {
 7418                            div.shadow(vec![gpui::BoxShadow {
 7419                                color: Hsla {
 7420                                    h: 0.,
 7421                                    s: 0.,
 7422                                    l: 0.,
 7423                                    a: 0.4,
 7424                                },
 7425                                blur_radius: theme::CLIENT_SIDE_DECORATION_SHADOW / 2.,
 7426                                spread_radius: px(0.),
 7427                                offset: point(px(0.0), px(0.0)),
 7428                            }])
 7429                        }),
 7430                })
 7431                .on_mouse_move(|_e, _, cx| {
 7432                    cx.stop_propagation();
 7433                })
 7434                .size_full()
 7435                .child(element),
 7436        )
 7437        .map(|div| match decorations {
 7438            Decorations::Server => div,
 7439            Decorations::Client { tiling, .. } => div.child(
 7440                canvas(
 7441                    |_bounds, window, _| {
 7442                        window.insert_hitbox(
 7443                            Bounds::new(
 7444                                point(px(0.0), px(0.0)),
 7445                                window.window_bounds().get_bounds().size,
 7446                            ),
 7447                            HitboxBehavior::Normal,
 7448                        )
 7449                    },
 7450                    move |_bounds, hitbox, window, cx| {
 7451                        let mouse = window.mouse_position();
 7452                        let size = window.window_bounds().get_bounds().size;
 7453                        let Some(edge) =
 7454                            resize_edge(mouse, theme::CLIENT_SIDE_DECORATION_SHADOW, size, tiling)
 7455                        else {
 7456                            return;
 7457                        };
 7458                        cx.set_global(GlobalResizeEdge(edge));
 7459                        window.set_cursor_style(
 7460                            match edge {
 7461                                ResizeEdge::Top | ResizeEdge::Bottom => CursorStyle::ResizeUpDown,
 7462                                ResizeEdge::Left | ResizeEdge::Right => {
 7463                                    CursorStyle::ResizeLeftRight
 7464                                }
 7465                                ResizeEdge::TopLeft | ResizeEdge::BottomRight => {
 7466                                    CursorStyle::ResizeUpLeftDownRight
 7467                                }
 7468                                ResizeEdge::TopRight | ResizeEdge::BottomLeft => {
 7469                                    CursorStyle::ResizeUpRightDownLeft
 7470                                }
 7471                            },
 7472                            &hitbox,
 7473                        );
 7474                    },
 7475                )
 7476                .size_full()
 7477                .absolute(),
 7478            ),
 7479        })
 7480}
 7481
 7482fn resize_edge(
 7483    pos: Point<Pixels>,
 7484    shadow_size: Pixels,
 7485    window_size: Size<Pixels>,
 7486    tiling: Tiling,
 7487) -> Option<ResizeEdge> {
 7488    let bounds = Bounds::new(Point::default(), window_size).inset(shadow_size * 1.5);
 7489    if bounds.contains(&pos) {
 7490        return None;
 7491    }
 7492
 7493    let corner_size = size(shadow_size * 1.5, shadow_size * 1.5);
 7494    let top_left_bounds = Bounds::new(Point::new(px(0.), px(0.)), corner_size);
 7495    if !tiling.top && top_left_bounds.contains(&pos) {
 7496        return Some(ResizeEdge::TopLeft);
 7497    }
 7498
 7499    let top_right_bounds = Bounds::new(
 7500        Point::new(window_size.width - corner_size.width, px(0.)),
 7501        corner_size,
 7502    );
 7503    if !tiling.top && top_right_bounds.contains(&pos) {
 7504        return Some(ResizeEdge::TopRight);
 7505    }
 7506
 7507    let bottom_left_bounds = Bounds::new(
 7508        Point::new(px(0.), window_size.height - corner_size.height),
 7509        corner_size,
 7510    );
 7511    if !tiling.bottom && bottom_left_bounds.contains(&pos) {
 7512        return Some(ResizeEdge::BottomLeft);
 7513    }
 7514
 7515    let bottom_right_bounds = Bounds::new(
 7516        Point::new(
 7517            window_size.width - corner_size.width,
 7518            window_size.height - corner_size.height,
 7519        ),
 7520        corner_size,
 7521    );
 7522    if !tiling.bottom && bottom_right_bounds.contains(&pos) {
 7523        return Some(ResizeEdge::BottomRight);
 7524    }
 7525
 7526    if !tiling.top && pos.y < shadow_size {
 7527        Some(ResizeEdge::Top)
 7528    } else if !tiling.bottom && pos.y > window_size.height - shadow_size {
 7529        Some(ResizeEdge::Bottom)
 7530    } else if !tiling.left && pos.x < shadow_size {
 7531        Some(ResizeEdge::Left)
 7532    } else if !tiling.right && pos.x > window_size.width - shadow_size {
 7533        Some(ResizeEdge::Right)
 7534    } else {
 7535        None
 7536    }
 7537}
 7538
 7539fn join_pane_into_active(
 7540    active_pane: &Entity<Pane>,
 7541    pane: &Entity<Pane>,
 7542    window: &mut Window,
 7543    cx: &mut App,
 7544) {
 7545    if pane == active_pane {
 7546        return;
 7547    } else if pane.read(cx).items_len() == 0 {
 7548        pane.update(cx, |_, cx| {
 7549            cx.emit(pane::Event::Remove {
 7550                focus_on_pane: None,
 7551            });
 7552        })
 7553    } else {
 7554        move_all_items(pane, active_pane, window, cx);
 7555    }
 7556}
 7557
 7558fn move_all_items(
 7559    from_pane: &Entity<Pane>,
 7560    to_pane: &Entity<Pane>,
 7561    window: &mut Window,
 7562    cx: &mut App,
 7563) {
 7564    let destination_is_different = from_pane != to_pane;
 7565    let mut moved_items = 0;
 7566    for (item_ix, item_handle) in from_pane
 7567        .read(cx)
 7568        .items()
 7569        .enumerate()
 7570        .map(|(ix, item)| (ix, item.clone()))
 7571        .collect::<Vec<_>>()
 7572    {
 7573        let ix = item_ix - moved_items;
 7574        if destination_is_different {
 7575            // Close item from previous pane
 7576            from_pane.update(cx, |source, cx| {
 7577                source.remove_item_and_focus_on_pane(ix, false, to_pane.clone(), window, cx);
 7578            });
 7579            moved_items += 1;
 7580        }
 7581
 7582        // This automatically removes duplicate items in the pane
 7583        to_pane.update(cx, |destination, cx| {
 7584            destination.add_item(item_handle, true, true, None, window, cx);
 7585            window.focus(&destination.focus_handle(cx))
 7586        });
 7587    }
 7588}
 7589
 7590pub fn move_item(
 7591    source: &Entity<Pane>,
 7592    destination: &Entity<Pane>,
 7593    item_id_to_move: EntityId,
 7594    destination_index: usize,
 7595    activate: bool,
 7596    window: &mut Window,
 7597    cx: &mut App,
 7598) {
 7599    let Some((item_ix, item_handle)) = source
 7600        .read(cx)
 7601        .items()
 7602        .enumerate()
 7603        .find(|(_, item_handle)| item_handle.item_id() == item_id_to_move)
 7604        .map(|(ix, item)| (ix, item.clone()))
 7605    else {
 7606        // Tab was closed during drag
 7607        return;
 7608    };
 7609
 7610    if source != destination {
 7611        // Close item from previous pane
 7612        source.update(cx, |source, cx| {
 7613            source.remove_item_and_focus_on_pane(item_ix, false, destination.clone(), window, cx);
 7614        });
 7615    }
 7616
 7617    // This automatically removes duplicate items in the pane
 7618    destination.update(cx, |destination, cx| {
 7619        destination.add_item_inner(
 7620            item_handle,
 7621            activate,
 7622            activate,
 7623            activate,
 7624            Some(destination_index),
 7625            window,
 7626            cx,
 7627        );
 7628        if activate {
 7629            window.focus(&destination.focus_handle(cx))
 7630        }
 7631    });
 7632}
 7633
 7634pub fn move_active_item(
 7635    source: &Entity<Pane>,
 7636    destination: &Entity<Pane>,
 7637    focus_destination: bool,
 7638    close_if_empty: bool,
 7639    window: &mut Window,
 7640    cx: &mut App,
 7641) {
 7642    if source == destination {
 7643        return;
 7644    }
 7645    let Some(active_item) = source.read(cx).active_item() else {
 7646        return;
 7647    };
 7648    source.update(cx, |source_pane, cx| {
 7649        let item_id = active_item.item_id();
 7650        source_pane.remove_item(item_id, false, close_if_empty, window, cx);
 7651        destination.update(cx, |target_pane, cx| {
 7652            target_pane.add_item(
 7653                active_item,
 7654                focus_destination,
 7655                focus_destination,
 7656                Some(target_pane.items_len()),
 7657                window,
 7658                cx,
 7659            );
 7660        });
 7661    });
 7662}
 7663
 7664pub fn clone_active_item(
 7665    workspace_id: Option<WorkspaceId>,
 7666    source: &Entity<Pane>,
 7667    destination: &Entity<Pane>,
 7668    focus_destination: bool,
 7669    window: &mut Window,
 7670    cx: &mut App,
 7671) {
 7672    if source == destination {
 7673        return;
 7674    }
 7675    let Some(active_item) = source.read(cx).active_item() else {
 7676        return;
 7677    };
 7678    destination.update(cx, |target_pane, cx| {
 7679        let Some(clone) = active_item.clone_on_split(workspace_id, window, cx) else {
 7680            return;
 7681        };
 7682        target_pane.add_item(
 7683            clone,
 7684            focus_destination,
 7685            focus_destination,
 7686            Some(target_pane.items_len()),
 7687            window,
 7688            cx,
 7689        );
 7690    });
 7691}
 7692
 7693#[derive(Debug)]
 7694pub struct WorkspacePosition {
 7695    pub window_bounds: Option<WindowBounds>,
 7696    pub display: Option<Uuid>,
 7697    pub centered_layout: bool,
 7698}
 7699
 7700pub fn ssh_workspace_position_from_db(
 7701    host: String,
 7702    port: Option<u16>,
 7703    user: Option<String>,
 7704    paths_to_open: &[PathBuf],
 7705    cx: &App,
 7706) -> Task<Result<WorkspacePosition>> {
 7707    let paths = paths_to_open
 7708        .iter()
 7709        .map(|path| path.to_string_lossy().to_string())
 7710        .collect::<Vec<_>>();
 7711
 7712    cx.background_spawn(async move {
 7713        let serialized_ssh_project = persistence::DB
 7714            .get_or_create_ssh_project(host, port, paths, user)
 7715            .await
 7716            .context("fetching serialized ssh project")?;
 7717        let serialized_workspace =
 7718            persistence::DB.workspace_for_ssh_project(&serialized_ssh_project);
 7719
 7720        let (window_bounds, display) = if let Some(bounds) = window_bounds_env_override() {
 7721            (Some(WindowBounds::Windowed(bounds)), None)
 7722        } else {
 7723            let restorable_bounds = serialized_workspace
 7724                .as_ref()
 7725                .and_then(|workspace| Some((workspace.display?, workspace.window_bounds?)))
 7726                .or_else(|| {
 7727                    let (display, window_bounds) = DB.last_window().log_err()?;
 7728                    Some((display?, window_bounds?))
 7729                });
 7730
 7731            if let Some((serialized_display, serialized_status)) = restorable_bounds {
 7732                (Some(serialized_status.0), Some(serialized_display))
 7733            } else {
 7734                (None, None)
 7735            }
 7736        };
 7737
 7738        let centered_layout = serialized_workspace
 7739            .as_ref()
 7740            .map(|w| w.centered_layout)
 7741            .unwrap_or(false);
 7742
 7743        Ok(WorkspacePosition {
 7744            window_bounds,
 7745            display,
 7746            centered_layout,
 7747        })
 7748    })
 7749}
 7750
 7751pub fn with_active_or_new_workspace(
 7752    cx: &mut App,
 7753    f: impl FnOnce(&mut Workspace, &mut Window, &mut Context<Workspace>) + Send + 'static,
 7754) {
 7755    match cx.active_window().and_then(|w| w.downcast::<Workspace>()) {
 7756        Some(workspace) => {
 7757            cx.defer(move |cx| {
 7758                workspace
 7759                    .update(cx, |workspace, window, cx| f(workspace, window, cx))
 7760                    .log_err();
 7761            });
 7762        }
 7763        None => {
 7764            let app_state = AppState::global(cx);
 7765            if let Some(app_state) = app_state.upgrade() {
 7766                open_new(
 7767                    OpenOptions::default(),
 7768                    app_state,
 7769                    cx,
 7770                    move |workspace, window, cx| f(workspace, window, cx),
 7771                )
 7772                .detach_and_log_err(cx);
 7773            }
 7774        }
 7775    }
 7776}
 7777
 7778#[cfg(test)]
 7779mod tests {
 7780    use std::{cell::RefCell, rc::Rc};
 7781
 7782    use super::*;
 7783    use crate::{
 7784        dock::{PanelEvent, test::TestPanel},
 7785        item::{
 7786            ItemEvent,
 7787            test::{TestItem, TestProjectItem},
 7788        },
 7789    };
 7790    use fs::FakeFs;
 7791    use gpui::{
 7792        DismissEvent, Empty, EventEmitter, FocusHandle, Focusable, Render, TestAppContext,
 7793        UpdateGlobal, VisualTestContext, px,
 7794    };
 7795    use project::{Project, ProjectEntryId};
 7796    use serde_json::json;
 7797    use settings::SettingsStore;
 7798
 7799    #[gpui::test]
 7800    async fn test_tab_disambiguation(cx: &mut TestAppContext) {
 7801        init_test(cx);
 7802
 7803        let fs = FakeFs::new(cx.executor());
 7804        let project = Project::test(fs, [], cx).await;
 7805        let (workspace, cx) =
 7806            cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
 7807
 7808        // Adding an item with no ambiguity renders the tab without detail.
 7809        let item1 = cx.new(|cx| {
 7810            let mut item = TestItem::new(cx);
 7811            item.tab_descriptions = Some(vec!["c", "b1/c", "a/b1/c"]);
 7812            item
 7813        });
 7814        workspace.update_in(cx, |workspace, window, cx| {
 7815            workspace.add_item_to_active_pane(Box::new(item1.clone()), None, true, window, cx);
 7816        });
 7817        item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(0)));
 7818
 7819        // Adding an item that creates ambiguity increases the level of detail on
 7820        // both tabs.
 7821        let item2 = cx.new_window_entity(|_window, cx| {
 7822            let mut item = TestItem::new(cx);
 7823            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
 7824            item
 7825        });
 7826        workspace.update_in(cx, |workspace, window, cx| {
 7827            workspace.add_item_to_active_pane(Box::new(item2.clone()), None, true, window, cx);
 7828        });
 7829        item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
 7830        item2.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
 7831
 7832        // Adding an item that creates ambiguity increases the level of detail only
 7833        // on the ambiguous tabs. In this case, the ambiguity can't be resolved so
 7834        // we stop at the highest detail available.
 7835        let item3 = cx.new(|cx| {
 7836            let mut item = TestItem::new(cx);
 7837            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
 7838            item
 7839        });
 7840        workspace.update_in(cx, |workspace, window, cx| {
 7841            workspace.add_item_to_active_pane(Box::new(item3.clone()), None, true, window, cx);
 7842        });
 7843        item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
 7844        item2.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
 7845        item3.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
 7846    }
 7847
 7848    #[gpui::test]
 7849    async fn test_tracking_active_path(cx: &mut TestAppContext) {
 7850        init_test(cx);
 7851
 7852        let fs = FakeFs::new(cx.executor());
 7853        fs.insert_tree(
 7854            "/root1",
 7855            json!({
 7856                "one.txt": "",
 7857                "two.txt": "",
 7858            }),
 7859        )
 7860        .await;
 7861        fs.insert_tree(
 7862            "/root2",
 7863            json!({
 7864                "three.txt": "",
 7865            }),
 7866        )
 7867        .await;
 7868
 7869        let project = Project::test(fs, ["root1".as_ref()], cx).await;
 7870        let (workspace, cx) =
 7871            cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
 7872        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
 7873        let worktree_id = project.update(cx, |project, cx| {
 7874            project.worktrees(cx).next().unwrap().read(cx).id()
 7875        });
 7876
 7877        let item1 = cx.new(|cx| {
 7878            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "one.txt", cx)])
 7879        });
 7880        let item2 = cx.new(|cx| {
 7881            TestItem::new(cx).with_project_items(&[TestProjectItem::new(2, "two.txt", cx)])
 7882        });
 7883
 7884        // Add an item to an empty pane
 7885        workspace.update_in(cx, |workspace, window, cx| {
 7886            workspace.add_item_to_active_pane(Box::new(item1), None, true, window, cx)
 7887        });
 7888        project.update(cx, |project, cx| {
 7889            assert_eq!(
 7890                project.active_entry(),
 7891                project
 7892                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
 7893                    .map(|e| e.id)
 7894            );
 7895        });
 7896        assert_eq!(cx.window_title().as_deref(), Some("root1 — one.txt"));
 7897
 7898        // Add a second item to a non-empty pane
 7899        workspace.update_in(cx, |workspace, window, cx| {
 7900            workspace.add_item_to_active_pane(Box::new(item2), None, true, window, cx)
 7901        });
 7902        assert_eq!(cx.window_title().as_deref(), Some("root1 — two.txt"));
 7903        project.update(cx, |project, cx| {
 7904            assert_eq!(
 7905                project.active_entry(),
 7906                project
 7907                    .entry_for_path(&(worktree_id, "two.txt").into(), cx)
 7908                    .map(|e| e.id)
 7909            );
 7910        });
 7911
 7912        // Close the active item
 7913        pane.update_in(cx, |pane, window, cx| {
 7914            pane.close_active_item(&Default::default(), window, cx)
 7915        })
 7916        .await
 7917        .unwrap();
 7918        assert_eq!(cx.window_title().as_deref(), Some("root1 — one.txt"));
 7919        project.update(cx, |project, cx| {
 7920            assert_eq!(
 7921                project.active_entry(),
 7922                project
 7923                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
 7924                    .map(|e| e.id)
 7925            );
 7926        });
 7927
 7928        // Add a project folder
 7929        project
 7930            .update(cx, |project, cx| {
 7931                project.find_or_create_worktree("root2", true, cx)
 7932            })
 7933            .await
 7934            .unwrap();
 7935        assert_eq!(cx.window_title().as_deref(), Some("root1, root2 — one.txt"));
 7936
 7937        // Remove a project folder
 7938        project.update(cx, |project, cx| project.remove_worktree(worktree_id, cx));
 7939        assert_eq!(cx.window_title().as_deref(), Some("root2 — one.txt"));
 7940    }
 7941
 7942    #[gpui::test]
 7943    async fn test_close_window(cx: &mut TestAppContext) {
 7944        init_test(cx);
 7945
 7946        let fs = FakeFs::new(cx.executor());
 7947        fs.insert_tree("/root", json!({ "one": "" })).await;
 7948
 7949        let project = Project::test(fs, ["root".as_ref()], cx).await;
 7950        let (workspace, cx) =
 7951            cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
 7952
 7953        // When there are no dirty items, there's nothing to do.
 7954        let item1 = cx.new(TestItem::new);
 7955        workspace.update_in(cx, |w, window, cx| {
 7956            w.add_item_to_active_pane(Box::new(item1.clone()), None, true, window, cx)
 7957        });
 7958        let task = workspace.update_in(cx, |w, window, cx| {
 7959            w.prepare_to_close(CloseIntent::CloseWindow, window, cx)
 7960        });
 7961        assert!(task.await.unwrap());
 7962
 7963        // When there are dirty untitled items, prompt to save each one. If the user
 7964        // cancels any prompt, then abort.
 7965        let item2 = cx.new(|cx| TestItem::new(cx).with_dirty(true));
 7966        let item3 = cx.new(|cx| {
 7967            TestItem::new(cx)
 7968                .with_dirty(true)
 7969                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
 7970        });
 7971        workspace.update_in(cx, |w, window, cx| {
 7972            w.add_item_to_active_pane(Box::new(item2.clone()), None, true, window, cx);
 7973            w.add_item_to_active_pane(Box::new(item3.clone()), None, true, window, cx);
 7974        });
 7975        let task = workspace.update_in(cx, |w, window, cx| {
 7976            w.prepare_to_close(CloseIntent::CloseWindow, window, cx)
 7977        });
 7978        cx.executor().run_until_parked();
 7979        cx.simulate_prompt_answer("Cancel"); // cancel save all
 7980        cx.executor().run_until_parked();
 7981        assert!(!cx.has_pending_prompt());
 7982        assert!(!task.await.unwrap());
 7983    }
 7984
 7985    #[gpui::test]
 7986    async fn test_close_window_with_serializable_items(cx: &mut TestAppContext) {
 7987        init_test(cx);
 7988
 7989        // Register TestItem as a serializable item
 7990        cx.update(|cx| {
 7991            register_serializable_item::<TestItem>(cx);
 7992        });
 7993
 7994        let fs = FakeFs::new(cx.executor());
 7995        fs.insert_tree("/root", json!({ "one": "" })).await;
 7996
 7997        let project = Project::test(fs, ["root".as_ref()], cx).await;
 7998        let (workspace, cx) =
 7999            cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
 8000
 8001        // When there are dirty untitled items, but they can serialize, then there is no prompt.
 8002        let item1 = cx.new(|cx| {
 8003            TestItem::new(cx)
 8004                .with_dirty(true)
 8005                .with_serialize(|| Some(Task::ready(Ok(()))))
 8006        });
 8007        let item2 = cx.new(|cx| {
 8008            TestItem::new(cx)
 8009                .with_dirty(true)
 8010                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
 8011                .with_serialize(|| Some(Task::ready(Ok(()))))
 8012        });
 8013        workspace.update_in(cx, |w, window, cx| {
 8014            w.add_item_to_active_pane(Box::new(item1.clone()), None, true, window, cx);
 8015            w.add_item_to_active_pane(Box::new(item2.clone()), None, true, window, cx);
 8016        });
 8017        let task = workspace.update_in(cx, |w, window, cx| {
 8018            w.prepare_to_close(CloseIntent::CloseWindow, window, cx)
 8019        });
 8020        assert!(task.await.unwrap());
 8021    }
 8022
 8023    #[gpui::test]
 8024    async fn test_close_pane_items(cx: &mut TestAppContext) {
 8025        init_test(cx);
 8026
 8027        let fs = FakeFs::new(cx.executor());
 8028
 8029        let project = Project::test(fs, None, cx).await;
 8030        let (workspace, cx) =
 8031            cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
 8032
 8033        let item1 = cx.new(|cx| {
 8034            TestItem::new(cx)
 8035                .with_dirty(true)
 8036                .with_project_items(&[dirty_project_item(1, "1.txt", cx)])
 8037        });
 8038        let item2 = cx.new(|cx| {
 8039            TestItem::new(cx)
 8040                .with_dirty(true)
 8041                .with_conflict(true)
 8042                .with_project_items(&[dirty_project_item(2, "2.txt", cx)])
 8043        });
 8044        let item3 = cx.new(|cx| {
 8045            TestItem::new(cx)
 8046                .with_dirty(true)
 8047                .with_conflict(true)
 8048                .with_project_items(&[dirty_project_item(3, "3.txt", cx)])
 8049        });
 8050        let item4 = cx.new(|cx| {
 8051            TestItem::new(cx).with_dirty(true).with_project_items(&[{
 8052                let project_item = TestProjectItem::new_untitled(cx);
 8053                project_item.update(cx, |project_item, _| project_item.is_dirty = true);
 8054                project_item
 8055            }])
 8056        });
 8057        let pane = workspace.update_in(cx, |workspace, window, cx| {
 8058            workspace.add_item_to_active_pane(Box::new(item1.clone()), None, true, window, cx);
 8059            workspace.add_item_to_active_pane(Box::new(item2.clone()), None, true, window, cx);
 8060            workspace.add_item_to_active_pane(Box::new(item3.clone()), None, true, window, cx);
 8061            workspace.add_item_to_active_pane(Box::new(item4.clone()), None, true, window, cx);
 8062            workspace.active_pane().clone()
 8063        });
 8064
 8065        let close_items = pane.update_in(cx, |pane, window, cx| {
 8066            pane.activate_item(1, true, true, window, cx);
 8067            assert_eq!(pane.active_item().unwrap().item_id(), item2.item_id());
 8068            let item1_id = item1.item_id();
 8069            let item3_id = item3.item_id();
 8070            let item4_id = item4.item_id();
 8071            pane.close_items(window, cx, SaveIntent::Close, move |id| {
 8072                [item1_id, item3_id, item4_id].contains(&id)
 8073            })
 8074        });
 8075        cx.executor().run_until_parked();
 8076
 8077        assert!(cx.has_pending_prompt());
 8078        cx.simulate_prompt_answer("Save all");
 8079
 8080        cx.executor().run_until_parked();
 8081
 8082        // Item 1 is saved. There's a prompt to save item 3.
 8083        pane.update(cx, |pane, cx| {
 8084            assert_eq!(item1.read(cx).save_count, 1);
 8085            assert_eq!(item1.read(cx).save_as_count, 0);
 8086            assert_eq!(item1.read(cx).reload_count, 0);
 8087            assert_eq!(pane.items_len(), 3);
 8088            assert_eq!(pane.active_item().unwrap().item_id(), item3.item_id());
 8089        });
 8090        assert!(cx.has_pending_prompt());
 8091
 8092        // Cancel saving item 3.
 8093        cx.simulate_prompt_answer("Discard");
 8094        cx.executor().run_until_parked();
 8095
 8096        // Item 3 is reloaded. There's a prompt to save item 4.
 8097        pane.update(cx, |pane, cx| {
 8098            assert_eq!(item3.read(cx).save_count, 0);
 8099            assert_eq!(item3.read(cx).save_as_count, 0);
 8100            assert_eq!(item3.read(cx).reload_count, 1);
 8101            assert_eq!(pane.items_len(), 2);
 8102            assert_eq!(pane.active_item().unwrap().item_id(), item4.item_id());
 8103        });
 8104
 8105        // There's a prompt for a path for item 4.
 8106        cx.simulate_new_path_selection(|_| Some(Default::default()));
 8107        close_items.await.unwrap();
 8108
 8109        // The requested items are closed.
 8110        pane.update(cx, |pane, cx| {
 8111            assert_eq!(item4.read(cx).save_count, 0);
 8112            assert_eq!(item4.read(cx).save_as_count, 1);
 8113            assert_eq!(item4.read(cx).reload_count, 0);
 8114            assert_eq!(pane.items_len(), 1);
 8115            assert_eq!(pane.active_item().unwrap().item_id(), item2.item_id());
 8116        });
 8117    }
 8118
 8119    #[gpui::test]
 8120    async fn test_prompting_to_save_only_on_last_item_for_entry(cx: &mut TestAppContext) {
 8121        init_test(cx);
 8122
 8123        let fs = FakeFs::new(cx.executor());
 8124        let project = Project::test(fs, [], cx).await;
 8125        let (workspace, cx) =
 8126            cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
 8127
 8128        // Create several workspace items with single project entries, and two
 8129        // workspace items with multiple project entries.
 8130        let single_entry_items = (0..=4)
 8131            .map(|project_entry_id| {
 8132                cx.new(|cx| {
 8133                    TestItem::new(cx)
 8134                        .with_dirty(true)
 8135                        .with_project_items(&[dirty_project_item(
 8136                            project_entry_id,
 8137                            &format!("{project_entry_id}.txt"),
 8138                            cx,
 8139                        )])
 8140                })
 8141            })
 8142            .collect::<Vec<_>>();
 8143        let item_2_3 = cx.new(|cx| {
 8144            TestItem::new(cx)
 8145                .with_dirty(true)
 8146                .with_singleton(false)
 8147                .with_project_items(&[
 8148                    single_entry_items[2].read(cx).project_items[0].clone(),
 8149                    single_entry_items[3].read(cx).project_items[0].clone(),
 8150                ])
 8151        });
 8152        let item_3_4 = cx.new(|cx| {
 8153            TestItem::new(cx)
 8154                .with_dirty(true)
 8155                .with_singleton(false)
 8156                .with_project_items(&[
 8157                    single_entry_items[3].read(cx).project_items[0].clone(),
 8158                    single_entry_items[4].read(cx).project_items[0].clone(),
 8159                ])
 8160        });
 8161
 8162        // Create two panes that contain the following project entries:
 8163        //   left pane:
 8164        //     multi-entry items:   (2, 3)
 8165        //     single-entry items:  0, 2, 3, 4
 8166        //   right pane:
 8167        //     single-entry items:  4, 1
 8168        //     multi-entry items:   (3, 4)
 8169        let (left_pane, right_pane) = workspace.update_in(cx, |workspace, window, cx| {
 8170            let left_pane = workspace.active_pane().clone();
 8171            workspace.add_item_to_active_pane(Box::new(item_2_3.clone()), None, true, window, cx);
 8172            workspace.add_item_to_active_pane(
 8173                single_entry_items[0].boxed_clone(),
 8174                None,
 8175                true,
 8176                window,
 8177                cx,
 8178            );
 8179            workspace.add_item_to_active_pane(
 8180                single_entry_items[2].boxed_clone(),
 8181                None,
 8182                true,
 8183                window,
 8184                cx,
 8185            );
 8186            workspace.add_item_to_active_pane(
 8187                single_entry_items[3].boxed_clone(),
 8188                None,
 8189                true,
 8190                window,
 8191                cx,
 8192            );
 8193            workspace.add_item_to_active_pane(
 8194                single_entry_items[4].boxed_clone(),
 8195                None,
 8196                true,
 8197                window,
 8198                cx,
 8199            );
 8200
 8201            let right_pane = workspace
 8202                .split_and_clone(left_pane.clone(), SplitDirection::Right, window, cx)
 8203                .unwrap();
 8204
 8205            right_pane.update(cx, |pane, cx| {
 8206                pane.add_item(
 8207                    single_entry_items[1].boxed_clone(),
 8208                    true,
 8209                    true,
 8210                    None,
 8211                    window,
 8212                    cx,
 8213                );
 8214                pane.add_item(Box::new(item_3_4.clone()), true, true, None, window, cx);
 8215            });
 8216
 8217            (left_pane, right_pane)
 8218        });
 8219
 8220        cx.focus(&right_pane);
 8221
 8222        let mut close = right_pane.update_in(cx, |pane, window, cx| {
 8223            pane.close_all_items(&CloseAllItems::default(), window, cx)
 8224                .unwrap()
 8225        });
 8226        cx.executor().run_until_parked();
 8227
 8228        let msg = cx.pending_prompt().unwrap().0;
 8229        assert!(msg.contains("1.txt"));
 8230        assert!(!msg.contains("2.txt"));
 8231        assert!(!msg.contains("3.txt"));
 8232        assert!(!msg.contains("4.txt"));
 8233
 8234        cx.simulate_prompt_answer("Cancel");
 8235        close.await;
 8236
 8237        left_pane
 8238            .update_in(cx, |left_pane, window, cx| {
 8239                left_pane.close_item_by_id(
 8240                    single_entry_items[3].entity_id(),
 8241                    SaveIntent::Skip,
 8242                    window,
 8243                    cx,
 8244                )
 8245            })
 8246            .await
 8247            .unwrap();
 8248
 8249        close = right_pane.update_in(cx, |pane, window, cx| {
 8250            pane.close_all_items(&CloseAllItems::default(), window, cx)
 8251                .unwrap()
 8252        });
 8253        cx.executor().run_until_parked();
 8254
 8255        let details = cx.pending_prompt().unwrap().1;
 8256        assert!(details.contains("1.txt"));
 8257        assert!(!details.contains("2.txt"));
 8258        assert!(details.contains("3.txt"));
 8259        // ideally this assertion could be made, but today we can only
 8260        // save whole items not project items, so the orphaned item 3 causes
 8261        // 4 to be saved too.
 8262        // assert!(!details.contains("4.txt"));
 8263
 8264        cx.simulate_prompt_answer("Save all");
 8265
 8266        cx.executor().run_until_parked();
 8267        close.await;
 8268        right_pane.read_with(cx, |pane, _| {
 8269            assert_eq!(pane.items_len(), 0);
 8270        });
 8271    }
 8272
 8273    #[gpui::test]
 8274    async fn test_autosave(cx: &mut gpui::TestAppContext) {
 8275        init_test(cx);
 8276
 8277        let fs = FakeFs::new(cx.executor());
 8278        let project = Project::test(fs, [], cx).await;
 8279        let (workspace, cx) =
 8280            cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
 8281        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
 8282
 8283        let item = cx.new(|cx| {
 8284            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
 8285        });
 8286        let item_id = item.entity_id();
 8287        workspace.update_in(cx, |workspace, window, cx| {
 8288            workspace.add_item_to_active_pane(Box::new(item.clone()), None, true, window, cx);
 8289        });
 8290
 8291        // Autosave on window change.
 8292        item.update(cx, |item, cx| {
 8293            SettingsStore::update_global(cx, |settings, cx| {
 8294                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
 8295                    settings.autosave = Some(AutosaveSetting::OnWindowChange);
 8296                })
 8297            });
 8298            item.is_dirty = true;
 8299        });
 8300
 8301        // Deactivating the window saves the file.
 8302        cx.deactivate_window();
 8303        item.read_with(cx, |item, _| assert_eq!(item.save_count, 1));
 8304
 8305        // Re-activating the window doesn't save the file.
 8306        cx.update(|window, _| window.activate_window());
 8307        cx.executor().run_until_parked();
 8308        item.read_with(cx, |item, _| assert_eq!(item.save_count, 1));
 8309
 8310        // Autosave on focus change.
 8311        item.update_in(cx, |item, window, cx| {
 8312            cx.focus_self(window);
 8313            SettingsStore::update_global(cx, |settings, cx| {
 8314                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
 8315                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
 8316                })
 8317            });
 8318            item.is_dirty = true;
 8319        });
 8320
 8321        // Blurring the item saves the file.
 8322        item.update_in(cx, |_, window, _| window.blur());
 8323        cx.executor().run_until_parked();
 8324        item.read_with(cx, |item, _| assert_eq!(item.save_count, 2));
 8325
 8326        // Deactivating the window still saves the file.
 8327        item.update_in(cx, |item, window, cx| {
 8328            cx.focus_self(window);
 8329            item.is_dirty = true;
 8330        });
 8331        cx.deactivate_window();
 8332        item.update(cx, |item, _| assert_eq!(item.save_count, 3));
 8333
 8334        // Autosave after delay.
 8335        item.update(cx, |item, cx| {
 8336            SettingsStore::update_global(cx, |settings, cx| {
 8337                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
 8338                    settings.autosave = Some(AutosaveSetting::AfterDelay { milliseconds: 500 });
 8339                })
 8340            });
 8341            item.is_dirty = true;
 8342            cx.emit(ItemEvent::Edit);
 8343        });
 8344
 8345        // Delay hasn't fully expired, so the file is still dirty and unsaved.
 8346        cx.executor().advance_clock(Duration::from_millis(250));
 8347        item.read_with(cx, |item, _| assert_eq!(item.save_count, 3));
 8348
 8349        // After delay expires, the file is saved.
 8350        cx.executor().advance_clock(Duration::from_millis(250));
 8351        item.read_with(cx, |item, _| assert_eq!(item.save_count, 4));
 8352
 8353        // Autosave on focus change, ensuring closing the tab counts as such.
 8354        item.update(cx, |item, cx| {
 8355            SettingsStore::update_global(cx, |settings, cx| {
 8356                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
 8357                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
 8358                })
 8359            });
 8360            item.is_dirty = true;
 8361            for project_item in &mut item.project_items {
 8362                project_item.update(cx, |project_item, _| project_item.is_dirty = true);
 8363            }
 8364        });
 8365
 8366        pane.update_in(cx, |pane, window, cx| {
 8367            pane.close_items(window, cx, SaveIntent::Close, move |id| id == item_id)
 8368        })
 8369        .await
 8370        .unwrap();
 8371        assert!(!cx.has_pending_prompt());
 8372        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
 8373
 8374        // Add the item again, ensuring autosave is prevented if the underlying file has been deleted.
 8375        workspace.update_in(cx, |workspace, window, cx| {
 8376            workspace.add_item_to_active_pane(Box::new(item.clone()), None, true, window, cx);
 8377        });
 8378        item.update_in(cx, |item, window, cx| {
 8379            item.project_items[0].update(cx, |item, _| {
 8380                item.entry_id = None;
 8381            });
 8382            item.is_dirty = true;
 8383            window.blur();
 8384        });
 8385        cx.run_until_parked();
 8386        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
 8387
 8388        // Ensure autosave is prevented for deleted files also when closing the buffer.
 8389        let _close_items = pane.update_in(cx, |pane, window, cx| {
 8390            pane.close_items(window, cx, SaveIntent::Close, move |id| id == item_id)
 8391        });
 8392        cx.run_until_parked();
 8393        assert!(cx.has_pending_prompt());
 8394        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
 8395    }
 8396
 8397    #[gpui::test]
 8398    async fn test_pane_navigation(cx: &mut gpui::TestAppContext) {
 8399        init_test(cx);
 8400
 8401        let fs = FakeFs::new(cx.executor());
 8402
 8403        let project = Project::test(fs, [], cx).await;
 8404        let (workspace, cx) =
 8405            cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
 8406
 8407        let item = cx.new(|cx| {
 8408            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
 8409        });
 8410        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
 8411        let toolbar = pane.read_with(cx, |pane, _| pane.toolbar().clone());
 8412        let toolbar_notify_count = Rc::new(RefCell::new(0));
 8413
 8414        workspace.update_in(cx, |workspace, window, cx| {
 8415            workspace.add_item_to_active_pane(Box::new(item.clone()), None, true, window, cx);
 8416            let toolbar_notification_count = toolbar_notify_count.clone();
 8417            cx.observe_in(&toolbar, window, move |_, _, _, _| {
 8418                *toolbar_notification_count.borrow_mut() += 1
 8419            })
 8420            .detach();
 8421        });
 8422
 8423        pane.read_with(cx, |pane, _| {
 8424            assert!(!pane.can_navigate_backward());
 8425            assert!(!pane.can_navigate_forward());
 8426        });
 8427
 8428        item.update_in(cx, |item, _, cx| {
 8429            item.set_state("one".to_string(), cx);
 8430        });
 8431
 8432        // Toolbar must be notified to re-render the navigation buttons
 8433        assert_eq!(*toolbar_notify_count.borrow(), 1);
 8434
 8435        pane.read_with(cx, |pane, _| {
 8436            assert!(pane.can_navigate_backward());
 8437            assert!(!pane.can_navigate_forward());
 8438        });
 8439
 8440        workspace
 8441            .update_in(cx, |workspace, window, cx| {
 8442                workspace.go_back(pane.downgrade(), window, cx)
 8443            })
 8444            .await
 8445            .unwrap();
 8446
 8447        assert_eq!(*toolbar_notify_count.borrow(), 2);
 8448        pane.read_with(cx, |pane, _| {
 8449            assert!(!pane.can_navigate_backward());
 8450            assert!(pane.can_navigate_forward());
 8451        });
 8452    }
 8453
 8454    #[gpui::test]
 8455    async fn test_toggle_docks_and_panels(cx: &mut gpui::TestAppContext) {
 8456        init_test(cx);
 8457        let fs = FakeFs::new(cx.executor());
 8458
 8459        let project = Project::test(fs, [], cx).await;
 8460        let (workspace, cx) =
 8461            cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
 8462
 8463        let panel = workspace.update_in(cx, |workspace, window, cx| {
 8464            let panel = cx.new(|cx| TestPanel::new(DockPosition::Right, cx));
 8465            workspace.add_panel(panel.clone(), window, cx);
 8466
 8467            workspace
 8468                .right_dock()
 8469                .update(cx, |right_dock, cx| right_dock.set_open(true, window, cx));
 8470
 8471            panel
 8472        });
 8473
 8474        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
 8475        pane.update_in(cx, |pane, window, cx| {
 8476            let item = cx.new(TestItem::new);
 8477            pane.add_item(Box::new(item), true, true, None, window, cx);
 8478        });
 8479
 8480        // Transfer focus from center to panel
 8481        workspace.update_in(cx, |workspace, window, cx| {
 8482            workspace.toggle_panel_focus::<TestPanel>(window, cx);
 8483        });
 8484
 8485        workspace.update_in(cx, |workspace, window, cx| {
 8486            assert!(workspace.right_dock().read(cx).is_open());
 8487            assert!(!panel.is_zoomed(window, cx));
 8488            assert!(panel.read(cx).focus_handle(cx).contains_focused(window, cx));
 8489        });
 8490
 8491        // Transfer focus from panel to center
 8492        workspace.update_in(cx, |workspace, window, cx| {
 8493            workspace.toggle_panel_focus::<TestPanel>(window, cx);
 8494        });
 8495
 8496        workspace.update_in(cx, |workspace, window, cx| {
 8497            assert!(workspace.right_dock().read(cx).is_open());
 8498            assert!(!panel.is_zoomed(window, cx));
 8499            assert!(!panel.read(cx).focus_handle(cx).contains_focused(window, cx));
 8500        });
 8501
 8502        // Close the dock
 8503        workspace.update_in(cx, |workspace, window, cx| {
 8504            workspace.toggle_dock(DockPosition::Right, window, cx);
 8505        });
 8506
 8507        workspace.update_in(cx, |workspace, window, cx| {
 8508            assert!(!workspace.right_dock().read(cx).is_open());
 8509            assert!(!panel.is_zoomed(window, cx));
 8510            assert!(!panel.read(cx).focus_handle(cx).contains_focused(window, cx));
 8511        });
 8512
 8513        // Open the dock
 8514        workspace.update_in(cx, |workspace, window, cx| {
 8515            workspace.toggle_dock(DockPosition::Right, window, cx);
 8516        });
 8517
 8518        workspace.update_in(cx, |workspace, window, cx| {
 8519            assert!(workspace.right_dock().read(cx).is_open());
 8520            assert!(!panel.is_zoomed(window, cx));
 8521            assert!(panel.read(cx).focus_handle(cx).contains_focused(window, cx));
 8522        });
 8523
 8524        // Focus and zoom panel
 8525        panel.update_in(cx, |panel, window, cx| {
 8526            cx.focus_self(window);
 8527            panel.set_zoomed(true, window, cx)
 8528        });
 8529
 8530        workspace.update_in(cx, |workspace, window, cx| {
 8531            assert!(workspace.right_dock().read(cx).is_open());
 8532            assert!(panel.is_zoomed(window, cx));
 8533            assert!(panel.read(cx).focus_handle(cx).contains_focused(window, cx));
 8534        });
 8535
 8536        // Transfer focus to the center closes the dock
 8537        workspace.update_in(cx, |workspace, window, cx| {
 8538            workspace.toggle_panel_focus::<TestPanel>(window, cx);
 8539        });
 8540
 8541        workspace.update_in(cx, |workspace, window, cx| {
 8542            assert!(!workspace.right_dock().read(cx).is_open());
 8543            assert!(panel.is_zoomed(window, cx));
 8544            assert!(!panel.read(cx).focus_handle(cx).contains_focused(window, cx));
 8545        });
 8546
 8547        // Transferring focus back to the panel keeps it zoomed
 8548        workspace.update_in(cx, |workspace, window, cx| {
 8549            workspace.toggle_panel_focus::<TestPanel>(window, cx);
 8550        });
 8551
 8552        workspace.update_in(cx, |workspace, window, cx| {
 8553            assert!(workspace.right_dock().read(cx).is_open());
 8554            assert!(panel.is_zoomed(window, cx));
 8555            assert!(panel.read(cx).focus_handle(cx).contains_focused(window, cx));
 8556        });
 8557
 8558        // Close the dock while it is zoomed
 8559        workspace.update_in(cx, |workspace, window, cx| {
 8560            workspace.toggle_dock(DockPosition::Right, window, cx)
 8561        });
 8562
 8563        workspace.update_in(cx, |workspace, window, cx| {
 8564            assert!(!workspace.right_dock().read(cx).is_open());
 8565            assert!(panel.is_zoomed(window, cx));
 8566            assert!(workspace.zoomed.is_none());
 8567            assert!(!panel.read(cx).focus_handle(cx).contains_focused(window, cx));
 8568        });
 8569
 8570        // Opening the dock, when it's zoomed, retains focus
 8571        workspace.update_in(cx, |workspace, window, cx| {
 8572            workspace.toggle_dock(DockPosition::Right, window, cx)
 8573        });
 8574
 8575        workspace.update_in(cx, |workspace, window, cx| {
 8576            assert!(workspace.right_dock().read(cx).is_open());
 8577            assert!(panel.is_zoomed(window, cx));
 8578            assert!(workspace.zoomed.is_some());
 8579            assert!(panel.read(cx).focus_handle(cx).contains_focused(window, cx));
 8580        });
 8581
 8582        // Unzoom and close the panel, zoom the active pane.
 8583        panel.update_in(cx, |panel, window, cx| panel.set_zoomed(false, window, cx));
 8584        workspace.update_in(cx, |workspace, window, cx| {
 8585            workspace.toggle_dock(DockPosition::Right, window, cx)
 8586        });
 8587        pane.update_in(cx, |pane, window, cx| {
 8588            pane.toggle_zoom(&Default::default(), window, cx)
 8589        });
 8590
 8591        // Opening a dock unzooms the pane.
 8592        workspace.update_in(cx, |workspace, window, cx| {
 8593            workspace.toggle_dock(DockPosition::Right, window, cx)
 8594        });
 8595        workspace.update_in(cx, |workspace, window, cx| {
 8596            let pane = pane.read(cx);
 8597            assert!(!pane.is_zoomed());
 8598            assert!(!pane.focus_handle(cx).is_focused(window));
 8599            assert!(workspace.right_dock().read(cx).is_open());
 8600            assert!(workspace.zoomed.is_none());
 8601        });
 8602    }
 8603
 8604    #[gpui::test]
 8605    async fn test_join_pane_into_next(cx: &mut gpui::TestAppContext) {
 8606        init_test(cx);
 8607
 8608        let fs = FakeFs::new(cx.executor());
 8609
 8610        let project = Project::test(fs, None, cx).await;
 8611        let (workspace, cx) =
 8612            cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
 8613
 8614        // Let's arrange the panes like this:
 8615        //
 8616        // +-----------------------+
 8617        // |         top           |
 8618        // +------+--------+-------+
 8619        // | left | center | right |
 8620        // +------+--------+-------+
 8621        // |        bottom         |
 8622        // +-----------------------+
 8623
 8624        let top_item = cx.new(|cx| {
 8625            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "top.txt", cx)])
 8626        });
 8627        let bottom_item = cx.new(|cx| {
 8628            TestItem::new(cx).with_project_items(&[TestProjectItem::new(2, "bottom.txt", cx)])
 8629        });
 8630        let left_item = cx.new(|cx| {
 8631            TestItem::new(cx).with_project_items(&[TestProjectItem::new(3, "left.txt", cx)])
 8632        });
 8633        let right_item = cx.new(|cx| {
 8634            TestItem::new(cx).with_project_items(&[TestProjectItem::new(4, "right.txt", cx)])
 8635        });
 8636        let center_item = cx.new(|cx| {
 8637            TestItem::new(cx).with_project_items(&[TestProjectItem::new(5, "center.txt", cx)])
 8638        });
 8639
 8640        let top_pane_id = workspace.update_in(cx, |workspace, window, cx| {
 8641            let top_pane_id = workspace.active_pane().entity_id();
 8642            workspace.add_item_to_active_pane(Box::new(top_item.clone()), None, false, window, cx);
 8643            workspace.split_pane(
 8644                workspace.active_pane().clone(),
 8645                SplitDirection::Down,
 8646                window,
 8647                cx,
 8648            );
 8649            top_pane_id
 8650        });
 8651        let bottom_pane_id = workspace.update_in(cx, |workspace, window, cx| {
 8652            let bottom_pane_id = workspace.active_pane().entity_id();
 8653            workspace.add_item_to_active_pane(
 8654                Box::new(bottom_item.clone()),
 8655                None,
 8656                false,
 8657                window,
 8658                cx,
 8659            );
 8660            workspace.split_pane(
 8661                workspace.active_pane().clone(),
 8662                SplitDirection::Up,
 8663                window,
 8664                cx,
 8665            );
 8666            bottom_pane_id
 8667        });
 8668        let left_pane_id = workspace.update_in(cx, |workspace, window, cx| {
 8669            let left_pane_id = workspace.active_pane().entity_id();
 8670            workspace.add_item_to_active_pane(Box::new(left_item.clone()), None, false, window, cx);
 8671            workspace.split_pane(
 8672                workspace.active_pane().clone(),
 8673                SplitDirection::Right,
 8674                window,
 8675                cx,
 8676            );
 8677            left_pane_id
 8678        });
 8679        let right_pane_id = workspace.update_in(cx, |workspace, window, cx| {
 8680            let right_pane_id = workspace.active_pane().entity_id();
 8681            workspace.add_item_to_active_pane(
 8682                Box::new(right_item.clone()),
 8683                None,
 8684                false,
 8685                window,
 8686                cx,
 8687            );
 8688            workspace.split_pane(
 8689                workspace.active_pane().clone(),
 8690                SplitDirection::Left,
 8691                window,
 8692                cx,
 8693            );
 8694            right_pane_id
 8695        });
 8696        let center_pane_id = workspace.update_in(cx, |workspace, window, cx| {
 8697            let center_pane_id = workspace.active_pane().entity_id();
 8698            workspace.add_item_to_active_pane(
 8699                Box::new(center_item.clone()),
 8700                None,
 8701                false,
 8702                window,
 8703                cx,
 8704            );
 8705            center_pane_id
 8706        });
 8707        cx.executor().run_until_parked();
 8708
 8709        workspace.update_in(cx, |workspace, window, cx| {
 8710            assert_eq!(center_pane_id, workspace.active_pane().entity_id());
 8711
 8712            // Join into next from center pane into right
 8713            workspace.join_pane_into_next(workspace.active_pane().clone(), window, cx);
 8714        });
 8715
 8716        workspace.update_in(cx, |workspace, window, cx| {
 8717            let active_pane = workspace.active_pane();
 8718            assert_eq!(right_pane_id, active_pane.entity_id());
 8719            assert_eq!(2, active_pane.read(cx).items_len());
 8720            let item_ids_in_pane =
 8721                HashSet::from_iter(active_pane.read(cx).items().map(|item| item.item_id()));
 8722            assert!(item_ids_in_pane.contains(&center_item.item_id()));
 8723            assert!(item_ids_in_pane.contains(&right_item.item_id()));
 8724
 8725            // Join into next from right pane into bottom
 8726            workspace.join_pane_into_next(workspace.active_pane().clone(), window, cx);
 8727        });
 8728
 8729        workspace.update_in(cx, |workspace, window, cx| {
 8730            let active_pane = workspace.active_pane();
 8731            assert_eq!(bottom_pane_id, active_pane.entity_id());
 8732            assert_eq!(3, active_pane.read(cx).items_len());
 8733            let item_ids_in_pane =
 8734                HashSet::from_iter(active_pane.read(cx).items().map(|item| item.item_id()));
 8735            assert!(item_ids_in_pane.contains(&center_item.item_id()));
 8736            assert!(item_ids_in_pane.contains(&right_item.item_id()));
 8737            assert!(item_ids_in_pane.contains(&bottom_item.item_id()));
 8738
 8739            // Join into next from bottom pane into left
 8740            workspace.join_pane_into_next(workspace.active_pane().clone(), window, cx);
 8741        });
 8742
 8743        workspace.update_in(cx, |workspace, window, cx| {
 8744            let active_pane = workspace.active_pane();
 8745            assert_eq!(left_pane_id, active_pane.entity_id());
 8746            assert_eq!(4, active_pane.read(cx).items_len());
 8747            let item_ids_in_pane =
 8748                HashSet::from_iter(active_pane.read(cx).items().map(|item| item.item_id()));
 8749            assert!(item_ids_in_pane.contains(&center_item.item_id()));
 8750            assert!(item_ids_in_pane.contains(&right_item.item_id()));
 8751            assert!(item_ids_in_pane.contains(&bottom_item.item_id()));
 8752            assert!(item_ids_in_pane.contains(&left_item.item_id()));
 8753
 8754            // Join into next from left pane into top
 8755            workspace.join_pane_into_next(workspace.active_pane().clone(), window, cx);
 8756        });
 8757
 8758        workspace.update_in(cx, |workspace, window, cx| {
 8759            let active_pane = workspace.active_pane();
 8760            assert_eq!(top_pane_id, active_pane.entity_id());
 8761            assert_eq!(5, active_pane.read(cx).items_len());
 8762            let item_ids_in_pane =
 8763                HashSet::from_iter(active_pane.read(cx).items().map(|item| item.item_id()));
 8764            assert!(item_ids_in_pane.contains(&center_item.item_id()));
 8765            assert!(item_ids_in_pane.contains(&right_item.item_id()));
 8766            assert!(item_ids_in_pane.contains(&bottom_item.item_id()));
 8767            assert!(item_ids_in_pane.contains(&left_item.item_id()));
 8768            assert!(item_ids_in_pane.contains(&top_item.item_id()));
 8769
 8770            // Single pane left: no-op
 8771            workspace.join_pane_into_next(workspace.active_pane().clone(), window, cx)
 8772        });
 8773
 8774        workspace.update(cx, |workspace, _cx| {
 8775            let active_pane = workspace.active_pane();
 8776            assert_eq!(top_pane_id, active_pane.entity_id());
 8777        });
 8778    }
 8779
 8780    fn add_an_item_to_active_pane(
 8781        cx: &mut VisualTestContext,
 8782        workspace: &Entity<Workspace>,
 8783        item_id: u64,
 8784    ) -> Entity<TestItem> {
 8785        let item = cx.new(|cx| {
 8786            TestItem::new(cx).with_project_items(&[TestProjectItem::new(
 8787                item_id,
 8788                "item{item_id}.txt",
 8789                cx,
 8790            )])
 8791        });
 8792        workspace.update_in(cx, |workspace, window, cx| {
 8793            workspace.add_item_to_active_pane(Box::new(item.clone()), None, false, window, cx);
 8794        });
 8795        return item;
 8796    }
 8797
 8798    fn split_pane(cx: &mut VisualTestContext, workspace: &Entity<Workspace>) -> Entity<Pane> {
 8799        return workspace.update_in(cx, |workspace, window, cx| {
 8800            let new_pane = workspace.split_pane(
 8801                workspace.active_pane().clone(),
 8802                SplitDirection::Right,
 8803                window,
 8804                cx,
 8805            );
 8806            new_pane
 8807        });
 8808    }
 8809
 8810    #[gpui::test]
 8811    async fn test_join_all_panes(cx: &mut gpui::TestAppContext) {
 8812        init_test(cx);
 8813        let fs = FakeFs::new(cx.executor());
 8814        let project = Project::test(fs, None, cx).await;
 8815        let (workspace, cx) =
 8816            cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
 8817
 8818        add_an_item_to_active_pane(cx, &workspace, 1);
 8819        split_pane(cx, &workspace);
 8820        add_an_item_to_active_pane(cx, &workspace, 2);
 8821        split_pane(cx, &workspace); // empty pane
 8822        split_pane(cx, &workspace);
 8823        let last_item = add_an_item_to_active_pane(cx, &workspace, 3);
 8824
 8825        cx.executor().run_until_parked();
 8826
 8827        workspace.update(cx, |workspace, cx| {
 8828            let num_panes = workspace.panes().len();
 8829            let num_items_in_current_pane = workspace.active_pane().read(cx).items().count();
 8830            let active_item = workspace
 8831                .active_pane()
 8832                .read(cx)
 8833                .active_item()
 8834                .expect("item is in focus");
 8835
 8836            assert_eq!(num_panes, 4);
 8837            assert_eq!(num_items_in_current_pane, 1);
 8838            assert_eq!(active_item.item_id(), last_item.item_id());
 8839        });
 8840
 8841        workspace.update_in(cx, |workspace, window, cx| {
 8842            workspace.join_all_panes(window, cx);
 8843        });
 8844
 8845        workspace.update(cx, |workspace, cx| {
 8846            let num_panes = workspace.panes().len();
 8847            let num_items_in_current_pane = workspace.active_pane().read(cx).items().count();
 8848            let active_item = workspace
 8849                .active_pane()
 8850                .read(cx)
 8851                .active_item()
 8852                .expect("item is in focus");
 8853
 8854            assert_eq!(num_panes, 1);
 8855            assert_eq!(num_items_in_current_pane, 3);
 8856            assert_eq!(active_item.item_id(), last_item.item_id());
 8857        });
 8858    }
 8859    struct TestModal(FocusHandle);
 8860
 8861    impl TestModal {
 8862        fn new(_: &mut Window, cx: &mut Context<Self>) -> Self {
 8863            Self(cx.focus_handle())
 8864        }
 8865    }
 8866
 8867    impl EventEmitter<DismissEvent> for TestModal {}
 8868
 8869    impl Focusable for TestModal {
 8870        fn focus_handle(&self, _cx: &App) -> FocusHandle {
 8871            self.0.clone()
 8872        }
 8873    }
 8874
 8875    impl ModalView for TestModal {}
 8876
 8877    impl Render for TestModal {
 8878        fn render(
 8879            &mut self,
 8880            _window: &mut Window,
 8881            _cx: &mut Context<TestModal>,
 8882        ) -> impl IntoElement {
 8883            div().track_focus(&self.0)
 8884        }
 8885    }
 8886
 8887    #[gpui::test]
 8888    async fn test_panels(cx: &mut gpui::TestAppContext) {
 8889        init_test(cx);
 8890        let fs = FakeFs::new(cx.executor());
 8891
 8892        let project = Project::test(fs, [], cx).await;
 8893        let (workspace, cx) =
 8894            cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
 8895
 8896        let (panel_1, panel_2) = workspace.update_in(cx, |workspace, window, cx| {
 8897            let panel_1 = cx.new(|cx| TestPanel::new(DockPosition::Left, cx));
 8898            workspace.add_panel(panel_1.clone(), window, cx);
 8899            workspace.toggle_dock(DockPosition::Left, window, cx);
 8900            let panel_2 = cx.new(|cx| TestPanel::new(DockPosition::Right, cx));
 8901            workspace.add_panel(panel_2.clone(), window, cx);
 8902            workspace.toggle_dock(DockPosition::Right, window, cx);
 8903
 8904            let left_dock = workspace.left_dock();
 8905            assert_eq!(
 8906                left_dock.read(cx).visible_panel().unwrap().panel_id(),
 8907                panel_1.panel_id()
 8908            );
 8909            assert_eq!(
 8910                left_dock.read(cx).active_panel_size(window, cx).unwrap(),
 8911                panel_1.size(window, cx)
 8912            );
 8913
 8914            left_dock.update(cx, |left_dock, cx| {
 8915                left_dock.resize_active_panel(Some(px(1337.)), window, cx)
 8916            });
 8917            assert_eq!(
 8918                workspace
 8919                    .right_dock()
 8920                    .read(cx)
 8921                    .visible_panel()
 8922                    .unwrap()
 8923                    .panel_id(),
 8924                panel_2.panel_id(),
 8925            );
 8926
 8927            (panel_1, panel_2)
 8928        });
 8929
 8930        // Move panel_1 to the right
 8931        panel_1.update_in(cx, |panel_1, window, cx| {
 8932            panel_1.set_position(DockPosition::Right, window, cx)
 8933        });
 8934
 8935        workspace.update_in(cx, |workspace, window, cx| {
 8936            // Since panel_1 was visible on the left, it should now be visible now that it's been moved to the right.
 8937            // Since it was the only panel on the left, the left dock should now be closed.
 8938            assert!(!workspace.left_dock().read(cx).is_open());
 8939            assert!(workspace.left_dock().read(cx).visible_panel().is_none());
 8940            let right_dock = workspace.right_dock();
 8941            assert_eq!(
 8942                right_dock.read(cx).visible_panel().unwrap().panel_id(),
 8943                panel_1.panel_id()
 8944            );
 8945            assert_eq!(
 8946                right_dock.read(cx).active_panel_size(window, cx).unwrap(),
 8947                px(1337.)
 8948            );
 8949
 8950            // Now we move panel_2 to the left
 8951            panel_2.set_position(DockPosition::Left, window, cx);
 8952        });
 8953
 8954        workspace.update(cx, |workspace, cx| {
 8955            // Since panel_2 was not visible on the right, we don't open the left dock.
 8956            assert!(!workspace.left_dock().read(cx).is_open());
 8957            // And the right dock is unaffected in its displaying of panel_1
 8958            assert!(workspace.right_dock().read(cx).is_open());
 8959            assert_eq!(
 8960                workspace
 8961                    .right_dock()
 8962                    .read(cx)
 8963                    .visible_panel()
 8964                    .unwrap()
 8965                    .panel_id(),
 8966                panel_1.panel_id(),
 8967            );
 8968        });
 8969
 8970        // Move panel_1 back to the left
 8971        panel_1.update_in(cx, |panel_1, window, cx| {
 8972            panel_1.set_position(DockPosition::Left, window, cx)
 8973        });
 8974
 8975        workspace.update_in(cx, |workspace, window, cx| {
 8976            // Since panel_1 was visible on the right, we open the left dock and make panel_1 active.
 8977            let left_dock = workspace.left_dock();
 8978            assert!(left_dock.read(cx).is_open());
 8979            assert_eq!(
 8980                left_dock.read(cx).visible_panel().unwrap().panel_id(),
 8981                panel_1.panel_id()
 8982            );
 8983            assert_eq!(
 8984                left_dock.read(cx).active_panel_size(window, cx).unwrap(),
 8985                px(1337.)
 8986            );
 8987            // And the right dock should be closed as it no longer has any panels.
 8988            assert!(!workspace.right_dock().read(cx).is_open());
 8989
 8990            // Now we move panel_1 to the bottom
 8991            panel_1.set_position(DockPosition::Bottom, window, cx);
 8992        });
 8993
 8994        workspace.update_in(cx, |workspace, window, cx| {
 8995            // Since panel_1 was visible on the left, we close the left dock.
 8996            assert!(!workspace.left_dock().read(cx).is_open());
 8997            // The bottom dock is sized based on the panel's default size,
 8998            // since the panel orientation changed from vertical to horizontal.
 8999            let bottom_dock = workspace.bottom_dock();
 9000            assert_eq!(
 9001                bottom_dock.read(cx).active_panel_size(window, cx).unwrap(),
 9002                panel_1.size(window, cx),
 9003            );
 9004            // Close bottom dock and move panel_1 back to the left.
 9005            bottom_dock.update(cx, |bottom_dock, cx| {
 9006                bottom_dock.set_open(false, window, cx)
 9007            });
 9008            panel_1.set_position(DockPosition::Left, window, cx);
 9009        });
 9010
 9011        // Emit activated event on panel 1
 9012        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::Activate));
 9013
 9014        // Now the left dock is open and panel_1 is active and focused.
 9015        workspace.update_in(cx, |workspace, window, cx| {
 9016            let left_dock = workspace.left_dock();
 9017            assert!(left_dock.read(cx).is_open());
 9018            assert_eq!(
 9019                left_dock.read(cx).visible_panel().unwrap().panel_id(),
 9020                panel_1.panel_id(),
 9021            );
 9022            assert!(panel_1.focus_handle(cx).is_focused(window));
 9023        });
 9024
 9025        // Emit closed event on panel 2, which is not active
 9026        panel_2.update(cx, |_, cx| cx.emit(PanelEvent::Close));
 9027
 9028        // Wo don't close the left dock, because panel_2 wasn't the active panel
 9029        workspace.update(cx, |workspace, cx| {
 9030            let left_dock = workspace.left_dock();
 9031            assert!(left_dock.read(cx).is_open());
 9032            assert_eq!(
 9033                left_dock.read(cx).visible_panel().unwrap().panel_id(),
 9034                panel_1.panel_id(),
 9035            );
 9036        });
 9037
 9038        // Emitting a ZoomIn event shows the panel as zoomed.
 9039        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::ZoomIn));
 9040        workspace.read_with(cx, |workspace, _| {
 9041            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
 9042            assert_eq!(workspace.zoomed_position, Some(DockPosition::Left));
 9043        });
 9044
 9045        // Move panel to another dock while it is zoomed
 9046        panel_1.update_in(cx, |panel, window, cx| {
 9047            panel.set_position(DockPosition::Right, window, cx)
 9048        });
 9049        workspace.read_with(cx, |workspace, _| {
 9050            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
 9051
 9052            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
 9053        });
 9054
 9055        // This is a helper for getting a:
 9056        // - valid focus on an element,
 9057        // - that isn't a part of the panes and panels system of the Workspace,
 9058        // - and doesn't trigger the 'on_focus_lost' API.
 9059        let focus_other_view = {
 9060            let workspace = workspace.clone();
 9061            move |cx: &mut VisualTestContext| {
 9062                workspace.update_in(cx, |workspace, window, cx| {
 9063                    if let Some(_) = workspace.active_modal::<TestModal>(cx) {
 9064                        workspace.toggle_modal(window, cx, TestModal::new);
 9065                        workspace.toggle_modal(window, cx, TestModal::new);
 9066                    } else {
 9067                        workspace.toggle_modal(window, cx, TestModal::new);
 9068                    }
 9069                })
 9070            }
 9071        };
 9072
 9073        // If focus is transferred to another view that's not a panel or another pane, we still show
 9074        // the panel as zoomed.
 9075        focus_other_view(cx);
 9076        workspace.read_with(cx, |workspace, _| {
 9077            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
 9078            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
 9079        });
 9080
 9081        // If focus is transferred elsewhere in the workspace, the panel is no longer zoomed.
 9082        workspace.update_in(cx, |_workspace, window, cx| {
 9083            cx.focus_self(window);
 9084        });
 9085        workspace.read_with(cx, |workspace, _| {
 9086            assert_eq!(workspace.zoomed, None);
 9087            assert_eq!(workspace.zoomed_position, None);
 9088        });
 9089
 9090        // If focus is transferred again to another view that's not a panel or a pane, we won't
 9091        // show the panel as zoomed because it wasn't zoomed before.
 9092        focus_other_view(cx);
 9093        workspace.read_with(cx, |workspace, _| {
 9094            assert_eq!(workspace.zoomed, None);
 9095            assert_eq!(workspace.zoomed_position, None);
 9096        });
 9097
 9098        // When the panel is activated, it is zoomed again.
 9099        cx.dispatch_action(ToggleRightDock);
 9100        workspace.read_with(cx, |workspace, _| {
 9101            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
 9102            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
 9103        });
 9104
 9105        // Emitting a ZoomOut event unzooms the panel.
 9106        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::ZoomOut));
 9107        workspace.read_with(cx, |workspace, _| {
 9108            assert_eq!(workspace.zoomed, None);
 9109            assert_eq!(workspace.zoomed_position, None);
 9110        });
 9111
 9112        // Emit closed event on panel 1, which is active
 9113        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::Close));
 9114
 9115        // Now the left dock is closed, because panel_1 was the active panel
 9116        workspace.update(cx, |workspace, cx| {
 9117            let right_dock = workspace.right_dock();
 9118            assert!(!right_dock.read(cx).is_open());
 9119        });
 9120    }
 9121
 9122    #[gpui::test]
 9123    async fn test_no_save_prompt_when_multi_buffer_dirty_items_closed(cx: &mut TestAppContext) {
 9124        init_test(cx);
 9125
 9126        let fs = FakeFs::new(cx.background_executor.clone());
 9127        let project = Project::test(fs, [], cx).await;
 9128        let (workspace, cx) =
 9129            cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
 9130        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
 9131
 9132        let dirty_regular_buffer = cx.new(|cx| {
 9133            TestItem::new(cx)
 9134                .with_dirty(true)
 9135                .with_label("1.txt")
 9136                .with_project_items(&[dirty_project_item(1, "1.txt", cx)])
 9137        });
 9138        let dirty_regular_buffer_2 = cx.new(|cx| {
 9139            TestItem::new(cx)
 9140                .with_dirty(true)
 9141                .with_label("2.txt")
 9142                .with_project_items(&[dirty_project_item(2, "2.txt", cx)])
 9143        });
 9144        let dirty_multi_buffer_with_both = cx.new(|cx| {
 9145            TestItem::new(cx)
 9146                .with_dirty(true)
 9147                .with_singleton(false)
 9148                .with_label("Fake Project Search")
 9149                .with_project_items(&[
 9150                    dirty_regular_buffer.read(cx).project_items[0].clone(),
 9151                    dirty_regular_buffer_2.read(cx).project_items[0].clone(),
 9152                ])
 9153        });
 9154        let multi_buffer_with_both_files_id = dirty_multi_buffer_with_both.item_id();
 9155        workspace.update_in(cx, |workspace, window, cx| {
 9156            workspace.add_item(
 9157                pane.clone(),
 9158                Box::new(dirty_regular_buffer.clone()),
 9159                None,
 9160                false,
 9161                false,
 9162                window,
 9163                cx,
 9164            );
 9165            workspace.add_item(
 9166                pane.clone(),
 9167                Box::new(dirty_regular_buffer_2.clone()),
 9168                None,
 9169                false,
 9170                false,
 9171                window,
 9172                cx,
 9173            );
 9174            workspace.add_item(
 9175                pane.clone(),
 9176                Box::new(dirty_multi_buffer_with_both.clone()),
 9177                None,
 9178                false,
 9179                false,
 9180                window,
 9181                cx,
 9182            );
 9183        });
 9184
 9185        pane.update_in(cx, |pane, window, cx| {
 9186            pane.activate_item(2, true, true, window, cx);
 9187            assert_eq!(
 9188                pane.active_item().unwrap().item_id(),
 9189                multi_buffer_with_both_files_id,
 9190                "Should select the multi buffer in the pane"
 9191            );
 9192        });
 9193        let close_all_but_multi_buffer_task = pane.update_in(cx, |pane, window, cx| {
 9194            pane.close_inactive_items(
 9195                &CloseInactiveItems {
 9196                    save_intent: Some(SaveIntent::Save),
 9197                    close_pinned: true,
 9198                },
 9199                window,
 9200                cx,
 9201            )
 9202        });
 9203        cx.background_executor.run_until_parked();
 9204        assert!(!cx.has_pending_prompt());
 9205        close_all_but_multi_buffer_task
 9206            .await
 9207            .expect("Closing all buffers but the multi buffer failed");
 9208        pane.update(cx, |pane, cx| {
 9209            assert_eq!(dirty_regular_buffer.read(cx).save_count, 1);
 9210            assert_eq!(dirty_multi_buffer_with_both.read(cx).save_count, 0);
 9211            assert_eq!(dirty_regular_buffer_2.read(cx).save_count, 1);
 9212            assert_eq!(pane.items_len(), 1);
 9213            assert_eq!(
 9214                pane.active_item().unwrap().item_id(),
 9215                multi_buffer_with_both_files_id,
 9216                "Should have only the multi buffer left in the pane"
 9217            );
 9218            assert!(
 9219                dirty_multi_buffer_with_both.read(cx).is_dirty,
 9220                "The multi buffer containing the unsaved buffer should still be dirty"
 9221            );
 9222        });
 9223
 9224        dirty_regular_buffer.update(cx, |buffer, cx| {
 9225            buffer.project_items[0].update(cx, |pi, _| pi.is_dirty = true)
 9226        });
 9227
 9228        let close_multi_buffer_task = pane.update_in(cx, |pane, window, cx| {
 9229            pane.close_active_item(
 9230                &CloseActiveItem {
 9231                    save_intent: Some(SaveIntent::Close),
 9232                    close_pinned: false,
 9233                },
 9234                window,
 9235                cx,
 9236            )
 9237        });
 9238        cx.background_executor.run_until_parked();
 9239        assert!(
 9240            cx.has_pending_prompt(),
 9241            "Dirty multi buffer should prompt a save dialog"
 9242        );
 9243        cx.simulate_prompt_answer("Save");
 9244        cx.background_executor.run_until_parked();
 9245        close_multi_buffer_task
 9246            .await
 9247            .expect("Closing the multi buffer failed");
 9248        pane.update(cx, |pane, cx| {
 9249            assert_eq!(
 9250                dirty_multi_buffer_with_both.read(cx).save_count,
 9251                1,
 9252                "Multi buffer item should get be saved"
 9253            );
 9254            // Test impl does not save inner items, so we do not assert them
 9255            assert_eq!(
 9256                pane.items_len(),
 9257                0,
 9258                "No more items should be left in the pane"
 9259            );
 9260            assert!(pane.active_item().is_none());
 9261        });
 9262    }
 9263
 9264    #[gpui::test]
 9265    async fn test_save_prompt_when_dirty_multi_buffer_closed_with_some_of_its_dirty_items_not_present_in_the_pane(
 9266        cx: &mut TestAppContext,
 9267    ) {
 9268        init_test(cx);
 9269
 9270        let fs = FakeFs::new(cx.background_executor.clone());
 9271        let project = Project::test(fs, [], cx).await;
 9272        let (workspace, cx) =
 9273            cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
 9274        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
 9275
 9276        let dirty_regular_buffer = cx.new(|cx| {
 9277            TestItem::new(cx)
 9278                .with_dirty(true)
 9279                .with_label("1.txt")
 9280                .with_project_items(&[dirty_project_item(1, "1.txt", cx)])
 9281        });
 9282        let dirty_regular_buffer_2 = cx.new(|cx| {
 9283            TestItem::new(cx)
 9284                .with_dirty(true)
 9285                .with_label("2.txt")
 9286                .with_project_items(&[dirty_project_item(2, "2.txt", cx)])
 9287        });
 9288        let clear_regular_buffer = cx.new(|cx| {
 9289            TestItem::new(cx)
 9290                .with_label("3.txt")
 9291                .with_project_items(&[TestProjectItem::new(3, "3.txt", cx)])
 9292        });
 9293
 9294        let dirty_multi_buffer_with_both = cx.new(|cx| {
 9295            TestItem::new(cx)
 9296                .with_dirty(true)
 9297                .with_singleton(false)
 9298                .with_label("Fake Project Search")
 9299                .with_project_items(&[
 9300                    dirty_regular_buffer.read(cx).project_items[0].clone(),
 9301                    dirty_regular_buffer_2.read(cx).project_items[0].clone(),
 9302                    clear_regular_buffer.read(cx).project_items[0].clone(),
 9303                ])
 9304        });
 9305        let multi_buffer_with_both_files_id = dirty_multi_buffer_with_both.item_id();
 9306        workspace.update_in(cx, |workspace, window, cx| {
 9307            workspace.add_item(
 9308                pane.clone(),
 9309                Box::new(dirty_regular_buffer.clone()),
 9310                None,
 9311                false,
 9312                false,
 9313                window,
 9314                cx,
 9315            );
 9316            workspace.add_item(
 9317                pane.clone(),
 9318                Box::new(dirty_multi_buffer_with_both.clone()),
 9319                None,
 9320                false,
 9321                false,
 9322                window,
 9323                cx,
 9324            );
 9325        });
 9326
 9327        pane.update_in(cx, |pane, window, cx| {
 9328            pane.activate_item(1, true, true, window, cx);
 9329            assert_eq!(
 9330                pane.active_item().unwrap().item_id(),
 9331                multi_buffer_with_both_files_id,
 9332                "Should select the multi buffer in the pane"
 9333            );
 9334        });
 9335        let _close_multi_buffer_task = pane.update_in(cx, |pane, window, cx| {
 9336            pane.close_active_item(
 9337                &CloseActiveItem {
 9338                    save_intent: None,
 9339                    close_pinned: false,
 9340                },
 9341                window,
 9342                cx,
 9343            )
 9344        });
 9345        cx.background_executor.run_until_parked();
 9346        assert!(
 9347            cx.has_pending_prompt(),
 9348            "With one dirty item from the multi buffer not being in the pane, a save prompt should be shown"
 9349        );
 9350    }
 9351
 9352    /// Tests that when `close_on_file_delete` is enabled, files are automatically
 9353    /// closed when they are deleted from disk.
 9354    #[gpui::test]
 9355    async fn test_close_on_disk_deletion_enabled(cx: &mut TestAppContext) {
 9356        init_test(cx);
 9357
 9358        // Enable the close_on_disk_deletion setting
 9359        cx.update_global(|store: &mut SettingsStore, cx| {
 9360            store.update_user_settings::<WorkspaceSettings>(cx, |settings| {
 9361                settings.close_on_file_delete = Some(true);
 9362            });
 9363        });
 9364
 9365        let fs = FakeFs::new(cx.background_executor.clone());
 9366        let project = Project::test(fs, [], cx).await;
 9367        let (workspace, cx) =
 9368            cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
 9369        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
 9370
 9371        // Create a test item that simulates a file
 9372        let item = cx.new(|cx| {
 9373            TestItem::new(cx)
 9374                .with_label("test.txt")
 9375                .with_project_items(&[TestProjectItem::new(1, "test.txt", cx)])
 9376        });
 9377
 9378        // Add item to workspace
 9379        workspace.update_in(cx, |workspace, window, cx| {
 9380            workspace.add_item(
 9381                pane.clone(),
 9382                Box::new(item.clone()),
 9383                None,
 9384                false,
 9385                false,
 9386                window,
 9387                cx,
 9388            );
 9389        });
 9390
 9391        // Verify the item is in the pane
 9392        pane.read_with(cx, |pane, _| {
 9393            assert_eq!(pane.items().count(), 1);
 9394        });
 9395
 9396        // Simulate file deletion by setting the item's deleted state
 9397        item.update(cx, |item, _| {
 9398            item.set_has_deleted_file(true);
 9399        });
 9400
 9401        // Emit UpdateTab event to trigger the close behavior
 9402        cx.run_until_parked();
 9403        item.update(cx, |_, cx| {
 9404            cx.emit(ItemEvent::UpdateTab);
 9405        });
 9406
 9407        // Allow the close operation to complete
 9408        cx.run_until_parked();
 9409
 9410        // Verify the item was automatically closed
 9411        pane.read_with(cx, |pane, _| {
 9412            assert_eq!(
 9413                pane.items().count(),
 9414                0,
 9415                "Item should be automatically closed when file is deleted"
 9416            );
 9417        });
 9418    }
 9419
 9420    /// Tests that when `close_on_file_delete` is disabled (default), files remain
 9421    /// open with a strikethrough when they are deleted from disk.
 9422    #[gpui::test]
 9423    async fn test_close_on_disk_deletion_disabled(cx: &mut TestAppContext) {
 9424        init_test(cx);
 9425
 9426        // Ensure close_on_disk_deletion is disabled (default)
 9427        cx.update_global(|store: &mut SettingsStore, cx| {
 9428            store.update_user_settings::<WorkspaceSettings>(cx, |settings| {
 9429                settings.close_on_file_delete = Some(false);
 9430            });
 9431        });
 9432
 9433        let fs = FakeFs::new(cx.background_executor.clone());
 9434        let project = Project::test(fs, [], cx).await;
 9435        let (workspace, cx) =
 9436            cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
 9437        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
 9438
 9439        // Create a test item that simulates a file
 9440        let item = cx.new(|cx| {
 9441            TestItem::new(cx)
 9442                .with_label("test.txt")
 9443                .with_project_items(&[TestProjectItem::new(1, "test.txt", cx)])
 9444        });
 9445
 9446        // Add item to workspace
 9447        workspace.update_in(cx, |workspace, window, cx| {
 9448            workspace.add_item(
 9449                pane.clone(),
 9450                Box::new(item.clone()),
 9451                None,
 9452                false,
 9453                false,
 9454                window,
 9455                cx,
 9456            );
 9457        });
 9458
 9459        // Verify the item is in the pane
 9460        pane.read_with(cx, |pane, _| {
 9461            assert_eq!(pane.items().count(), 1);
 9462        });
 9463
 9464        // Simulate file deletion
 9465        item.update(cx, |item, _| {
 9466            item.set_has_deleted_file(true);
 9467        });
 9468
 9469        // Emit UpdateTab event
 9470        cx.run_until_parked();
 9471        item.update(cx, |_, cx| {
 9472            cx.emit(ItemEvent::UpdateTab);
 9473        });
 9474
 9475        // Allow any potential close operation to complete
 9476        cx.run_until_parked();
 9477
 9478        // Verify the item remains open (with strikethrough)
 9479        pane.read_with(cx, |pane, _| {
 9480            assert_eq!(
 9481                pane.items().count(),
 9482                1,
 9483                "Item should remain open when close_on_disk_deletion is disabled"
 9484            );
 9485        });
 9486
 9487        // Verify the item shows as deleted
 9488        item.read_with(cx, |item, _| {
 9489            assert!(
 9490                item.has_deleted_file,
 9491                "Item should be marked as having deleted file"
 9492            );
 9493        });
 9494    }
 9495
 9496    /// Tests that dirty files are not automatically closed when deleted from disk,
 9497    /// even when `close_on_file_delete` is enabled. This ensures users don't lose
 9498    /// unsaved changes without being prompted.
 9499    #[gpui::test]
 9500    async fn test_close_on_disk_deletion_with_dirty_file(cx: &mut TestAppContext) {
 9501        init_test(cx);
 9502
 9503        // Enable the close_on_file_delete setting
 9504        cx.update_global(|store: &mut SettingsStore, cx| {
 9505            store.update_user_settings::<WorkspaceSettings>(cx, |settings| {
 9506                settings.close_on_file_delete = Some(true);
 9507            });
 9508        });
 9509
 9510        let fs = FakeFs::new(cx.background_executor.clone());
 9511        let project = Project::test(fs, [], cx).await;
 9512        let (workspace, cx) =
 9513            cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
 9514        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
 9515
 9516        // Create a dirty test item
 9517        let item = cx.new(|cx| {
 9518            TestItem::new(cx)
 9519                .with_dirty(true)
 9520                .with_label("test.txt")
 9521                .with_project_items(&[TestProjectItem::new(1, "test.txt", cx)])
 9522        });
 9523
 9524        // Add item to workspace
 9525        workspace.update_in(cx, |workspace, window, cx| {
 9526            workspace.add_item(
 9527                pane.clone(),
 9528                Box::new(item.clone()),
 9529                None,
 9530                false,
 9531                false,
 9532                window,
 9533                cx,
 9534            );
 9535        });
 9536
 9537        // Simulate file deletion
 9538        item.update(cx, |item, _| {
 9539            item.set_has_deleted_file(true);
 9540        });
 9541
 9542        // Emit UpdateTab event to trigger the close behavior
 9543        cx.run_until_parked();
 9544        item.update(cx, |_, cx| {
 9545            cx.emit(ItemEvent::UpdateTab);
 9546        });
 9547
 9548        // Allow any potential close operation to complete
 9549        cx.run_until_parked();
 9550
 9551        // Verify the item remains open (dirty files are not auto-closed)
 9552        pane.read_with(cx, |pane, _| {
 9553            assert_eq!(
 9554                pane.items().count(),
 9555                1,
 9556                "Dirty items should not be automatically closed even when file is deleted"
 9557            );
 9558        });
 9559
 9560        // Verify the item is marked as deleted and still dirty
 9561        item.read_with(cx, |item, _| {
 9562            assert!(
 9563                item.has_deleted_file,
 9564                "Item should be marked as having deleted file"
 9565            );
 9566            assert!(item.is_dirty, "Item should still be dirty");
 9567        });
 9568    }
 9569
 9570    /// Tests that navigation history is cleaned up when files are auto-closed
 9571    /// due to deletion from disk.
 9572    #[gpui::test]
 9573    async fn test_close_on_disk_deletion_cleans_navigation_history(cx: &mut TestAppContext) {
 9574        init_test(cx);
 9575
 9576        // Enable the close_on_file_delete setting
 9577        cx.update_global(|store: &mut SettingsStore, cx| {
 9578            store.update_user_settings::<WorkspaceSettings>(cx, |settings| {
 9579                settings.close_on_file_delete = Some(true);
 9580            });
 9581        });
 9582
 9583        let fs = FakeFs::new(cx.background_executor.clone());
 9584        let project = Project::test(fs, [], cx).await;
 9585        let (workspace, cx) =
 9586            cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
 9587        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
 9588
 9589        // Create test items
 9590        let item1 = cx.new(|cx| {
 9591            TestItem::new(cx)
 9592                .with_label("test1.txt")
 9593                .with_project_items(&[TestProjectItem::new(1, "test1.txt", cx)])
 9594        });
 9595        let item1_id = item1.item_id();
 9596
 9597        let item2 = cx.new(|cx| {
 9598            TestItem::new(cx)
 9599                .with_label("test2.txt")
 9600                .with_project_items(&[TestProjectItem::new(2, "test2.txt", cx)])
 9601        });
 9602
 9603        // Add items to workspace
 9604        workspace.update_in(cx, |workspace, window, cx| {
 9605            workspace.add_item(
 9606                pane.clone(),
 9607                Box::new(item1.clone()),
 9608                None,
 9609                false,
 9610                false,
 9611                window,
 9612                cx,
 9613            );
 9614            workspace.add_item(
 9615                pane.clone(),
 9616                Box::new(item2.clone()),
 9617                None,
 9618                false,
 9619                false,
 9620                window,
 9621                cx,
 9622            );
 9623        });
 9624
 9625        // Activate item1 to ensure it gets navigation entries
 9626        pane.update_in(cx, |pane, window, cx| {
 9627            pane.activate_item(0, true, true, window, cx);
 9628        });
 9629
 9630        // Switch to item2 and back to create navigation history
 9631        pane.update_in(cx, |pane, window, cx| {
 9632            pane.activate_item(1, true, true, window, cx);
 9633        });
 9634        cx.run_until_parked();
 9635
 9636        pane.update_in(cx, |pane, window, cx| {
 9637            pane.activate_item(0, true, true, window, cx);
 9638        });
 9639        cx.run_until_parked();
 9640
 9641        // Simulate file deletion for item1
 9642        item1.update(cx, |item, _| {
 9643            item.set_has_deleted_file(true);
 9644        });
 9645
 9646        // Emit UpdateTab event to trigger the close behavior
 9647        item1.update(cx, |_, cx| {
 9648            cx.emit(ItemEvent::UpdateTab);
 9649        });
 9650        cx.run_until_parked();
 9651
 9652        // Verify item1 was closed
 9653        pane.read_with(cx, |pane, _| {
 9654            assert_eq!(
 9655                pane.items().count(),
 9656                1,
 9657                "Should have 1 item remaining after auto-close"
 9658            );
 9659        });
 9660
 9661        // Check navigation history after close
 9662        let has_item = pane.read_with(cx, |pane, cx| {
 9663            let mut has_item = false;
 9664            pane.nav_history().for_each_entry(cx, |entry, _| {
 9665                if entry.item.id() == item1_id {
 9666                    has_item = true;
 9667                }
 9668            });
 9669            has_item
 9670        });
 9671
 9672        assert!(
 9673            !has_item,
 9674            "Navigation history should not contain closed item entries"
 9675        );
 9676    }
 9677
 9678    #[gpui::test]
 9679    async fn test_no_save_prompt_when_dirty_multi_buffer_closed_with_all_of_its_dirty_items_present_in_the_pane(
 9680        cx: &mut TestAppContext,
 9681    ) {
 9682        init_test(cx);
 9683
 9684        let fs = FakeFs::new(cx.background_executor.clone());
 9685        let project = Project::test(fs, [], cx).await;
 9686        let (workspace, cx) =
 9687            cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
 9688        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
 9689
 9690        let dirty_regular_buffer = cx.new(|cx| {
 9691            TestItem::new(cx)
 9692                .with_dirty(true)
 9693                .with_label("1.txt")
 9694                .with_project_items(&[dirty_project_item(1, "1.txt", cx)])
 9695        });
 9696        let dirty_regular_buffer_2 = cx.new(|cx| {
 9697            TestItem::new(cx)
 9698                .with_dirty(true)
 9699                .with_label("2.txt")
 9700                .with_project_items(&[dirty_project_item(2, "2.txt", cx)])
 9701        });
 9702        let clear_regular_buffer = cx.new(|cx| {
 9703            TestItem::new(cx)
 9704                .with_label("3.txt")
 9705                .with_project_items(&[TestProjectItem::new(3, "3.txt", cx)])
 9706        });
 9707
 9708        let dirty_multi_buffer = cx.new(|cx| {
 9709            TestItem::new(cx)
 9710                .with_dirty(true)
 9711                .with_singleton(false)
 9712                .with_label("Fake Project Search")
 9713                .with_project_items(&[
 9714                    dirty_regular_buffer.read(cx).project_items[0].clone(),
 9715                    dirty_regular_buffer_2.read(cx).project_items[0].clone(),
 9716                    clear_regular_buffer.read(cx).project_items[0].clone(),
 9717                ])
 9718        });
 9719        workspace.update_in(cx, |workspace, window, cx| {
 9720            workspace.add_item(
 9721                pane.clone(),
 9722                Box::new(dirty_regular_buffer.clone()),
 9723                None,
 9724                false,
 9725                false,
 9726                window,
 9727                cx,
 9728            );
 9729            workspace.add_item(
 9730                pane.clone(),
 9731                Box::new(dirty_regular_buffer_2.clone()),
 9732                None,
 9733                false,
 9734                false,
 9735                window,
 9736                cx,
 9737            );
 9738            workspace.add_item(
 9739                pane.clone(),
 9740                Box::new(dirty_multi_buffer.clone()),
 9741                None,
 9742                false,
 9743                false,
 9744                window,
 9745                cx,
 9746            );
 9747        });
 9748
 9749        pane.update_in(cx, |pane, window, cx| {
 9750            pane.activate_item(2, true, true, window, cx);
 9751            assert_eq!(
 9752                pane.active_item().unwrap().item_id(),
 9753                dirty_multi_buffer.item_id(),
 9754                "Should select the multi buffer in the pane"
 9755            );
 9756        });
 9757        let close_multi_buffer_task = pane.update_in(cx, |pane, window, cx| {
 9758            pane.close_active_item(
 9759                &CloseActiveItem {
 9760                    save_intent: None,
 9761                    close_pinned: false,
 9762                },
 9763                window,
 9764                cx,
 9765            )
 9766        });
 9767        cx.background_executor.run_until_parked();
 9768        assert!(
 9769            !cx.has_pending_prompt(),
 9770            "All dirty items from the multi buffer are in the pane still, no save prompts should be shown"
 9771        );
 9772        close_multi_buffer_task
 9773            .await
 9774            .expect("Closing multi buffer failed");
 9775        pane.update(cx, |pane, cx| {
 9776            assert_eq!(dirty_regular_buffer.read(cx).save_count, 0);
 9777            assert_eq!(dirty_multi_buffer.read(cx).save_count, 0);
 9778            assert_eq!(dirty_regular_buffer_2.read(cx).save_count, 0);
 9779            assert_eq!(
 9780                pane.items()
 9781                    .map(|item| item.item_id())
 9782                    .sorted()
 9783                    .collect::<Vec<_>>(),
 9784                vec![
 9785                    dirty_regular_buffer.item_id(),
 9786                    dirty_regular_buffer_2.item_id(),
 9787                ],
 9788                "Should have no multi buffer left in the pane"
 9789            );
 9790            assert!(dirty_regular_buffer.read(cx).is_dirty);
 9791            assert!(dirty_regular_buffer_2.read(cx).is_dirty);
 9792        });
 9793    }
 9794
 9795    #[gpui::test]
 9796    async fn test_move_focused_panel_to_next_position(cx: &mut gpui::TestAppContext) {
 9797        init_test(cx);
 9798        let fs = FakeFs::new(cx.executor());
 9799        let project = Project::test(fs, [], cx).await;
 9800        let (workspace, cx) =
 9801            cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
 9802
 9803        // Add a new panel to the right dock, opening the dock and setting the
 9804        // focus to the new panel.
 9805        let panel = workspace.update_in(cx, |workspace, window, cx| {
 9806            let panel = cx.new(|cx| TestPanel::new(DockPosition::Right, cx));
 9807            workspace.add_panel(panel.clone(), window, cx);
 9808
 9809            workspace
 9810                .right_dock()
 9811                .update(cx, |right_dock, cx| right_dock.set_open(true, window, cx));
 9812
 9813            workspace.toggle_panel_focus::<TestPanel>(window, cx);
 9814
 9815            panel
 9816        });
 9817
 9818        // Dispatch the `MoveFocusedPanelToNextPosition` action, moving the
 9819        // panel to the next valid position which, in this case, is the left
 9820        // dock.
 9821        cx.dispatch_action(MoveFocusedPanelToNextPosition);
 9822        workspace.update(cx, |workspace, cx| {
 9823            assert!(workspace.left_dock().read(cx).is_open());
 9824            assert_eq!(panel.read(cx).position, DockPosition::Left);
 9825        });
 9826
 9827        // Dispatch the `MoveFocusedPanelToNextPosition` action, moving the
 9828        // panel to the next valid position which, in this case, is the bottom
 9829        // dock.
 9830        cx.dispatch_action(MoveFocusedPanelToNextPosition);
 9831        workspace.update(cx, |workspace, cx| {
 9832            assert!(workspace.bottom_dock().read(cx).is_open());
 9833            assert_eq!(panel.read(cx).position, DockPosition::Bottom);
 9834        });
 9835
 9836        // Dispatch the `MoveFocusedPanelToNextPosition` action again, this time
 9837        // around moving the panel to its initial position, the right dock.
 9838        cx.dispatch_action(MoveFocusedPanelToNextPosition);
 9839        workspace.update(cx, |workspace, cx| {
 9840            assert!(workspace.right_dock().read(cx).is_open());
 9841            assert_eq!(panel.read(cx).position, DockPosition::Right);
 9842        });
 9843
 9844        // Remove focus from the panel, ensuring that, if the panel is not
 9845        // focused, the `MoveFocusedPanelToNextPosition` action does not update
 9846        // the panel's position, so the panel is still in the right dock.
 9847        workspace.update_in(cx, |workspace, window, cx| {
 9848            workspace.toggle_panel_focus::<TestPanel>(window, cx);
 9849        });
 9850
 9851        cx.dispatch_action(MoveFocusedPanelToNextPosition);
 9852        workspace.update(cx, |workspace, cx| {
 9853            assert!(workspace.right_dock().read(cx).is_open());
 9854            assert_eq!(panel.read(cx).position, DockPosition::Right);
 9855        });
 9856    }
 9857
 9858    #[gpui::test]
 9859    async fn test_moving_items_create_panes(cx: &mut TestAppContext) {
 9860        init_test(cx);
 9861
 9862        let fs = FakeFs::new(cx.executor());
 9863        let project = Project::test(fs, [], cx).await;
 9864        let (workspace, cx) =
 9865            cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
 9866
 9867        let item_1 = cx.new(|cx| {
 9868            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "first.txt", cx)])
 9869        });
 9870        workspace.update_in(cx, |workspace, window, cx| {
 9871            workspace.add_item_to_active_pane(Box::new(item_1), None, true, window, cx);
 9872            workspace.move_item_to_pane_in_direction(
 9873                &MoveItemToPaneInDirection {
 9874                    direction: SplitDirection::Right,
 9875                    focus: true,
 9876                    clone: false,
 9877                },
 9878                window,
 9879                cx,
 9880            );
 9881            workspace.move_item_to_pane_at_index(
 9882                &MoveItemToPane {
 9883                    destination: 3,
 9884                    focus: true,
 9885                    clone: false,
 9886                },
 9887                window,
 9888                cx,
 9889            );
 9890
 9891            assert_eq!(workspace.panes.len(), 1, "No new panes were created");
 9892            assert_eq!(
 9893                pane_items_paths(&workspace.active_pane, cx),
 9894                vec!["first.txt".to_string()],
 9895                "Single item was not moved anywhere"
 9896            );
 9897        });
 9898
 9899        let item_2 = cx.new(|cx| {
 9900            TestItem::new(cx).with_project_items(&[TestProjectItem::new(2, "second.txt", cx)])
 9901        });
 9902        workspace.update_in(cx, |workspace, window, cx| {
 9903            workspace.add_item_to_active_pane(Box::new(item_2), None, true, window, cx);
 9904            assert_eq!(
 9905                pane_items_paths(&workspace.panes[0], cx),
 9906                vec!["first.txt".to_string(), "second.txt".to_string()],
 9907            );
 9908            workspace.move_item_to_pane_in_direction(
 9909                &MoveItemToPaneInDirection {
 9910                    direction: SplitDirection::Right,
 9911                    focus: true,
 9912                    clone: false,
 9913                },
 9914                window,
 9915                cx,
 9916            );
 9917
 9918            assert_eq!(workspace.panes.len(), 2, "A new pane should be created");
 9919            assert_eq!(
 9920                pane_items_paths(&workspace.panes[0], cx),
 9921                vec!["first.txt".to_string()],
 9922                "After moving, one item should be left in the original pane"
 9923            );
 9924            assert_eq!(
 9925                pane_items_paths(&workspace.panes[1], cx),
 9926                vec!["second.txt".to_string()],
 9927                "New item should have been moved to the new pane"
 9928            );
 9929        });
 9930
 9931        let item_3 = cx.new(|cx| {
 9932            TestItem::new(cx).with_project_items(&[TestProjectItem::new(3, "third.txt", cx)])
 9933        });
 9934        workspace.update_in(cx, |workspace, window, cx| {
 9935            let original_pane = workspace.panes[0].clone();
 9936            workspace.set_active_pane(&original_pane, window, cx);
 9937            workspace.add_item_to_active_pane(Box::new(item_3), None, true, window, cx);
 9938            assert_eq!(workspace.panes.len(), 2, "No new panes were created");
 9939            assert_eq!(
 9940                pane_items_paths(&workspace.active_pane, cx),
 9941                vec!["first.txt".to_string(), "third.txt".to_string()],
 9942                "New pane should be ready to move one item out"
 9943            );
 9944
 9945            workspace.move_item_to_pane_at_index(
 9946                &MoveItemToPane {
 9947                    destination: 3,
 9948                    focus: true,
 9949                    clone: false,
 9950                },
 9951                window,
 9952                cx,
 9953            );
 9954            assert_eq!(workspace.panes.len(), 3, "A new pane should be created");
 9955            assert_eq!(
 9956                pane_items_paths(&workspace.active_pane, cx),
 9957                vec!["first.txt".to_string()],
 9958                "After moving, one item should be left in the original pane"
 9959            );
 9960            assert_eq!(
 9961                pane_items_paths(&workspace.panes[1], cx),
 9962                vec!["second.txt".to_string()],
 9963                "Previously created pane should be unchanged"
 9964            );
 9965            assert_eq!(
 9966                pane_items_paths(&workspace.panes[2], cx),
 9967                vec!["third.txt".to_string()],
 9968                "New item should have been moved to the new pane"
 9969            );
 9970        });
 9971    }
 9972
 9973    #[gpui::test]
 9974    async fn test_moving_items_can_clone_panes(cx: &mut TestAppContext) {
 9975        init_test(cx);
 9976
 9977        let fs = FakeFs::new(cx.executor());
 9978        let project = Project::test(fs, [], cx).await;
 9979        let (workspace, cx) =
 9980            cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
 9981
 9982        let item_1 = cx.new(|cx| {
 9983            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "first.txt", cx)])
 9984        });
 9985        workspace.update_in(cx, |workspace, window, cx| {
 9986            workspace.add_item_to_active_pane(Box::new(item_1), None, true, window, cx);
 9987            workspace.move_item_to_pane_in_direction(
 9988                &MoveItemToPaneInDirection {
 9989                    direction: SplitDirection::Right,
 9990                    focus: true,
 9991                    clone: true,
 9992                },
 9993                window,
 9994                cx,
 9995            );
 9996            workspace.move_item_to_pane_at_index(
 9997                &MoveItemToPane {
 9998                    destination: 3,
 9999                    focus: true,
10000                    clone: true,
10001                },
10002                window,
10003                cx,
10004            );
10005
10006            assert_eq!(workspace.panes.len(), 3, "Two new panes were created");
10007            for pane in workspace.panes() {
10008                assert_eq!(
10009                    pane_items_paths(pane, cx),
10010                    vec!["first.txt".to_string()],
10011                    "Single item exists in all panes"
10012                );
10013            }
10014        });
10015
10016        // verify that the active pane has been updated after waiting for the
10017        // pane focus event to fire and resolve
10018        workspace.read_with(cx, |workspace, _app| {
10019            assert_eq!(
10020                workspace.active_pane(),
10021                &workspace.panes[2],
10022                "The third pane should be the active one: {:?}",
10023                workspace.panes
10024            );
10025        })
10026    }
10027
10028    mod register_project_item_tests {
10029
10030        use super::*;
10031
10032        // View
10033        struct TestPngItemView {
10034            focus_handle: FocusHandle,
10035        }
10036        // Model
10037        struct TestPngItem {}
10038
10039        impl project::ProjectItem for TestPngItem {
10040            fn try_open(
10041                _project: &Entity<Project>,
10042                path: &ProjectPath,
10043                cx: &mut App,
10044            ) -> Option<Task<anyhow::Result<Entity<Self>>>> {
10045                if path.path.extension().unwrap() == "png" {
10046                    Some(cx.spawn(async move |cx| cx.new(|_| TestPngItem {})))
10047                } else {
10048                    None
10049                }
10050            }
10051
10052            fn entry_id(&self, _: &App) -> Option<ProjectEntryId> {
10053                None
10054            }
10055
10056            fn project_path(&self, _: &App) -> Option<ProjectPath> {
10057                None
10058            }
10059
10060            fn is_dirty(&self) -> bool {
10061                false
10062            }
10063        }
10064
10065        impl Item for TestPngItemView {
10066            type Event = ();
10067            fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString {
10068                "".into()
10069            }
10070        }
10071        impl EventEmitter<()> for TestPngItemView {}
10072        impl Focusable for TestPngItemView {
10073            fn focus_handle(&self, _cx: &App) -> FocusHandle {
10074                self.focus_handle.clone()
10075            }
10076        }
10077
10078        impl Render for TestPngItemView {
10079            fn render(
10080                &mut self,
10081                _window: &mut Window,
10082                _cx: &mut Context<Self>,
10083            ) -> impl IntoElement {
10084                Empty
10085            }
10086        }
10087
10088        impl ProjectItem for TestPngItemView {
10089            type Item = TestPngItem;
10090
10091            fn for_project_item(
10092                _project: Entity<Project>,
10093                _pane: Option<&Pane>,
10094                _item: Entity<Self::Item>,
10095                _: &mut Window,
10096                cx: &mut Context<Self>,
10097            ) -> Self
10098            where
10099                Self: Sized,
10100            {
10101                Self {
10102                    focus_handle: cx.focus_handle(),
10103                }
10104            }
10105        }
10106
10107        // View
10108        struct TestIpynbItemView {
10109            focus_handle: FocusHandle,
10110        }
10111        // Model
10112        struct TestIpynbItem {}
10113
10114        impl project::ProjectItem for TestIpynbItem {
10115            fn try_open(
10116                _project: &Entity<Project>,
10117                path: &ProjectPath,
10118                cx: &mut App,
10119            ) -> Option<Task<anyhow::Result<Entity<Self>>>> {
10120                if path.path.extension().unwrap() == "ipynb" {
10121                    Some(cx.spawn(async move |cx| cx.new(|_| TestIpynbItem {})))
10122                } else {
10123                    None
10124                }
10125            }
10126
10127            fn entry_id(&self, _: &App) -> Option<ProjectEntryId> {
10128                None
10129            }
10130
10131            fn project_path(&self, _: &App) -> Option<ProjectPath> {
10132                None
10133            }
10134
10135            fn is_dirty(&self) -> bool {
10136                false
10137            }
10138        }
10139
10140        impl Item for TestIpynbItemView {
10141            type Event = ();
10142            fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString {
10143                "".into()
10144            }
10145        }
10146        impl EventEmitter<()> for TestIpynbItemView {}
10147        impl Focusable for TestIpynbItemView {
10148            fn focus_handle(&self, _cx: &App) -> FocusHandle {
10149                self.focus_handle.clone()
10150            }
10151        }
10152
10153        impl Render for TestIpynbItemView {
10154            fn render(
10155                &mut self,
10156                _window: &mut Window,
10157                _cx: &mut Context<Self>,
10158            ) -> impl IntoElement {
10159                Empty
10160            }
10161        }
10162
10163        impl ProjectItem for TestIpynbItemView {
10164            type Item = TestIpynbItem;
10165
10166            fn for_project_item(
10167                _project: Entity<Project>,
10168                _pane: Option<&Pane>,
10169                _item: Entity<Self::Item>,
10170                _: &mut Window,
10171                cx: &mut Context<Self>,
10172            ) -> Self
10173            where
10174                Self: Sized,
10175            {
10176                Self {
10177                    focus_handle: cx.focus_handle(),
10178                }
10179            }
10180        }
10181
10182        struct TestAlternatePngItemView {
10183            focus_handle: FocusHandle,
10184        }
10185
10186        impl Item for TestAlternatePngItemView {
10187            type Event = ();
10188            fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString {
10189                "".into()
10190            }
10191        }
10192
10193        impl EventEmitter<()> for TestAlternatePngItemView {}
10194        impl Focusable for TestAlternatePngItemView {
10195            fn focus_handle(&self, _cx: &App) -> FocusHandle {
10196                self.focus_handle.clone()
10197            }
10198        }
10199
10200        impl Render for TestAlternatePngItemView {
10201            fn render(
10202                &mut self,
10203                _window: &mut Window,
10204                _cx: &mut Context<Self>,
10205            ) -> impl IntoElement {
10206                Empty
10207            }
10208        }
10209
10210        impl ProjectItem for TestAlternatePngItemView {
10211            type Item = TestPngItem;
10212
10213            fn for_project_item(
10214                _project: Entity<Project>,
10215                _pane: Option<&Pane>,
10216                _item: Entity<Self::Item>,
10217                _: &mut Window,
10218                cx: &mut Context<Self>,
10219            ) -> Self
10220            where
10221                Self: Sized,
10222            {
10223                Self {
10224                    focus_handle: cx.focus_handle(),
10225                }
10226            }
10227        }
10228
10229        #[gpui::test]
10230        async fn test_register_project_item(cx: &mut TestAppContext) {
10231            init_test(cx);
10232
10233            cx.update(|cx| {
10234                register_project_item::<TestPngItemView>(cx);
10235                register_project_item::<TestIpynbItemView>(cx);
10236            });
10237
10238            let fs = FakeFs::new(cx.executor());
10239            fs.insert_tree(
10240                "/root1",
10241                json!({
10242                    "one.png": "BINARYDATAHERE",
10243                    "two.ipynb": "{ totally a notebook }",
10244                    "three.txt": "editing text, sure why not?"
10245                }),
10246            )
10247            .await;
10248
10249            let project = Project::test(fs, ["root1".as_ref()], cx).await;
10250            let (workspace, cx) =
10251                cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
10252
10253            let worktree_id = project.update(cx, |project, cx| {
10254                project.worktrees(cx).next().unwrap().read(cx).id()
10255            });
10256
10257            let handle = workspace
10258                .update_in(cx, |workspace, window, cx| {
10259                    let project_path = (worktree_id, "one.png");
10260                    workspace.open_path(project_path, None, true, window, cx)
10261                })
10262                .await
10263                .unwrap();
10264
10265            // Now we can check if the handle we got back errored or not
10266            assert_eq!(
10267                handle.to_any().entity_type(),
10268                TypeId::of::<TestPngItemView>()
10269            );
10270
10271            let handle = workspace
10272                .update_in(cx, |workspace, window, cx| {
10273                    let project_path = (worktree_id, "two.ipynb");
10274                    workspace.open_path(project_path, None, true, window, cx)
10275                })
10276                .await
10277                .unwrap();
10278
10279            assert_eq!(
10280                handle.to_any().entity_type(),
10281                TypeId::of::<TestIpynbItemView>()
10282            );
10283
10284            let handle = workspace
10285                .update_in(cx, |workspace, window, cx| {
10286                    let project_path = (worktree_id, "three.txt");
10287                    workspace.open_path(project_path, None, true, window, cx)
10288                })
10289                .await;
10290            assert!(handle.is_err());
10291        }
10292
10293        #[gpui::test]
10294        async fn test_register_project_item_two_enter_one_leaves(cx: &mut TestAppContext) {
10295            init_test(cx);
10296
10297            cx.update(|cx| {
10298                register_project_item::<TestPngItemView>(cx);
10299                register_project_item::<TestAlternatePngItemView>(cx);
10300            });
10301
10302            let fs = FakeFs::new(cx.executor());
10303            fs.insert_tree(
10304                "/root1",
10305                json!({
10306                    "one.png": "BINARYDATAHERE",
10307                    "two.ipynb": "{ totally a notebook }",
10308                    "three.txt": "editing text, sure why not?"
10309                }),
10310            )
10311            .await;
10312            let project = Project::test(fs, ["root1".as_ref()], cx).await;
10313            let (workspace, cx) =
10314                cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
10315            let worktree_id = project.update(cx, |project, cx| {
10316                project.worktrees(cx).next().unwrap().read(cx).id()
10317            });
10318
10319            let handle = workspace
10320                .update_in(cx, |workspace, window, cx| {
10321                    let project_path = (worktree_id, "one.png");
10322                    workspace.open_path(project_path, None, true, window, cx)
10323                })
10324                .await
10325                .unwrap();
10326
10327            // This _must_ be the second item registered
10328            assert_eq!(
10329                handle.to_any().entity_type(),
10330                TypeId::of::<TestAlternatePngItemView>()
10331            );
10332
10333            let handle = workspace
10334                .update_in(cx, |workspace, window, cx| {
10335                    let project_path = (worktree_id, "three.txt");
10336                    workspace.open_path(project_path, None, true, window, cx)
10337                })
10338                .await;
10339            assert!(handle.is_err());
10340        }
10341    }
10342
10343    fn pane_items_paths(pane: &Entity<Pane>, cx: &App) -> Vec<String> {
10344        pane.read(cx)
10345            .items()
10346            .flat_map(|item| {
10347                item.project_paths(cx)
10348                    .into_iter()
10349                    .map(|path| path.path.to_string_lossy().to_string())
10350            })
10351            .collect()
10352    }
10353
10354    pub fn init_test(cx: &mut TestAppContext) {
10355        cx.update(|cx| {
10356            let settings_store = SettingsStore::test(cx);
10357            cx.set_global(settings_store);
10358            theme::init(theme::LoadThemes::JustBase, cx);
10359            language::init(cx);
10360            crate::init_settings(cx);
10361            Project::init_settings(cx);
10362        });
10363    }
10364
10365    fn dirty_project_item(id: u64, path: &str, cx: &mut App) -> Entity<TestProjectItem> {
10366        let item = TestProjectItem::new(id, path, cx);
10367        item.update(cx, |item, _| {
10368            item.is_dirty = true;
10369        });
10370        item
10371    }
10372}