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