aboutsummaryrefslogtreecommitdiff
path: root/packages/bun-debug-adapter-protocol/debugger/adapter.ts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/bun-debug-adapter-protocol/debugger/adapter.ts')
-rw-r--r--packages/bun-debug-adapter-protocol/debugger/adapter.ts101
1 files changed, 67 insertions, 34 deletions
diff --git a/packages/bun-debug-adapter-protocol/debugger/adapter.ts b/packages/bun-debug-adapter-protocol/debugger/adapter.ts
index 32bbf0670..6af7d9ea6 100644
--- a/packages/bun-debug-adapter-protocol/debugger/adapter.ts
+++ b/packages/bun-debug-adapter-protocol/debugger/adapter.ts
@@ -1,4 +1,5 @@
import type { DAP } from "..";
+// @ts-ignore: FIXME - there is something wrong with the types
import type { JSC, InspectorListener } from "../../bun-inspector-protocol";
import { WebSocketInspector } from "../../bun-inspector-protocol";
import type { ChildProcess } from "node:child_process";
@@ -7,6 +8,10 @@ import capabilities from "./capabilities";
import { SourceMap } from "./sourcemap";
import { compare, parse } from "semver";
+type InitializeRequest = DAP.InitializeRequest & {
+ supportsConfigurationDoneRequest?: boolean;
+};
+
type LaunchRequest = DAP.LaunchRequest & {
runtime?: string;
program?: string;
@@ -90,6 +95,7 @@ export class DebugAdapter implements IDebugAdapter, InspectorListener {
#functionBreakpoints: Map<string, FunctionBreakpoint>;
#variables: (Variable | Variable[])[];
#process?: ChildProcess;
+ #initialized?: InitializeRequest;
#terminated?: boolean;
constructor({ sendToAdapter }: DebugAdapterOptions) {
@@ -115,6 +121,7 @@ export class DebugAdapter implements IDebugAdapter, InspectorListener {
this.#breakpoints.length = 0;
this.#functionBreakpoints.clear();
this.#variables.length = 1;
+ this.#initialized = undefined;
}
/**
@@ -207,8 +214,8 @@ export class DebugAdapter implements IDebugAdapter, InspectorListener {
});
}
- async initialize(request: DAP.InitializeRequest): Promise<DAP.InitializeResponse> {
- const { clientID, supportsConfigurationDoneRequest } = request as any;
+ async initialize(request: InitializeRequest): Promise<DAP.InitializeResponse> {
+ const { clientID, supportsConfigurationDoneRequest } = (this.#initialized = request);
this.#send("Inspector.enable");
this.#send("Runtime.enable");
@@ -264,7 +271,7 @@ export class DebugAdapter implements IDebugAdapter, InspectorListener {
throw new Error("Program must be a JavaScript or TypeScript file.");
}
- const subprocess = spawn(runtime, ["--inspect-wait", "--inspect=0", ...args, program], {
+ const subprocess = spawn(runtime, ["--inspect-wait=0", ...args, program], {
stdio: ["ignore", "pipe", "pipe", "pipe"],
cwd,
env: inheritEnv ? { ...process.env, ...env } : env,
@@ -502,7 +509,11 @@ export class DebugAdapter implements IDebugAdapter, InspectorListener {
#generatedLocation(source: Source, line?: number, column?: number): JSC.Debugger.Location {
const { sourceMap, scriptId, path } = source;
- const { line: line0, column: column0 } = sourceMap.generatedPosition(line, column, path);
+ const { line: line0, column: column0 } = sourceMap.generatedLocation(
+ this.#lineTo0BasedLine(line),
+ this.#columnTo0BasedColumn(column),
+ path,
+ );
return {
scriptId,
@@ -511,6 +522,20 @@ export class DebugAdapter implements IDebugAdapter, InspectorListener {
};
}
+ #lineTo0BasedLine(line?: number): number {
+ if (this.#initialized?.linesStartAt1) {
+ return line ? line - 1 : 0;
+ }
+ return line ?? 0;
+ }
+
+ #columnTo0BasedColumn(column?: number): number {
+ if (this.#initialized?.columnsStartAt1) {
+ return column ? column - 1 : 0;
+ }
+ return column ?? 0;
+ }
+
#originalLocation(
source: Source,
line?: number | JSC.Debugger.Location,
@@ -523,14 +548,28 @@ export class DebugAdapter implements IDebugAdapter, InspectorListener {
}
const { sourceMap } = source;
- const { line: line0, column: column0 } = sourceMap.originalPosition(line, column);
+ const { line: line0, column: column0 } = sourceMap.originalLocation(line, column);
return {
- line: line0,
- column: column0,
+ line: this.#lineFrom0BasedLine(line0),
+ column: this.#columnFrom0BasedColumn(column0),
};
}
+ #lineFrom0BasedLine(line?: number): number {
+ if (this.#initialized?.linesStartAt1) {
+ return line ? line + 1 : 1;
+ }
+ return line ?? 0;
+ }
+
+ #columnFrom0BasedColumn(column?: number): number {
+ if (this.#initialized?.columnsStartAt1) {
+ return column ? column + 1 : 1;
+ }
+ return column ?? 0;
+ }
+
async setBreakpoints(request: DAP.SetBreakpointsRequest): Promise<DAP.SetBreakpointsResponse> {
const { source: source0, breakpoints: requests } = request;
const sourceId = sourceToId(source0);
@@ -853,12 +892,6 @@ export class DebugAdapter implements IDebugAdapter, InspectorListener {
}
async ["Debugger.scriptParsed"](event: JSC.Debugger.ScriptParsedEvent): Promise<void> {
- // HACK: remove once Bun starts sending correct source map urls
- if (event.url && event.url.startsWith("/") && event.url.endsWith(".ts")) {
- event.sourceMapURL = generateSourceMapUrl(event.url);
- } else {
- event.sourceMapURL = undefined;
- }
const { url, scriptId, sourceMapURL } = event;
// If no url is present, the script is from a `evaluate` request.
@@ -966,10 +999,9 @@ export class DebugAdapter implements IDebugAdapter, InspectorListener {
["Console.messageAdded"](event: JSC.Console.MessageAddedEvent): void {
const { message } = event;
- const { type, text, parameters, line, column, stackTrace } = message;
+ const { type, level, text, parameters, line, column, stackTrace } = message;
let output: string;
- let isError: boolean | undefined;
let variablesReference: number | undefined;
if (parameters?.length) {
@@ -978,9 +1010,8 @@ export class DebugAdapter implements IDebugAdapter, InspectorListener {
const variables = parameters.map((parameter, i) => {
const variable = this.#addVariable(parameter, { name: `${i}` });
- const { value, type } = variable;
+ const { value } = variable;
output += value + " ";
- isError ||= type === "error";
return variable;
});
@@ -999,6 +1030,11 @@ export class DebugAdapter implements IDebugAdapter, InspectorListener {
output += "\n";
}
+ const color = consoleLevelToAnsiColor(level);
+ if (color) {
+ output = `${color}${output}`;
+ }
+
if (variablesReference) {
variablesReference = this.#setVariable([
{
@@ -1014,13 +1050,13 @@ export class DebugAdapter implements IDebugAdapter, InspectorListener {
if (stackTrace) {
const { callFrames } = stackTrace;
if (callFrames.length) {
- const [{ scriptId }] = callFrames.slice(0, -1);
+ const { scriptId } = callFrames.at(-1)!;
source = this.#getSourceIfPresent(scriptId);
}
}
this.#emit("output", {
- category: isError ? "stderr" : "debug console",
+ category: "debug console",
group: consoleMessageGroup(type),
output,
variablesReference,
@@ -1680,21 +1716,18 @@ function variablesSortBy(a: DAP.Variable, b: DAP.Variable): number {
return 0;
}
-// HACK: this will be removed once Bun starts sending source maps
-// with the `Debugger.scriptParsed` event.
-function generateSourceMapUrl(path: string): string | undefined {
- const { stdout } = spawnSync("bunx", ["esbuild", path, "--sourcemap=inline"], {
- stdio: "pipe",
- encoding: "utf-8",
- });
- const match = /sourceMappingURL=(.*)/im.exec(stdout);
- if (!match) {
- return undefined;
- }
- const [_, sourceMapUrl] = match;
- return sourceMapUrl;
-}
-
function isSameLocation(a: { line?: number; column?: number }, b: { line?: number; column?: number }): boolean {
return (a.line === b.line || (!a.line && !b.line)) && (a.column === b.column || (!a.column && !b.column));
}
+
+function consoleLevelToAnsiColor(level: JSC.Console.ConsoleMessage["level"]): string | undefined {
+ switch (level) {
+ case "warning":
+ return "\u001b[33m";
+ case "error":
+ return "\u001b[31m";
+ case "debug":
+ return "\u001b[36m";
+ }
+ return undefined;
+}