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