fix(debugger): Java source paths on Windows and breakpoints before session start (#387)

java-debug treated forward-slash Windows drive paths as URIs, so breakpoints never verified; source paths are now sent with native separators and mapped back for stack frames and breakpoint events. Debug sessions also flush every file's pending breakpoint updates before starting, so configurationDone never runs with an empty breakpoint snapshot (all adapters, launch and attach).

Not verified on a real Windows machine; the path conversion is covered by unit tests.

Fixes #380
Refs #374
Цей коміт міститься в:
l0ng-ai authored and GitHub committed 2026-10-30 17:46:58 +08:00
1 джерело 3655961642
коміт e017154c75
5 files changed
+244 -12

No files matched your search

+28 -8
Переглянути файл
@@ -94,6 +94,7 @@ export function loadBreakpoints(root: string | null) {
const timers = new Map<string, ReturnType<typeof setTimeout>>();
/** Ids in the order last sent per file (backend states are index-aligned). */
const sent = new Map<string, string[]>();
const inFlight = new Map<string, Promise<void>>();
const SYNC_DELAY = 120;
export function scheduleSync(path: string) {
@@ -102,12 +103,35 @@ export function scheduleSync(path: string) {
path,
setTimeout(() => {
timers.delete(path);
void syncFile(path);
void syncFile(path).catch((e) => console.warn("debugSetBreakpoints failed", e));
}, SYNC_DELAY),
);
}
export async function syncFile(path: string) {
/** Drain debounced edits before the backend takes its startup breakpoint snapshot. */
export async function flushBreakpoints() {
let paths = new Set([...timers.keys(), ...inFlight.keys(), ...Object.keys(useBreakpoints.getState().byFile)]);
while (paths.size) {
for (const timer of timers.values()) clearTimeout(timer);
timers.clear();
await Promise.all([...paths].map((path) => syncFile(path)));
paths = new Set([...timers.keys(), ...inFlight.keys()]);
}
}
export function syncFile(path: string): Promise<void> {
const previous = inFlight.get(path) ?? Promise.resolve();
const next = previous.catch(() => {}).then(() => sendFile(path));
inFlight.set(path, next);
void next
.finally(() => {
if (inFlight.get(path) === next) inFlight.delete(path);
})
.catch(() => {});
return next;
}
async function sendFile(path: string) {
if (!isTauri()) return;
const { byFile, muted } = useBreakpoints.getState();
const list = byFile[path] ?? [];
@@ -115,12 +139,8 @@ export async function syncFile(path: string) {
path,
list.map((b) => b.id),
);
try {
const states = await call(commands.debugSetBreakpoints(path, toBackend(list, muted, path)));
applyStates(path, states);
} catch (e) {
console.warn("debugSetBreakpoints failed", e);
}
const states = await call(commands.debugSetBreakpoints(path, toBackend(list, muted, path)));
applyStates(path, states);
}
/** Re-reads verification states after a `breakpointsChanged` event. */