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