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