Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 98 additions & 0 deletions src/debugger/logTracker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,8 @@ export class LoggingDebugAdapterTracker implements vscode.DebugAdapterTracker {
return;
}

this.handleBreakpointFallback(debugMessage);

if (debugMessage.event === "exited" && debugMessage.body.exitCode) {
this.exitCode = debugMessage.body.exitCode;
this.exitHandler?.(debugMessage.body.exitCode);
Expand All @@ -131,6 +133,102 @@ export class LoggingDebugAdapterTracker implements vscode.DebugAdapterTracker {
}
}

private handleBreakpointEvent(rawMsg: unknown): void {
try {
// Minimal typed view of the event payload we care about.
type FrameLike = { source?: { path?: string }; line?: number };
type BodyLike = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: instead of defining these types ourselves, we already have "@vscode/debugprotocol" as a dependency in the package.json and can make use of BreakpointEvent and other types defined in there. That way the type checker can flag mismatches :)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it. I’ll switch to the types from @vscode/debugprotocol instead of redefining them.

exitCode?: number;
category?: string;
output?: string;
reason?: string;
thread?: { frames?: FrameLike[] };
source?: { path?: string };
line?: number;
};

const msg = rawMsg as { type?: string; event?: string; body?: BodyLike | undefined };
if (!msg || msg.type !== "event") {
return;
}

// Case A: stopped event with reason = "breakpoint"
if (msg.event === "stopped" && msg.body?.reason === "breakpoint") {
const frames = msg.body.thread?.frames;
if (!Array.isArray(frames) || frames.length === 0) {
return;
}

const top = frames[0];
const sourcePath = top?.source?.path;
const line = top?.line;
if (!sourcePath || typeof line !== "number") {
return;
}

const bpLine0 = line - 1; // VS Code uses 0-based lines

const breakpoints = vscode.debug.breakpoints.filter(
b => b instanceof vscode.SourceBreakpoint
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thought: we probably are ok since looks like a lot of tests passed, but something to keep in mind, is instanceof we try to avoid because if an object was instantiated by the test runner, the extension under test will return false because the types are not the same (different node instances), so we could make the check b => !!b.location since that is the distinguishing property of the SourceBreakpoint

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

actually I see you seem to have that check below, so ya we could simplify by either not performing the filter and let check below make sure we have a location, or if we check for location when filter we can drop that extra if (!loc) check below

) as vscode.SourceBreakpoint[];

for (const bp of breakpoints) {
const loc = bp.location;
if (!loc) {
continue;
}
if (loc.uri.fsPath !== sourcePath) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: does the DAP spec ensure that the sourcePath we get will be for the full path?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right — the DAP spec doesn’t guarantee absolute paths. I’ll normalize the path before comparing.

continue;
}
if (loc.range.start.line !== bpLine0) {
continue;
}

// Force a UI refresh so the breakpoint shows as installed.
vscode.debug.removeBreakpoints([bp]);
vscode.debug.addBreakpoints([bp]);
break;
}
return;
}

// Case B: explicit "breakpoint" event that carries source+line info
if (msg.event === "breakpoint" && msg.body) {
const sourcePath = msg.body.source?.path;
const line = msg.body.line;
if (!sourcePath || typeof line !== "number") {
return;
}

const bpLine0 = line - 1;
const breakpoints = vscode.debug.breakpoints.filter(
b => b instanceof vscode.SourceBreakpoint
) as vscode.SourceBreakpoint[];

for (const bp of breakpoints) {
const loc = bp.location;
if (!loc) {
continue;
}
if (loc.uri.fsPath !== sourcePath) {
continue;
}
if (loc.range.start.line !== bpLine0) {
continue;
}

vscode.debug.removeBreakpoints([bp]);
vscode.debug.addBreakpoints([bp]);
break;
}
return;
}
} catch (err) {
// eslint-disable-next-line no-console
console.error("Breakpoint fallback error:", err);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

todo: Let's get the logger into LoggingDebugAdapterTracker so that way the error ends up in the log file

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, I can move this into the tracker so it gets logged properly.

}
}

/**
* The debug adapter session is about to be stopped. Delete the session from
* the tracker
Expand Down