From b4bd0ec1e2fd204c09cf489b5ac65748837ded4c Mon Sep 17 00:00:00 2001 From: l0ng-ai <24760907+l0ng-ai@users.noreply.github.com> Date: Fri, 6 Nov 2026 05:35:43 +0800 Subject: [PATCH] feat(debugger): Reload Changed Classes in the editor context menu (#1627) Shown on a Java file while a debug session can reload classes (java-debug hot code replace); it runs the existing debug.reloadChangedClasses action. --- src/features/debugger/__tests__/hotswap.test.ts | 9 ++++++++- src/features/debugger/hotswap.ts | 7 +++++++ src/features/debugger/index.ts | 7 ++++++- 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/features/debugger/__tests__/hotswap.test.ts b/src/features/debugger/__tests__/hotswap.test.ts index 3473a32b..6ce4e5a6 100644 --- a/src/features/debugger/__tests__/hotswap.test.ts +++ b/src/features/debugger/__tests__/hotswap.test.ts @@ -30,7 +30,7 @@ vi.mock("@/workbench/contributions", async (importOriginal) => { import type { DebugCapabilities, DebugSessionId } from "@/platform/ipc"; import { getContext } from "@/platform/context"; import { _resetI18nForTests, setLocale } from "@/platform/i18n"; -import { hotSwapMessage, onHotCodeReplace, reloadChangedClasses } from "../hotswap"; +import { hotSwapMenuItems, hotSwapMessage, onHotCodeReplace, reloadChangedClasses } from "../hotswap"; import { publishContext, useDebug, type DebugSession } from "../state"; function session(id: number, hotCodeReplace: boolean): DebugSession { @@ -80,6 +80,13 @@ describe("HotSwap", () => { expect(getContext().debugCanHotSwap).toBe(false); }); + it("offers the reload in the editor menu of a Java file while a session can reload classes", () => { + expect(hotSwapMenuItems(["/p/app/src/A.java"])).toEqual([{ kind: "action", action: "debug.reloadChangedClasses" }]); + expect(hotSwapMenuItems(["/p/app/pom.xml"])).toEqual([]); + useDebug.setState({ sessions: { 2: session(2, false) }, active: 2 }); + expect(hotSwapMenuItems(["/p/app/src/A.java"])).toEqual([]); + }); + it("builds, reloads in Java sessions and reports the count", async () => { await reloadChangedClasses(); expect(ipc.javaBuildWorkspace).toHaveBeenCalledWith("/p/app"); diff --git a/src/features/debugger/hotswap.ts b/src/features/debugger/hotswap.ts index b24eb225..e4e03923 100644 --- a/src/features/debugger/hotswap.ts +++ b/src/features/debugger/hotswap.ts @@ -9,6 +9,7 @@ import { call, commands, isTauri, type DebugEvent, type DebugSessionId } from "@ import { maybeEditorService, notifications } from "@/workbench/contributions"; import { isLive, useDebug, type DebugSession } from "./state"; import { tx } from "@/platform/i18n"; +import type { MenuItem } from "@/ui/Menu"; const KEY = "debug.hotswap"; /** `BUILD_COMPLETE` events of our own build are not worth a prompt. */ @@ -18,6 +19,12 @@ export function hotSwapSessions(): DebugSession[] { return Object.values(useDebug.getState().sessions).filter((s) => isLive(s) && !!s.caps?.hotCodeReplace); } +/** Editor menu: Reload Changed Classes on a Java file while a debug session can reload classes. */ +export function hotSwapMenuItems(paths: readonly string[]): MenuItem[] { + const java = paths.length === 1 && /\.java$/i.test(paths[0]!); + return java && hotSwapSessions().length ? [{ kind: "action", action: "debug.reloadChangedClasses" }] : []; +} + /** "3 classes", translated per count (the sentence around it is translated on its own). */ const classesText = (n: number) => (n === 1 ? tx`${n} class` : tx`${n} classes`); diff --git a/src/features/debugger/index.ts b/src/features/debugger/index.ts index 5c606dfa..0414745d 100644 --- a/src/features/debugger/index.ts +++ b/src/features/debugger/index.ts @@ -24,7 +24,7 @@ import { breakpointGutter } from "./editor/breakpointGutter"; import { executionPoint } from "./editor/execution"; import { runToCursorInlay } from "./editor/runToCursor"; import { installSmartStepPicking, smartStepTargets } from "./editor/smartStep"; -import { onHotCodeReplace } from "./hotswap"; +import { hotSwapMenuItems, onHotCodeReplace } from "./hotswap"; import { activeSession, handleDebugEvent, @@ -68,6 +68,11 @@ export function activate(): () => void { menus: ["editor"], items: () => (activeSession()?.status === "paused" ? [{ kind: "action", action: "debug.evaluate" }] : []), }), + registerMenuGroup({ + id: "debugger.hotswap", + menus: ["editor"], + items: (_menu, { paths }) => hotSwapMenuItems(paths), + }), // Stop (⌘F2) offers the running debug sessions beside run processes. registerMenuGroup({ id: "debugger.stop",