aboutsummaryrefslogtreecommitdiff
path: root/packages/bun-vscode/src
diff options
context:
space:
mode:
Diffstat (limited to 'packages/bun-vscode/src')
-rw-r--r--packages/bun-vscode/src/activate.ts118
-rw-r--r--packages/bun-vscode/src/dap.ts1305
-rw-r--r--packages/bun-vscode/src/extension.ts16
-rw-r--r--packages/bun-vscode/src/features/debug.ts153
-rw-r--r--packages/bun-vscode/src/features/lockfile.ts (renamed from packages/bun-vscode/src/lockfile.ts)24
-rw-r--r--packages/bun-vscode/src/jsc.ts308
-rw-r--r--packages/bun-vscode/src/web-extension.ts9
7 files changed, 170 insertions, 1763 deletions
diff --git a/packages/bun-vscode/src/activate.ts b/packages/bun-vscode/src/activate.ts
deleted file mode 100644
index cbcb8cc1a..000000000
--- a/packages/bun-vscode/src/activate.ts
+++ /dev/null
@@ -1,118 +0,0 @@
-import * as vscode from "vscode";
-import { CancellationToken, DebugConfiguration, ProviderResult, WorkspaceFolder } from "vscode";
-import { DAPAdapter } from "./dap";
-import lockfile from "./lockfile";
-
-export function activateBunDebug(context: vscode.ExtensionContext, factory?: vscode.DebugAdapterDescriptorFactory) {
- lockfile(context);
-
- context.subscriptions.push(
- vscode.commands.registerCommand("extension.bun.runEditorContents", (resource: vscode.Uri) => {
- let targetResource = resource;
- if (!targetResource && vscode.window.activeTextEditor) {
- targetResource = vscode.window.activeTextEditor.document.uri;
- }
- if (targetResource) {
- vscode.debug.startDebugging(
- undefined,
- {
- type: "bun",
- name: "Run File",
- request: "launch",
- program: targetResource.fsPath,
- },
- { noDebug: true },
- );
- }
- }),
- vscode.commands.registerCommand("extension.bun.debugEditorContents", (resource: vscode.Uri) => {
- let targetResource = resource;
- if (!targetResource && vscode.window.activeTextEditor) {
- targetResource = vscode.window.activeTextEditor.document.uri;
- }
- if (targetResource) {
- vscode.debug.startDebugging(undefined, {
- type: "bun",
- name: "Debug File",
- request: "launch",
- program: targetResource.fsPath,
- stopOnEntry: true,
- });
- }
- }),
- );
-
- context.subscriptions.push(
- vscode.commands.registerCommand("extension.bun.getProgramName", config => {
- return vscode.window.showInputBox({
- placeHolder: "Please enter the name of a file in the workspace folder",
- value: "src/index.js",
- });
- }),
- );
-
- const provider = new BunConfigurationProvider();
- context.subscriptions.push(vscode.debug.registerDebugConfigurationProvider("bun", provider));
-
- context.subscriptions.push(
- vscode.debug.registerDebugConfigurationProvider(
- "bun",
- {
- provideDebugConfigurations(folder: WorkspaceFolder | undefined): ProviderResult<DebugConfiguration[]> {
- return [
- {
- name: "Launch",
- request: "launch",
- type: "bun",
- program: "${file}",
- },
- ];
- },
- },
- vscode.DebugConfigurationProviderTriggerKind.Dynamic,
- ),
- );
-
- if (!factory) {
- factory = new InlineDebugAdapterFactory();
- }
- context.subscriptions.push(vscode.debug.registerDebugAdapterDescriptorFactory("bun", factory));
- if ("dispose" in factory) {
- // @ts-expect-error ???
- context.subscriptions.push(factory);
- }
-}
-
-class BunConfigurationProvider implements vscode.DebugConfigurationProvider {
- resolveDebugConfiguration(
- folder: WorkspaceFolder | undefined,
- config: DebugConfiguration,
- token?: CancellationToken,
- ): ProviderResult<DebugConfiguration> {
- // if launch.json is missing or empty
- if (!config.type && !config.request && !config.name) {
- const editor = vscode.window.activeTextEditor;
- if (editor && editor.document.languageId === "javascript") {
- config.type = "bun";
- config.name = "Launch";
- config.request = "launch";
- config.program = "${file}";
- config.stopOnEntry = true;
- }
- }
-
- if (!config.program) {
- return vscode.window.showInformationMessage("Cannot find a program to debug").then(_ => {
- return undefined; // abort launch
- });
- }
-
- return config;
- }
-}
-
-class InlineDebugAdapterFactory implements vscode.DebugAdapterDescriptorFactory {
- createDebugAdapterDescriptor(_session: vscode.DebugSession): ProviderResult<vscode.DebugAdapterDescriptor> {
- return new vscode.DebugAdapterInlineImplementation(new DAPAdapter(_session));
- }
-}
diff --git a/packages/bun-vscode/src/dap.ts b/packages/bun-vscode/src/dap.ts
deleted file mode 100644
index efa7e08b4..000000000
--- a/packages/bun-vscode/src/dap.ts
+++ /dev/null
@@ -1,1305 +0,0 @@
-import * as vscode from "vscode";
-import { spawn, type ChildProcess } from "node:child_process";
-import {
- ContinuedEvent,
- InitializedEvent,
- LoadedSourceEvent,
- LoggingDebugSession,
- OutputEvent,
- StoppedEvent,
- ThreadEvent,
- Thread,
- ExitedEvent,
- TerminatedEvent,
- Source,
- BreakpointEvent,
- DebugSession,
-} from "@vscode/debugadapter";
-import type { DebugProtocol as DAP } from "@vscode/debugprotocol";
-import { JSCClient, type JSC } from "./jsc";
-import { isAbsolute } from "node:path";
-
-const capabilities: Required<DAP.Capabilities> = {
- /** The debug adapter supports the `configurationDone` request. */
- supportsConfigurationDoneRequest: true,
- /** The debug adapter supports function breakpoints. */
- supportsFunctionBreakpoints: true,
- /** The debug adapter supports conditional breakpoints. */
- supportsConditionalBreakpoints: true,
- /** The debug adapter supports breakpoints that break execution after a specified number of hits. */
- supportsHitConditionalBreakpoints: true, // TODO
- /** The debug adapter supports a (side effect free) `evaluate` request for data hovers. */
- supportsEvaluateForHovers: true,
- /** Available exception filter options for the `setExceptionBreakpoints` request. */
- exceptionBreakpointFilters: [],
- /** The debug adapter supports stepping back via the `stepBack` and `reverseContinue` requests. */
- supportsStepBack: false,
- /** The debug adapter supports setting a variable to a value. */
- supportsSetVariable: false, // TODO
- /** The debug adapter supports restarting a frame. */
- supportsRestartFrame: false, // TODO
- /** The debug adapter supports the `gotoTargets` request. */
- supportsGotoTargetsRequest: false, // TODO
- /** The debug adapter supports the `stepInTargets` request. */
- supportsStepInTargetsRequest: false, // TODO
- /** The debug adapter supports the `completions` request. */
- supportsCompletionsRequest: false, // TODO
- /** The set of characters that should trigger completion in a REPL. If not specified, the UI should assume the `.` character. */
- completionTriggerCharacters: [".", "[", '"', "'"],
- /** The debug adapter supports the `modules` request. */
- supportsModulesRequest: true,
- /** The set of additional module information exposed by the debug adapter. */
- additionalModuleColumns: [],
- /** Checksum algorithms supported by the debug adapter. */
- supportedChecksumAlgorithms: [],
- /** The debug adapter supports the `restart` request. In this case a client should not implement `restart` by terminating and relaunching the adapter but by calling the `restart` request. */
- supportsRestartRequest: false,
- /** The debug adapter supports `exceptionOptions` on the `setExceptionBreakpoints` request. */
- supportsExceptionOptions: true,
- /** The debug adapter supports a `format` attribute on the `stackTrace`, `variables`, and `evaluate` requests. */
- supportsValueFormattingOptions: false, // TODO
- /** The debug adapter supports the `exceptionInfo` request. */
- supportsExceptionInfoRequest: true,
- /** The debug adapter supports the `terminateDebuggee` attribute on the `disconnect` request. */
- supportTerminateDebuggee: true,
- /** The debug adapter supports the `suspendDebuggee` attribute on the `disconnect` request. */
- supportSuspendDebuggee: false,
- /** The debug adapter supports the delayed loading of parts of the stack, which requires that both the `startFrame` and `levels` arguments and the `totalFrames` result of the `stackTrace` request are supported. */
- supportsDelayedStackTraceLoading: true,
- /** The debug adapter supports the `loadedSources` request. */
- supportsLoadedSourcesRequest: true,
- /** The debug adapter supports log points by interpreting the `logMessage` attribute of the `SourceBreakpoint`. */
- supportsLogPoints: true,
- /** The debug adapter supports the `terminateThreads` request. */
- supportsTerminateThreadsRequest: false,
- /** The debug adapter supports the `setExpression` request. */
- supportsSetExpression: false, // TODO
- /** The debug adapter supports the `terminate` request. */
- supportsTerminateRequest: true,
- /** The debug adapter supports data breakpoints. */
- supportsDataBreakpoints: true,
- /** The debug adapter supports the `readMemory` request. */
- supportsReadMemoryRequest: false,
- /** The debug adapter supports the `writeMemory` request. */
- supportsWriteMemoryRequest: false,
- /** The debug adapter supports the `disassemble` request. */
- supportsDisassembleRequest: false,
- /** The debug adapter supports the `cancel` request. */
- supportsCancelRequest: false,
- /** The debug adapter supports the `breakpointLocations` request. */
- supportsBreakpointLocationsRequest: true,
- /** The debug adapter supports the `clipboard` context value in the `evaluate` request. */
- supportsClipboardContext: false, // TODO
- /** The debug adapter supports stepping granularities (argument `granularity`) for the stepping requests. */
- supportsSteppingGranularity: false, // TODO
- /** The debug adapter supports adding breakpoints based on instruction references. */
- supportsInstructionBreakpoints: true,
- /** The debug adapter supports `filterOptions` as an argument on the `setExceptionBreakpoints` request. */
- supportsExceptionFilterOptions: false, // TODO
- /** The debug adapter supports the `singleThread` property on the execution requests (`continue`, `next`, `stepIn`, `stepOut`, `reverseContinue`, `stepBack`). */
- supportsSingleThreadExecutionRequests: false,
-};
-
-const nodejsCapabilities: DAP.Capabilities = {
- supportsConfigurationDoneRequest: true,
- supportsFunctionBreakpoints: false,
- supportsConditionalBreakpoints: true,
- supportsHitConditionalBreakpoints: true,
- supportsEvaluateForHovers: true,
- supportsReadMemoryRequest: true,
- supportsWriteMemoryRequest: true,
- exceptionBreakpointFilters: [
- {
- filter: "all",
- label: "Caught Exceptions",
- default: false,
- supportsCondition: true,
- description: "Breaks on all throw errors, even if they're caught later.",
- conditionDescription: `error.name == "MyError"`,
- },
- {
- filter: "uncaught",
- label: "Uncaught Exceptions",
- default: false,
- supportsCondition: true,
- description: "Breaks only on errors or promise rejections that are not handled.",
- conditionDescription: `error.name == "MyError"`,
- },
- ],
- supportsStepBack: false,
- supportsSetVariable: true,
- supportsRestartFrame: true,
- supportsGotoTargetsRequest: false,
- supportsStepInTargetsRequest: true,
- supportsCompletionsRequest: true,
- supportsModulesRequest: false,
- additionalModuleColumns: [],
- supportedChecksumAlgorithms: [],
- supportsRestartRequest: true,
- supportsExceptionOptions: false,
- supportsValueFormattingOptions: true,
- supportsExceptionInfoRequest: true,
- supportTerminateDebuggee: true,
- supportsDelayedStackTraceLoading: true,
- supportsLoadedSourcesRequest: true,
- supportsLogPoints: true,
- supportsTerminateThreadsRequest: false,
- supportsSetExpression: true,
- supportsTerminateRequest: false,
- completionTriggerCharacters: [".", "[", '"', "'"],
- supportsBreakpointLocationsRequest: true,
- supportsClipboardContext: true,
- supportsExceptionFilterOptions: true,
- //supportsEvaluationOptions: extended ? true : false,
- //supportsDebuggerProperties: extended ? true : false,
- //supportsSetSymbolOptions: extended ? true : false,
- //supportsDataBreakpoints: false,
- //supportsDisassembleRequest: false,
-};
-
-export type LaunchRequestArguments = DAP.LaunchRequestArguments & {
- program: string;
-};
-
-export type AttachRequestArguments = DAP.AttachRequestArguments & {
- url?: string;
- port?: number;
-};
-
-export class DAPAdapter extends LoggingDebugSession implements Context {
- #session: vscode.DebugSession;
- #pendingSources = new Map<string, Array<{ resolve: Function; reject: Function }>>();
- #client?: JSCClient;
- #process?: ChildProcess;
- #thread?: DAP.Thread;
- #ready: AbortController;
- #sources: Map<string, DAP.Source>;
- #scriptIds: Map<number, number>;
- #stackFrames: DAP.StackFrame[];
- #scopes: Map<number, DAP.Scope[]>;
- #frameIds = new Array<string>(64);
- #callFrames = new Array<JSC.Debugger.CallFrame>(64);
- #callFramesRange = [0, 0];
-
- public constructor(session: vscode.DebugSession) {
- super();
- this.#resumePromise = new Promise(resolve => {
- this.#resume = resolve;
- });
-
- this.#ready = new AbortController();
- this.#session = session;
- this.#sources = new Map();
- this.#scriptIds = new Map();
- this.#stackFrames = [];
- this.#scopes = new Map();
- // 1-based lines and columns
- this.setDebuggerLinesStartAt1(true);
- this.setDebuggerColumnsStartAt1(false);
- }
-
- #ack<R extends DAP.Response = DAP.Response>(response: R, extra?: Partial<R>["body"]): void {
- this.sendResponse({ ...response, body: extra, success: true });
- }
-
- #nack(response: DAP.Response, error?: unknown): void {
- const message = error instanceof Error ? error.message : String(error);
- this.sendResponse({ ...response, success: false, message });
- }
-
- #todo(response: DAP.Response, label: string): void {
- this.#nack(response, `TODO: ${label}`);
- }
-
- #noop(response: DAP.Response, label: string): void {
- this.#nack(response, `Not supported: ${label}`);
- }
-
- async #send<R extends DAP.Response, T extends keyof JSC.RequestMap>(
- response: R,
- method: T,
- params?: JSC.Request<T>["params"],
- callback?: (result: JSC.ResponseMap[T]) => Partial<R["body"]> | void,
- ) {
- try {
- const result = await this.#client.fetch(method, params);
- const ack = callback?.(result);
- if (ack) {
- this.#ack(response, ack);
- } else {
- this.#ack(response);
- }
- } catch (error) {
- console.error(error);
- this.#nack(response, error);
- }
- }
-
- getReferenceId(objectId: string): number {
- try {
- const { injectedScriptId, id } = JSON.parse(objectId);
- const referenceId = Number(`${injectedScriptId}${id}`);
- if (isNaN(referenceId)) {
- throw new Error();
- }
- return referenceId;
- } catch {
- return hashCode(objectId);
- }
- }
-
- getObjectId(referenceId: number): string {
- const objectId = String(referenceId);
- try {
- const injectedScriptId = Number(objectId.slice(0, 1));
- const id = Number(objectId.slice(1));
- return JSON.stringify({ injectedScriptId, id });
- } catch {
- return objectId;
- }
- }
-
- getStackFrameId(callFrameId: string): number {
- try {
- const { injectedScriptId, ordinal } = JSON.parse(callFrameId);
- const frameId = Number(`${injectedScriptId}${ordinal}`);
- if (isNaN(frameId)) {
- throw new Error();
- }
- return frameId;
- } catch {
- return hashCode(callFrameId);
- }
- }
-
- getCallFrameId(stackFrameId: number): string {
- const objectId = String(stackFrameId);
- try {
- const injectedScriptId = Number(objectId.slice(0, 1));
- const ordinal = Number(objectId.slice(1));
- return JSON.stringify({ injectedScriptId, ordinal });
- } catch {
- return objectId;
- }
- }
-
- getSource(scriptId: string): DAP.Source | undefined {
- return this.#sources.get(scriptId);
- }
-
- getModuleId(scriptId: string): number | undefined {
- return undefined; // TODO
- }
-
- async getProperties(objectId: string): Promise<JSC.Runtime.PropertyDescriptor[]> {
- const { properties } = await this.#client.fetch("Runtime.getDisplayableProperties", {
- objectId,
- });
- let hasEntries = false;
- for (const { name } of properties) {
- if (name === "entries") {
- hasEntries = true;
- }
- // HACK: Do not call on arrays, as it appears to error the debugger.
- // Internal error [code: -32000]
- if (name === "at") {
- hasEntries = false;
- break;
- }
- }
- if (!hasEntries) {
- return properties;
- }
- const { entries } = await this.#client.fetch("Runtime.getCollectionEntries", {
- objectId,
- });
- const results: JSC.Runtime.PropertyDescriptor[] = [...properties.reverse()];
- for (let i = entries.length - 1; i >= 0; i--) {
- const { key, value } = entries[i];
- results.push({
- name: key?.description ?? `${i}`,
- value,
- });
- }
- return results.reverse();
- }
-
- protected onEvent(event: JSC.Event): void {
- console.log(Date.now(), "JSC Event:", event);
- const { method, params } = event;
- this[method]?.(params);
- }
-
- protected ["Debugger.scriptParsed"](event: JSC.Debugger.ScriptParsedEvent): void {
- let { sourceURL, url, scriptId } = event;
-
- if (!url) {
- url = sourceURL;
- }
-
- if (!url) {
- return; // If the script has no URL, it is an `eval` command.
- }
-
- const name = vscode.workspace.asRelativePath(url);
- const scriptIdNumber = scriptIdFromEvent(scriptId);
- const source = new Source(name, url, scriptIdNumber);
- source.sourceReference = scriptIdNumber;
- this.#sources.set(scriptId, source);
- this.#scriptIds.set(hashCode(url), scriptIdNumber);
- this.sendEvent(new LoadedSourceEvent("new", source));
- const pendingMap = this.#pendingSources;
- const promises = pendingMap.get(url);
- if (promises) {
- pendingMap.delete(url);
- for (const { resolve } of promises) {
- resolve();
- }
- }
-
- if (!this.#thread) {
- this.#thread = new Thread(0, url);
- this.sendEvent(new ThreadEvent("started", 0));
- }
- }
-
- protected ["Debugger.paused"](event: JSC.Debugger.PausedEvent): void {
- const { reason, callFrames, asyncStackTrace } = event;
-
- this.sendEvent(new StoppedEvent(pauseReason(reason), this.#thread?.id ?? 0));
- const stackFrames: DAP.StackFrame[] = [];
- const scopes: Map<number, DAP.Scope[]> = new Map();
- const frameIds = this.#frameIds;
- frameIds.length =
- callFrames.length +
- (asyncStackTrace?.callFrames?.length || 0) +
- (asyncStackTrace?.parentStackTrace?.callFrames?.length || 0);
- const frames = this.#callFrames;
- frames.length = callFrames.length;
- let frameId = 0;
- for (const callFrame of callFrames) {
- const stackFrame = formatStackFrame(this, callFrame);
- frames[frameId] = callFrame;
- frameIds[frameId++] = callFrame.callFrameId;
- stackFrames.push(stackFrame);
- const frameScopes: DAP.Scope[] = [];
- for (const scope of callFrame.scopeChain) {
- frameScopes.push(...formatScope(this, scope));
- }
- scopes.set(stackFrame.id, frameScopes);
- }
-
- if (asyncStackTrace?.callFrames?.length) {
- for (const callFrame of asyncStackTrace.callFrames) {
- frameIds[frameId++] = "";
- stackFrames.push(formatAsyncStackFrame(this, callFrame));
- }
- }
-
- if (asyncStackTrace?.parentStackTrace?.callFrames?.length) {
- for (const callFrame of asyncStackTrace.parentStackTrace.callFrames) {
- frameIds[frameId++] = "";
- stackFrames.push(formatAsyncStackFrame(this, callFrame));
- }
- }
- this.#scopes = scopes;
- this.#stackFrames = stackFrames;
- }
-
- protected ["Debugger.resumed"](event: JSC.Debugger.ResumedEvent): void {
- this.#frameIds.length = 0;
- this.#callFrames.length = 0;
- this.sendEvent(new ContinuedEvent(this.#thread?.id));
- }
-
- handleMessage(message: DAP.ProtocolMessage): void {
- console.log(Date.now(), "DAP Request:", message);
- super.handleMessage(message);
- }
-
- sendResponse(response: DAP.Response): void {
- console.log(Date.now(), "DAP Response:", response);
- super.sendResponse(response);
- }
-
- sendEvent(event: DAP.Event): void {
- console.log(Date.now(), "DAP Event:", event);
- super.sendEvent(event);
- }
-
- runInTerminalRequest(
- args: DAP.RunInTerminalRequestArguments,
- timeout: number,
- cb: (response: DAP.RunInTerminalResponse) => void,
- ): void {
- // TODO
- }
-
- protected initializeRequest(response: DAP.InitializeResponse, args: DAP.InitializeRequestArguments): void {
- this.#ack(response, nodejsCapabilities);
- this.sendEvent(new InitializedEvent());
- }
-
- protected async disconnectRequest(
- response: DAP.DisconnectResponse,
- args: DAP.DisconnectArguments,
- request?: DAP.Request,
- ): Promise<void> {
- await this.#client?.fetch("Debugger.disable");
- const { terminateDebuggee } = args;
- if (terminateDebuggee) {
- this.#process?.kill();
- }
- await this.#ack(response);
- }
-
- async #launch(path: string): Promise<void> {
- this.#process?.kill();
- const url = "localhost:9232";
- // // TODO: Change to "bun" before merging, or make it configurable
- // const process = spawn("bun-debug", ["--inspect=" + url, "run", path], {
- // cwd: this.#session.workspaceFolder?.uri?.fsPath,
- // stdio: ["ignore", "pipe", "pipe"],
- // env: {
- // ...globalThis.process.env,
- // NODE_ENV: "development",
- // BUN_DEBUG_QUIET_LOGS: "1",
- // FORCE_COLOR: "1",
- // },
- // });
- // let resolve,
- // reject,
- // promise = new Promise<void>((res, rej) => {
- // resolve = res;
- // reject = rej;
- // });
- // process.on("error", error => {
- // console.error(error);
-
- // vscode.window.showErrorMessage(`Failed to start Bun: ${error.message}`);
- // this.sendEvent(new ExitedEvent(-1));
- // this.#process = undefined;
- // reject(error);
- // });
- // process.on("exit", exitCode => {
- // this.sendEvent(new ExitedEvent(exitCode));
- // this.#process = undefined;
- // });
- // process.stdout.on("data", (data: Buffer) => {
- // console.log(data);
- // this.sendEvent(new OutputEvent(data.toString(), "stdout"));
- // });
- // process.stderr.on("data", (data: Buffer) => {
- // console.error(data);
- // this.sendEvent(new OutputEvent(data.toString(), "stderr"));
- // });
- // this.#process = process;
-
- // process.once("spawn", () => {
- // this.#attach(url).then(resolve, reject);
- // });
- return this.#attach(url);
- }
-
- async #attach(url: string): Promise<void> {
- this.#client?.close();
- const client = new JSCClient({
- url,
- onEvent: this.onEvent.bind(this),
- onResponse(response) {
- console.log(Date.now(), "JSC Response:", response);
- },
- onRequest(request) {
- console.log(Date.now(), "JSC Request:", request);
- },
- onError(error) {
- console.error("JSC Error:", error);
- },
- onClose(code, reason) {
- console.log(Date.now(), "JSC Close:", code, reason);
- this.#process?.kill();
- },
- });
- this.#client = client;
- globalThis.jsc = client;
- await client.ready;
- await Promise.all([
- client.fetch("Runtime.enable"),
- client.fetch("Console.enable"),
- client.fetch("Debugger.enable"),
- client.fetch("Debugger.setAsyncStackTraceDepth", { depth: 100 }),
- client.fetch("Debugger.setPauseOnDebuggerStatements", { enabled: true }),
- client.fetch("Debugger.setBreakpointsActive", { active: true }),
- ]);
- }
-
- protected async launchRequest(
- response: DAP.LaunchResponse,
- args: LaunchRequestArguments,
- request?: DAP.Request,
- ): Promise<void> {
- await new Promise<void>(resolve => {
- if (this.#ready.signal.aborted) {
- resolve();
- return;
- }
- this.#ready.signal.addEventListener("abort", () => {
- resolve();
- });
- });
- const { program } = args;
- try {
- await this.#launch(program);
- this.#ack(response);
- } catch (error) {
- this.#nack(response, error);
- }
- }
-
- #resume = undefined;
- #resumePromise = undefined;
-
- protected async attachRequest(
- response: DAP.AttachResponse,
- args: AttachRequestArguments,
- request?: DAP.Request,
- ): Promise<void> {
- console.log("Attach!");
- const { url, port } = args;
- try {
- if (url) {
- await this.#attach(url);
- } else if (port) {
- await this.#attach(`localhost:${port}`);
- } else {
- await this.#attach("localhost:9229");
- }
- this.#resume();
- this.#ack(response);
- } catch (error) {
- this.#nack(response, error);
- }
- }
-
- protected terminateRequest(
- response: DAP.TerminateResponse,
- args: DAP.TerminateArguments,
- request?: DAP.Request,
- ): void {
- this.#client?.close();
- this.sendEvent(new TerminatedEvent());
- this.#ack(response);
- }
-
- protected restartRequest(response: DAP.RestartResponse, args: DAP.RestartArguments, request?: DAP.Request): void {
- this.#noop(response, "restartRequest");
- }
-
- getScriptIdFromSource(source: DAP.Source): string {
- if (source.sourceReference) {
- return String(source.sourceReference);
- }
-
- // @ts-expect-error
- if (source.sourceReferenceInternal) {
- // @ts-expect-error
- return String(source.sourceReferenceInternal);
- }
-
- const { path } = source;
- const id = this.#scriptIds.get(hashCode(path));
- if (!id) {
- return undefined;
- }
-
- return String(id);
- }
-
- async waitForSourceToBeLoaded(source: DAP.Source): Promise<void> {
- const pendingMap = this.#pendingSources;
- let promises = pendingMap.get(source.path);
- if (!promises) {
- promises = [];
- pendingMap.set(source.path, promises);
- }
-
- await new Promise<void>((resolve, reject) => {
- promises.push({ resolve, reject });
- });
- }
-
- protected async setBreakPointsRequest(
- response: DAP.SetBreakpointsResponse,
- args: DAP.SetBreakpointsArguments,
- request?: DAP.Request,
- ): Promise<void> {
- if (!args.breakpoints?.length) {
- this.#nack(response, "No breakpoints");
- return;
- }
- if (!this.#client) {
- await this.#resumePromise;
- }
-
- const { source, breakpoints } = args;
- let scriptId = this.getScriptIdFromSource(source);
-
- if (!scriptId) {
- await this.waitForSourceToBeLoaded(source);
- scriptId = this.getScriptIdFromSource(source);
- }
-
- const results: DAP.Breakpoint[] = await Promise.all(
- breakpoints.map(({ line, column }) =>
- this.#client
- .fetch("Debugger.setBreakpoint", {
- location: {
- scriptId,
- lineNumber: line,
- columnNumber: column,
- },
- })
- .then(
- ({ breakpointId, actualLocation }) => {
- return {
- id: Number(breakpointId),
- line: actualLocation.lineNumber,
- // column: actualLocation.columnNumber,
- verified: true,
- };
- },
- err => {
- if (err?.code === -32000) {
- return undefined;
- }
-
- throw err;
- },
- ),
- ),
- );
- this.#ack(response, { breakpoints: results.filter(Boolean) });
- }
-
- protected setFunctionBreakPointsRequest(
- response: DAP.SetFunctionBreakpointsResponse,
- args: DAP.SetFunctionBreakpointsArguments,
- request?: DAP.Request,
- ): void {
- this.#todo(response, "setFunctionBreakPointsRequest");
- }
-
- protected setExceptionBreakPointsRequest(
- response: DAP.SetExceptionBreakpointsResponse,
- args: DAP.SetExceptionBreakpointsArguments,
- request?: DAP.Request,
- ): void {
- this.#todo(response, "setExceptionBreakPointsRequest");
- }
-
- protected configurationDoneRequest(
- response: DAP.ConfigurationDoneResponse,
- args: DAP.ConfigurationDoneArguments,
- request?: DAP.Request,
- ): void {
- super.configurationDoneRequest(response, args, request);
- this.#ready.abort();
- // this.#ack(response);
- }
-
- protected async continueRequest(
- response: DAP.ContinueResponse,
- args: DAP.ContinueArguments,
- request?: DAP.Request,
- ): Promise<void> {
- await this.#send(response, "Debugger.resume");
- }
-
- protected async nextRequest(
- response: DAP.NextResponse,
- args: DAP.NextArguments,
- request?: DAP.Request,
- ): Promise<void> {
- await this.#send(response, "Debugger.stepNext");
- }
-
- protected async stepInRequest(
- response: DAP.StepInResponse,
- args: DAP.StepInArguments,
- request?: DAP.Request,
- ): Promise<void> {
- await this.#send(response, "Debugger.stepInto");
- }
-
- protected async stepOutRequest(
- response: DAP.StepOutResponse,
- args: DAP.StepOutArguments,
- request?: DAP.Request,
- ): Promise<void> {
- await this.#send(response, "Debugger.stepOut");
- }
-
- protected stepBackRequest(response: DAP.StepBackResponse, args: DAP.StepBackArguments, request?: DAP.Request): void {
- this.#todo(response, "stepBackRequest");
- }
-
- protected reverseContinueRequest(
- response: DAP.ReverseContinueResponse,
- args: DAP.ReverseContinueArguments,
- request?: DAP.Request,
- ): void {
- this.#todo(response, "reverseContinueRequest");
- }
-
- protected restartFrameRequest(
- response: DAP.RestartFrameResponse,
- args: DAP.RestartFrameArguments,
- request?: DAP.Request,
- ): void {
- this.#todo(response, "restartFrameRequest");
- }
-
- protected gotoRequest(response: DAP.GotoResponse, args: DAP.GotoArguments, request?: DAP.Request): void {
- this.#todo(response, "gotoRequest");
- }
-
- protected pauseRequest(response: DAP.PauseResponse, args: DAP.PauseArguments, request?: DAP.Request): void {
- this.#send(response, "Debugger.pause");
- }
-
- protected async sourceRequest(
- response: DAP.SourceResponse,
- args: DAP.SourceArguments,
- request?: DAP.Request,
- ): Promise<void> {
- const { sourceReference, source } = args;
- const scriptId = sourceReference ? String(sourceReference) : this.getScriptIdFromSource(source);
-
- await this.#send(response, "Debugger.getScriptSource", { scriptId }, ({ scriptSource }) => ({
- content: scriptSource,
- }));
- }
-
- protected threadsRequest(response: DAP.ThreadsResponse, request?: DAP.Request): void {
- if (this.#thread) {
- this.#ack(response, { threads: [this.#thread] });
- } else {
- this.#ack(response, { threads: [] });
- }
- }
-
- protected terminateThreadsRequest(
- response: DAP.TerminateThreadsResponse,
- args: DAP.TerminateThreadsArguments,
- request?: DAP.Request,
- ): void {
- this.#todo(response, "terminateThreadsRequest");
- }
-
- protected stackTraceRequest(
- response: DAP.StackTraceResponse,
- args: DAP.StackTraceArguments,
- request?: DAP.Request,
- ): void {
- const totalFrames = this.#stackFrames.length;
- const { startFrame = 0, levels = totalFrames } = args;
- const stackFrames = this.#stackFrames.slice(
- (this.#callFramesRange[0] = startFrame),
- (this.#callFramesRange[1] = Math.min(totalFrames, startFrame + levels)),
- );
-
- this.#ack(response, {
- stackFrames,
- totalFrames,
- });
- }
-
- protected scopesRequest(response: DAP.ScopesResponse, args: DAP.ScopesArguments, request?: DAP.Request): void {
- const { frameId } = args;
- const scopes = this.#scopes.get(frameId) ?? [];
- this.#ack(response, { scopes });
- }
-
- protected async variablesRequest(
- response: DAP.VariablesResponse,
- args: DAP.VariablesArguments,
- request?: DAP.Request,
- ): Promise<void> {
- const { variablesReference } = args;
- const objectId = this.getObjectId(variablesReference);
- try {
- const variables = await formatObject(this, objectId);
- this.#ack(response, { variables });
- } catch (error) {
- this.#nack(response, error);
- }
- }
-
- protected setVariableRequest(
- response: DAP.SetVariableResponse,
- args: DAP.SetVariableArguments,
- request?: DAP.Request,
- ): void {
- this.#todo(response, "setVariableRequest");
- }
-
- protected setExpressionRequest(
- response: DAP.SetExpressionResponse,
- args: DAP.SetExpressionArguments,
- request?: DAP.Request,
- ): void {
- this.#todo(response, "setExpressionRequest");
- }
-
- protected async evaluateRequest(
- response: DAP.EvaluateResponse,
- args: DAP.EvaluateArguments,
- request?: DAP.Request,
- ): Promise<void> {
- const { context, expression, frameId } = args;
- let callFrame: JSC.Debugger.CallFrame =
- typeof frameId === "number"
- ? this.#callFrames[this.#stackFrames.findIndex(frame => frame.id === frameId)]
- : undefined;
-
- if (callFrame && context === "hover") {
- if (expression.includes(".")) {
- // TODO: use getDisplayableProperties to make this side-effect free.
- // for each ".", call Runtime.getProperties on the previous result and find the specific property
- await this.#send(
- response,
- "Debugger.evaluateOnCallFrame",
- {
- expression,
- callFrameId: callFrame.callFrameId,
- "includeCommandLineAPI": false,
- },
- ({ result: { objectId, value, description }, wasThrown }) => {
- return {
- result: value ?? description,
- variablesReference: objectId ? this.getReferenceId(objectId) : 0,
- };
- },
- );
- } else {
- for (let scope of callFrame.scopeChain) {
- if (scope.empty || scope.type === "with" || scope.type === "global") {
- continue;
- }
-
- // For the rest of the scopes, it is artificial transient object enumerating scope variables as its properties.
- const { properties } = await this.#client.fetch("Runtime.getDisplayableProperties", {
- "objectId": scope.object.objectId,
- "fetchStart": 0,
- "fetchCount": 100,
- });
-
- for (let i = 0; i < properties.length; i++) {
- const prop = properties[i];
- const { name, value } = prop;
- if (name === expression) {
- if (value) {
- const { objectId } = value;
- response.body = {
- result: value?.value || value?.description || "",
- variablesReference: objectId ? this.getReferenceId(objectId) : 0,
- type: value?.type || "undefined",
- presentationHint: presentationHintForProperty(prop, !!value.classPrototype),
- };
- } else {
- response.body = {
- result: "undefined",
- variablesReference: 0,
- type: "undefined",
- };
- }
- response.success = true;
- this.sendResponse(response);
- return;
- }
- }
- }
- }
- } else {
- await this.#send(
- response,
- "Runtime.evaluate",
- {
- expression,
- includeCommandLineAPI: true,
- },
- ({ result: { objectId, value, description }, wasThrown }) => {
- return {
- result: value ?? description,
- variablesReference: objectId ? this.getReferenceId(objectId) : 0,
- };
- },
- );
- }
- }
-
- protected stepInTargetsRequest(
- response: DAP.StepInTargetsResponse,
- args: DAP.StepInTargetsArguments,
- request?: DAP.Request,
- ): void {
- this.#todo(response, "stepInTargetsRequest");
- }
-
- protected gotoTargetsRequest(
- response: DAP.GotoTargetsResponse,
- args: DAP.GotoTargetsArguments,
- request?: DAP.Request,
- ): void {
- this.#todo(response, "gotoTargetsRequest");
- }
-
- protected completionsRequest(
- response: DAP.CompletionsResponse,
- args: DAP.CompletionsArguments,
- request?: DAP.Request,
- ): void {
- this.#todo(response, "completionsRequest");
- }
-
- protected exceptionInfoRequest(
- response: DAP.ExceptionInfoResponse,
- args: DAP.ExceptionInfoArguments,
- request?: DAP.Request,
- ): void {
- this.#todo(response, "exceptionInfoRequest");
- }
-
- protected loadedSourcesRequest(
- response: DAP.LoadedSourcesResponse,
- args: DAP.LoadedSourcesArguments,
- request?: DAP.Request,
- ): void {
- this.#todo(response, "loadedSourcesRequest");
- }
-
- protected dataBreakpointInfoRequest(
- response: DAP.DataBreakpointInfoResponse,
- args: DAP.DataBreakpointInfoArguments,
- request?: DAP.Request,
- ): void {
- this.#todo(response, "dataBreakpointInfoRequest");
- }
-
- protected setDataBreakpointsRequest(
- response: DAP.SetDataBreakpointsResponse,
- args: DAP.SetDataBreakpointsArguments,
- request?: DAP.Request,
- ): void {
- this.#todo(response, "setDataBreakpointsRequest");
- }
-
- protected readMemoryRequest(
- response: DAP.ReadMemoryResponse,
- args: DAP.ReadMemoryArguments,
- request?: DAP.Request,
- ): void {
- this.#todo(response, "readMemoryRequest");
- }
-
- protected writeMemoryRequest(
- response: DAP.WriteMemoryResponse,
- args: DAP.WriteMemoryArguments,
- request?: DAP.Request,
- ): void {
- this.#todo(response, "writeMemoryRequest");
- }
-
- protected disassembleRequest(
- response: DAP.DisassembleResponse,
- args: DAP.DisassembleArguments,
- request?: DAP.Request,
- ): void {
- this.#todo(response, "disassembleRequest");
- }
-
- protected cancelRequest(response: DAP.CancelResponse, args: DAP.CancelArguments, request?: DAP.Request): void {
- this.#todo(response, "cancelRequest");
- }
-
- protected async breakpointLocationsRequest(
- response: DAP.BreakpointLocationsResponse,
- args: DAP.BreakpointLocationsArguments,
- request?: DAP.Request,
- ): Promise<void> {
- const { line, endLine, column, endColumn, source } = args;
- console.log(source);
- let scriptId: string = this.getScriptIdFromSource(source);
- if (!scriptId) {
- await this.waitForSourceToBeLoaded(source);
- scriptId = this.getScriptIdFromSource(source);
- }
-
- if (!scriptId) {
- this.#nack(response, new Error("Either source.path or source.sourceReference must be specified"));
- return;
- }
-
- await this.#send(
- response,
- "Debugger.getBreakpointLocations",
- {
- start: {
- scriptId,
- lineNumber: line,
- columnNumber: column,
- },
- end: {
- scriptId,
- lineNumber: endLine ?? line + 1,
- columnNumber: endColumn,
- },
- },
- ({ locations }) => {
- return {
- breakpoints: locations.map(({ lineNumber, columnNumber }) => ({
- line: lineNumber,
- column: columnNumber,
- })),
- };
- },
- );
- }
-
- protected setInstructionBreakpointsRequest(
- response: DAP.SetInstructionBreakpointsResponse,
- args: DAP.SetInstructionBreakpointsArguments,
- request?: DAP.Request,
- ): void {
- this.#todo(response, "setInstructionBreakpointsRequest");
- }
-
- protected customRequest(command: string, response: DAP.Response, args: any, request?: DAP.Request): void {
- super.customRequest(command, response, args, request);
- }
-
- protected convertClientLineToDebugger(line: number): number {
- return line;
- }
-
- protected convertDebuggerLineToClient(line: number): number {
- return line;
- }
-
- protected convertClientColumnToDebugger(column: number): number {
- return column;
- }
-
- protected convertDebuggerColumnToClient(column: number): number {
- return column;
- }
-
- protected convertClientPathToDebugger(clientPath: string): string {
- return clientPath;
- }
-
- protected convertDebuggerPathToClient(debuggerPath: string): string {
- return debuggerPath;
- }
-}
-
-function hashCode(string: string): number {
- let hash = 0,
- i,
- chr;
- if (string.length === 0) return hash;
- for (i = 0; i < string.length; i++) {
- chr = string.charCodeAt(i);
- hash = (hash << 5) - hash + chr;
- hash |= 0;
- }
- return hash;
-}
-
-interface Context {
- getReferenceId(objectId: string): number;
- getObjectId(referenceId: number): string;
- getStackFrameId(callFrameId: string): number;
- getCallFrameId(stackFrameId: number): string;
- getSource(scriptId: string): DAP.Source | undefined;
- getModuleId(scriptId: string): number | undefined;
- getProperties(objectId: string): Promise<JSC.Runtime.PropertyDescriptor[]>;
-}
-
-function formatStackFrame(ctx: Context, callFrame: JSC.Debugger.CallFrame): DAP.StackFrame {
- const { callFrameId, functionName, location } = callFrame;
- const { scriptId, lineNumber, columnNumber = 0 } = location;
- const source = ctx.getSource(scriptId);
- return {
- id: ctx.getStackFrameId(callFrameId),
- name: functionName || "<anonymous>",
- line: lineNumber,
- column: columnNumber,
- source,
- moduleId: ctx.getModuleId(scriptId),
- presentationHint: source?.presentationHint || !source?.path ? "subtle" : "normal",
- };
-}
-
-function formatAsyncStackFrame(ctx: Context, callFrame: JSC.Console.CallFrame): DAP.StackFrame {
- const { functionName, scriptId, lineNumber, columnNumber } = callFrame;
- return {
- id: hashCode(functionName + "-" + scriptId + "-" + lineNumber + "-" + columnNumber),
- name: functionName || "<anonymous>",
- line: lineNumber,
- column: columnNumber,
- source: ctx.getSource(scriptId),
- moduleId: scriptId,
- };
-}
-
-function formatScope(ctx: Context, scope: JSC.Debugger.Scope): DAP.Scope[] {
- const { name, type, location, object, empty } = scope;
- if (empty) {
- return [];
- }
- const presentationHint = formatScopeHint(type);
- const title = presentationHint.charAt(0).toUpperCase() + presentationHint.slice(1);
- const displayName = name ? `${title}: ${name}` : title;
- return [
- {
- name: displayName,
- presentationHint,
- expensive: presentationHint === "globals",
- variablesReference: object ? ctx.getReferenceId(object.objectId) : 0,
- line: location?.lineNumber,
- column: location?.columnNumber,
- source: location && ctx.getSource(location.scriptId),
- },
- ];
-}
-
-function formatScopeHint(type: JSC.Debugger.Scope["type"]): "arguments" | "locals" | "globals" | "" {
- switch (type) {
- case "closure":
- return "locals"; // ?
- case "functionName":
- case "with":
- case "catch":
- case "nestedLexical":
- return "locals";
- case "global":
- case "globalLexicalEnvironment":
- return "globals";
- default:
- return "";
- }
-}
-
-async function formatObject(ctx: Context, objectId: JSC.Runtime.RemoteObjectId): Promise<DAP.Variable[]> {
- const properties = await ctx.getProperties(objectId);
- return properties.flatMap(property => formatProperty(ctx, property));
-}
-
-function formatProperty(ctx: Context, propertyDescriptor: JSC.Runtime.PropertyDescriptor): DAP.Variable[] {
- const { name, value, get, set, symbol, isOwn } = propertyDescriptor;
- const variables: DAP.Variable[] = [];
- if (value) {
- variables.push(formatPropertyValue(ctx, name, value, propertyDescriptor));
- }
- return variables;
-}
-
-function formatPropertyValue(
- ctx: Context,
- name: string,
- remoteObject: JSC.Runtime.RemoteObject,
- descriptor: JSC.Runtime.PropertyDescriptor,
-): DAP.Variable {
- const { type, subtype, value, description, objectId } = remoteObject;
- return {
- name,
- value: description ?? "",
- type: subtype ?? type,
- variablesReference: objectId ? ctx.getReferenceId(objectId) : 0,
- presentationHint:
- value && typeof value !== "object" && typeof value !== "undefined"
- ? formatPropertyHint(descriptor, typeof value)
- : formatPropertyHint(value || descriptor, ""),
- };
-}
-
-function formatPropertyHint(
- propertyDescriptor: JSC.Runtime.PropertyDescriptor,
- valueType: string,
-): DAP.VariablePresentationHint {
- const { value, get, set, configurable, enumerable, writable, isOwn } = propertyDescriptor;
- const hasGetter = get?.type !== "undefined";
- const hasSetter = set?.type !== "undefined";
- const hint: DAP.VariablePresentationHint = {
- kind: (value && formatPropertyKind(value)) ?? "property",
- attributes: [],
- visibility: "public",
- };
- if (!writable && !hasSetter && hasGetter) {
- hint.attributes.push("readOnly");
- }
- if (!enumerable && !hasGetter) {
- hint.visibility = "internal";
- }
-
- if (valueType) {
- hint.kind = "data";
- }
-
- return hint;
-}
-
-function formatPropertyKind(remoteObject: JSC.Runtime.RemoteObject): DAP.VariablePresentationHint["kind"] {
- const { type, subtype, className, value } = remoteObject;
- if (type === "function") {
- return "method";
- }
- if (subtype === "class") {
- return "class";
- }
- if (className?.endsWith("Event")) {
- return "event";
- }
- if (value) {
- return "data";
- }
-
- return "property";
-}
-
-function scriptIdFromLocation(location: JSC.Debugger.Location): number {
- const id = Number(location.scriptId);
- console.log({ id });
- return id;
-}
-
-function pauseReason(reason: JSC.EventMap["Debugger.paused"]["reason"]): DAP.StoppedEvent["body"]["reason"] {
- return reason;
-}
-
-function scriptIdFromEvent(scriptId: JSC.Debugger.ScriptParsedEvent["scriptId"]): number {
- return Number(scriptId);
-}
-
-function presentationHintForProperty(property: JSC.Runtime.PropertyDescriptor, isClass): DAP.VariablePresentationHint {
- const attributes = [];
- if (!property.writable) {
- attributes.push("readOnly");
- }
-
- let kind = "";
- if (isClass) {
- kind = "class";
- } else if (property.get || property.set) {
- kind = "property";
- } else if (property.value) {
- kind = "data";
- }
-
- return {
- attributes,
- visibility: property.isPrivate || property.symbol || !property.enumerable ? "private" : "public",
- kind,
- };
-}
diff --git a/packages/bun-vscode/src/extension.ts b/packages/bun-vscode/src/extension.ts
index f840dd7a9..e333aedd7 100644
--- a/packages/bun-vscode/src/extension.ts
+++ b/packages/bun-vscode/src/extension.ts
@@ -1,16 +1,10 @@
import * as vscode from "vscode";
-import { activateBunDebug } from "./activate";
-
-const runMode: "external" | "server" | "namedPipeServer" | "inline" = "inline";
+import activateLockfile from "./features/lockfile";
+import activateDebug from "./features/debug";
export function activate(context: vscode.ExtensionContext) {
- if (runMode === "inline") {
- activateBunDebug(context);
- return;
- }
- throw new Error(`This extension does not support '${runMode}' mode.`);
+ activateLockfile(context);
+ activateDebug(context);
}
-export function deactivate() {
- // No-op
-}
+export function deactivate() {}
diff --git a/packages/bun-vscode/src/features/debug.ts b/packages/bun-vscode/src/features/debug.ts
new file mode 100644
index 000000000..3b841ea66
--- /dev/null
+++ b/packages/bun-vscode/src/features/debug.ts
@@ -0,0 +1,153 @@
+import * as vscode from "vscode";
+import type { CancellationToken, DebugConfiguration, ProviderResult, WorkspaceFolder } from "vscode";
+import type { DAP } from "../../../bun-debug-adapter-protocol";
+import { DebugAdapter } from "../../../bun-debug-adapter-protocol";
+import { DebugSession } from "@vscode/debugadapter";
+
+const debugConfiguration: vscode.DebugConfiguration = {
+ type: "bun",
+ request: "launch",
+ name: "Debug Bun",
+ program: "${file}",
+ watch: true,
+};
+
+const runConfiguration: vscode.DebugConfiguration = {
+ type: "bun",
+ request: "launch",
+ name: "Run Bun",
+ program: "${file}",
+ watch: true,
+};
+
+const attachConfiguration: vscode.DebugConfiguration = {
+ type: "bun",
+ request: "attach",
+ name: "Attach to Bun",
+ url: "ws://localhost:6499/",
+};
+
+const debugConfigurations: vscode.DebugConfiguration[] = [debugConfiguration, attachConfiguration];
+
+export default function (context: vscode.ExtensionContext, factory?: vscode.DebugAdapterDescriptorFactory) {
+ context.subscriptions.push(
+ vscode.commands.registerCommand("extension.bun.runFile", (resource: vscode.Uri) => {
+ let targetResource = resource;
+ if (!targetResource && vscode.window.activeTextEditor) {
+ targetResource = vscode.window.activeTextEditor.document.uri;
+ }
+ if (targetResource) {
+ vscode.debug.startDebugging(undefined, runConfiguration, {
+ noDebug: true,
+ });
+ }
+ }),
+ vscode.commands.registerCommand("extension.bun.debugFile", (resource: vscode.Uri) => {
+ let targetResource = resource;
+ if (!targetResource && vscode.window.activeTextEditor) {
+ targetResource = vscode.window.activeTextEditor.document.uri;
+ }
+ if (targetResource) {
+ vscode.debug.startDebugging(undefined, {
+ ...debugConfiguration,
+ program: targetResource.fsPath,
+ });
+ }
+ }),
+ );
+
+ const provider = new BunConfigurationProvider();
+ context.subscriptions.push(vscode.debug.registerDebugConfigurationProvider("bun", provider));
+
+ context.subscriptions.push(
+ vscode.debug.registerDebugConfigurationProvider(
+ "bun",
+ {
+ provideDebugConfigurations(folder: WorkspaceFolder | undefined): ProviderResult<DebugConfiguration[]> {
+ return debugConfigurations;
+ },
+ },
+ vscode.DebugConfigurationProviderTriggerKind.Dynamic,
+ ),
+ );
+
+ if (!factory) {
+ factory = new InlineDebugAdapterFactory();
+ }
+ context.subscriptions.push(vscode.debug.registerDebugAdapterDescriptorFactory("bun", factory));
+ if ("dispose" in factory && typeof factory.dispose === "function") {
+ // @ts-ignore
+ context.subscriptions.push(factory);
+ }
+}
+
+class BunConfigurationProvider implements vscode.DebugConfigurationProvider {
+ resolveDebugConfiguration(
+ folder: WorkspaceFolder | undefined,
+ config: DebugConfiguration,
+ token?: CancellationToken,
+ ): ProviderResult<DebugConfiguration> {
+ if (!config.type && !config.request && !config.name) {
+ const editor = vscode.window.activeTextEditor;
+ if (editor && isJavaScript(editor.document.languageId)) {
+ Object.assign(config, debugConfiguration);
+ }
+ }
+ return config;
+ }
+}
+
+class InlineDebugAdapterFactory implements vscode.DebugAdapterDescriptorFactory {
+ createDebugAdapterDescriptor(_session: vscode.DebugSession): ProviderResult<vscode.DebugAdapterDescriptor> {
+ const adapter = new VSCodeAdapter(_session);
+ return new vscode.DebugAdapterInlineImplementation(adapter);
+ }
+}
+
+function isJavaScript(languageId: string): boolean {
+ return (
+ languageId === "javascript" ||
+ languageId === "javascriptreact" ||
+ languageId === "typescript" ||
+ languageId === "typescriptreact"
+ );
+}
+
+export class VSCodeAdapter extends DebugSession {
+ #adapter: DebugAdapter;
+ #dap: vscode.OutputChannel;
+
+ constructor(session: vscode.DebugSession) {
+ super();
+ this.#dap = vscode.window.createOutputChannel("Debug Adapter Protocol");
+ this.#adapter = new DebugAdapter({
+ sendToAdapter: this.sendMessage.bind(this),
+ });
+ }
+
+ sendMessage(message: DAP.Request | DAP.Response | DAP.Event): void {
+ console.log("[dap] -->", message);
+ this.#dap.appendLine("--> " + JSON.stringify(message));
+
+ const { type } = message;
+ if (type === "response") {
+ this.sendResponse(message);
+ } else if (type === "event") {
+ this.sendEvent(message);
+ } else {
+ throw new Error(`Not supported: ${type}`);
+ }
+ }
+
+ handleMessage(message: DAP.Event | DAP.Request | DAP.Response): void {
+ console.log("[dap] <--", message);
+ this.#dap.appendLine("<-- " + JSON.stringify(message));
+
+ this.#adapter.accept(message);
+ }
+
+ dispose() {
+ this.#adapter.close();
+ this.#dap.dispose();
+ }
+}
diff --git a/packages/bun-vscode/src/lockfile.ts b/packages/bun-vscode/src/features/lockfile.ts
index 22b48faba..81adf5b9e 100644
--- a/packages/bun-vscode/src/lockfile.ts
+++ b/packages/bun-vscode/src/features/lockfile.ts
@@ -52,10 +52,10 @@ function previewLockfile(uri: vscode.Uri, token?: vscode.CancellationToken): Pro
process.stderr.on("data", (data: Buffer) => {
stderr += data.toString();
});
- process.on("error", (error) => {
+ process.on("error", error => {
reject(error);
});
- process.on("exit", (code) => {
+ process.on("exit", code => {
if (code === 0) {
resolve(stdout);
} else {
@@ -65,19 +65,15 @@ function previewLockfile(uri: vscode.Uri, token?: vscode.CancellationToken): Pro
});
}
-export default function(context: vscode.ExtensionContext): void {
+export default function (context: vscode.ExtensionContext): void {
const viewType = "bun.lockb";
const provider = new BunLockfileEditorProvider(context);
-
- vscode.window.registerCustomEditorProvider(
- viewType,
- provider,
- {
- supportsMultipleEditorsPerDocument: true,
- webviewOptions: {
- enableFindWidget: true,
- retainContextWhenHidden: true,
- },
+
+ vscode.window.registerCustomEditorProvider(viewType, provider, {
+ supportsMultipleEditorsPerDocument: true,
+ webviewOptions: {
+ enableFindWidget: true,
+ retainContextWhenHidden: true,
},
- );
+ });
}
diff --git a/packages/bun-vscode/src/jsc.ts b/packages/bun-vscode/src/jsc.ts
deleted file mode 100644
index 5b8d4ed84..000000000
--- a/packages/bun-vscode/src/jsc.ts
+++ /dev/null
@@ -1,308 +0,0 @@
-import { Socket, createConnection } from "node:net";
-import { inspect } from "node:util";
-import type { JSC } from "../types/jsc";
-export type { JSC };
-
-export type JSCClientOptions = {
- url: string | URL;
- retry?: boolean;
- onEvent?: (event: JSC.Event) => void;
- onRequest?: (request: JSC.Request) => void;
- onResponse?: (response: JSC.Response) => void;
- onError?: (error: Error) => void;
- onClose?: (code: number, reason: string) => void;
-};
-const headerInvalidNumber = 2147483646;
-
-// We use non-printable characters to separate messages in the stream.
-// These should never appear in textual messages.
-
-// These are non-sequential so that code which just counts up from 0 doesn't accidentally parse them as messages.
-// 0x12 0x11 0x13 0x14 as a little-endian 32-bit unsigned integer
-const headerPrefix = "\x14\x13\x11\x12";
-
-// 0x14 0x12 0x13 0x11 as a little-endian 32-bit unsigned integer
-const headerSuffixString = "\x11\x13\x12\x14";
-
-const headerSuffixInt = Buffer.from(headerSuffixString).readInt32LE(0);
-const headerPrefixInt = Buffer.from(headerPrefix).readInt32LE(0);
-
-const messageLengthBuffer = new ArrayBuffer(12);
-const messageLengthDataView = new DataView(messageLengthBuffer);
-messageLengthDataView.setInt32(0, headerPrefixInt, true);
-messageLengthDataView.setInt32(8, headerSuffixInt, true);
-
-function writeJSONMessageToBuffer(message: any) {
- const asString = JSON.stringify(message);
- const byteLength = Buffer.byteLength(asString, "utf8");
- const buffer = Buffer.allocUnsafe(12 + byteLength);
- buffer.writeInt32LE(headerPrefixInt, 0);
- buffer.writeInt32LE(byteLength, 4);
- buffer.writeInt32LE(headerSuffixInt, 8);
- if (buffer.write(asString, 12, byteLength, "utf8") !== byteLength) {
- throw new Error("Failed to write message to buffer");
- }
-
- return buffer;
-}
-
-let currentMessageLength = 0;
-const DEBUGGING = true;
-function extractMessageLengthAndOffsetFromBytes(buffer: Buffer, offset: number) {
- const bufferLength = buffer.length;
- while (offset < bufferLength) {
- const headerStart = buffer.indexOf(headerPrefix, offset, "binary");
- if (headerStart === -1) {
- if (DEBUGGING) {
- console.error("No header found in buffer of length " + bufferLength + " starting at offset " + offset);
- }
- return headerInvalidNumber;
- }
-
- // [headerPrefix (4), byteLength (4), headerSuffix (4)]
- if (bufferLength <= headerStart + 12) {
- if (DEBUGGING) {
- console.error(
- "Not enough bytes for header in buffer of length " + bufferLength + " starting at offset " + offset,
- );
- }
- return headerInvalidNumber;
- }
-
- const prefix = buffer.readInt32LE(headerStart);
- const byteLengthInt = buffer.readInt32LE(headerStart + 4);
- const suffix = buffer.readInt32LE(headerStart + 8);
-
- if (prefix !== headerPrefixInt || suffix !== headerSuffixInt) {
- offset = headerStart + 1;
- currentMessageLength = 0;
-
- if (DEBUGGING) {
- console.error(
- "Invalid header in buffer of length " + bufferLength + " starting at offset " + offset + ": " + prefix,
- byteLengthInt,
- suffix,
- );
- }
- continue;
- }
-
- if (byteLengthInt < 0) {
- if (DEBUGGING) {
- console.error(
- "Invalid byteLength in buffer of length " + bufferLength + " starting at offset " + offset + ": " + prefix,
- byteLengthInt,
- suffix,
- );
- }
-
- return headerInvalidNumber;
- }
-
- if (byteLengthInt === 0) {
- // Ignore 0-length messages
- // Shouldn't happen in practice
- offset = headerStart + 12;
- currentMessageLength = 0;
-
- if (DEBUGGING) {
- console.error(
- "Ignoring 0-length message in buffer of length " + bufferLength + " starting at offset " + offset,
- );
- console.error({
- buffer: buffer,
- string: buffer.toString(),
- });
- }
-
- continue;
- }
-
- currentMessageLength = byteLengthInt;
-
- return headerStart + 12;
- }
-
- if (DEBUGGING) {
- if (bufferLength > 0)
- console.error("Header not found in buffer of length " + bufferLength + " starting at offset " + offset);
- }
-
- return headerInvalidNumber;
-}
-
-class StreamingReader {
- pendingBuffer: Buffer;
-
- constructor() {
- this.pendingBuffer = Buffer.alloc(0);
- }
-
- *onMessage(chunk: Buffer) {
- let buffer: Buffer;
- if (this.pendingBuffer.length > 0) {
- this.pendingBuffer = buffer = Buffer.concat([this.pendingBuffer, chunk]);
- } else {
- this.pendingBuffer = buffer = chunk;
- }
-
- currentMessageLength = 0;
-
- for (
- let offset = extractMessageLengthAndOffsetFromBytes(buffer, 0);
- buffer.length > 0 && offset !== headerInvalidNumber;
- currentMessageLength = 0, offset = extractMessageLengthAndOffsetFromBytes(buffer, 0)
- ) {
- const messageLength = currentMessageLength;
- const start = offset;
- const end = start + messageLength;
- offset = end;
- const messageChunk = buffer.slice(start, end);
- this.pendingBuffer = buffer = buffer.slice(offset);
- yield messageChunk.toString();
- }
- }
-}
-
-export class JSCClient {
- #options: JSCClientOptions;
- #requestId: number;
- #pendingMessages: Buffer[];
- #pendingRequests: Map<number, (result: unknown) => void>;
- #socket: Socket;
- #ready?: Promise<void>;
- #reader = new StreamingReader();
- signal?: AbortSignal;
-
- constructor(options: JSCClientOptions) {
- this.#options = options;
- this.#socket = undefined;
- this.#requestId = 1;
-
- this.#pendingMessages = [];
- this.#pendingRequests = new Map();
- }
-
- get ready(): Promise<void> {
- if (!this.#ready) {
- this.#ready = this.#connect();
- }
- return this.#ready;
- }
-
- #connect(): Promise<void> {
- const { url, retry, onError, onResponse, onEvent, onClose } = this.#options;
- let [host, port] = typeof url === "string" ? url.split(":") : [url.hostname, url.port];
- if (port == null) {
- if (host == null) {
- host = "localhost";
- port = "9229";
- } else {
- port = "9229";
- }
- }
-
- if (host == null) {
- host = "localhost";
- }
- var resolve,
- reject,
- promise = new Promise<void>((r1, r2) => {
- resolve = r1;
- reject = r2;
- }),
- socket: Socket;
- let didConnect = false;
-
- this.#socket = socket = createConnection(
- {
- host,
- port: Number(port),
- },
- () => {
- for (const message of this.#pendingMessages) {
- this.#send(message);
- }
- this.#pendingMessages.length = 0;
- didConnect = true;
- resolve();
- },
- )
- .once("error", e => {
- const error = new Error(`Socket error: ${e?.message || e}`);
- reject(error);
- })
- .on("data", buffer => {
- for (const message of this.#reader.onMessage(buffer)) {
- let received: JSC.Event | JSC.Response;
- try {
- received = JSON.parse(message);
- } catch {
- const error = new Error(`Invalid WebSocket data: ${inspect(message)}`);
- onError?.(error);
- return;
- }
- console.log({ received });
- if ("id" in received) {
- onResponse?.(received);
- if ("error" in received) {
- const { message, code = "?" } = received.error;
- const error = new Error(`${message} [code: ${code}]`);
- error.code = code;
- onError?.(error);
- this.#pendingRequests.get(received.id)?.(error);
- } else {
- this.#pendingRequests.get(received.id)?.(received.result);
- }
- } else {
- onEvent?.(received);
- }
- }
- })
- .on("close", hadError => {
- if (didConnect) {
- onClose?.(hadError ? 1 : 0, "Socket closed");
- }
- });
-
- return promise;
- }
-
- #send(message: any): void {
- const socket = this.#socket;
- const framed = writeJSONMessageToBuffer(message);
- if (socket && !socket.connecting) {
- socket.write(framed);
- } else {
- this.#pendingMessages.push(framed);
- }
- }
-
- async fetch<T extends keyof JSC.RequestMap>(
- method: T,
- params?: JSC.Request<T>["params"],
- ): Promise<JSC.ResponseMap[T]> {
- const request: JSC.Request<T> = {
- id: this.#requestId++,
- method,
- params,
- };
- this.#options.onRequest?.(request);
- return new Promise((resolve, reject) => {
- const done = (result: Error | JSC.ResponseMap[T]) => {
- this.#pendingRequests.delete(request.id);
- if (result instanceof Error) {
- reject(result);
- } else {
- resolve(result);
- }
- };
- this.#pendingRequests.set(request.id, done);
- this.#send(request);
- });
- }
-
- close(): void {
- if (this.#socket) this.#socket.end();
- }
-}
diff --git a/packages/bun-vscode/src/web-extension.ts b/packages/bun-vscode/src/web-extension.ts
index 2a66793b5..cd2e2623e 100644
--- a/packages/bun-vscode/src/web-extension.ts
+++ b/packages/bun-vscode/src/web-extension.ts
@@ -1,10 +1,5 @@
import * as vscode from "vscode";
-import { activateBunDebug } from "./activate";
-export function activate(context: vscode.ExtensionContext) {
- activateBunDebug(context);
-}
+export function activate(context: vscode.ExtensionContext) {}
-export function deactivate() {
- // No-op
-}
+export function deactivate() {}