[Lldb-commits] [lldb] [lldb-dap] Allow providing debug adapter arguments in the extension (PR #129262)
Matthew Bastien via lldb-commits
lldb-commits at lists.llvm.org
Thu Mar 20 15:03:26 PDT 2025
https://github.com/matthewbastien updated https://github.com/llvm/llvm-project/pull/129262
>From 7ab3d3ac41e3aadda9c96dc5c5ea2551be7113c4 Mon Sep 17 00:00:00 2001
From: Matthew Bastien <matthew_bastien at apple.com>
Date: Fri, 28 Feb 2025 11:08:25 -0500
Subject: [PATCH 01/13] allow providing debug adapter arguments
---
lldb/tools/lldb-dap/package.json | 29 +++++--
.../lldb-dap/src-ts/debug-adapter-factory.ts | 78 ++++++++++++-------
2 files changed, 76 insertions(+), 31 deletions(-)
diff --git a/lldb/tools/lldb-dap/package.json b/lldb/tools/lldb-dap/package.json
index 428624f46feba..05e9540df17cf 100644
--- a/lldb/tools/lldb-dap/package.json
+++ b/lldb/tools/lldb-dap/package.json
@@ -76,6 +76,15 @@
"type": "string",
"description": "The path to the lldb-dap binary."
},
+ "lldb-dap.arguments": {
+ "scope": "resource",
+ "type": "array",
+ "default": [],
+ "items": {
+ "type": "string"
+ },
+ "description": "The arguments provided to the lldb-dap process."
+ },
"lldb-dap.log-path": {
"scope": "resource",
"type": "string",
@@ -149,10 +158,6 @@
{
"type": "lldb-dap",
"label": "LLDB DAP Debugger",
- "program": "./bin/lldb-dap",
- "windows": {
- "program": "./bin/lldb-dap.exe"
- },
"configurationAttributes": {
"launch": {
"required": [
@@ -163,6 +168,13 @@
"type": "string",
"markdownDescription": "The absolute path to the LLDB debug adapter executable to use."
},
+ "debugAdapterArgs": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "markdownDescription": "The list of arguments used to launch the debug adapter executable."
+ },
"program": {
"type": "string",
"description": "Path to the program to debug."
@@ -353,6 +365,13 @@
"type": "string",
"markdownDescription": "The absolute path to the LLDB debug adapter executable to use."
},
+ "debugAdapterArgs": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "markdownDescription": "The list of arguments used to launch the debug adapter executable."
+ },
"program": {
"type": "string",
"description": "Path to the program to attach to."
@@ -550,4 +569,4 @@
}
]
}
-}
\ No newline at end of file
+}
diff --git a/lldb/tools/lldb-dap/src-ts/debug-adapter-factory.ts b/lldb/tools/lldb-dap/src-ts/debug-adapter-factory.ts
index c2244dcbde8f2..85c34b773e047 100644
--- a/lldb/tools/lldb-dap/src-ts/debug-adapter-factory.ts
+++ b/lldb/tools/lldb-dap/src-ts/debug-adapter-factory.ts
@@ -25,7 +25,7 @@ async function findWithXcrun(executable: string): Promise<string | undefined> {
if (stdout) {
return stdout.toString().trimEnd();
}
- } catch (error) { }
+ } catch (error) {}
}
return undefined;
}
@@ -93,8 +93,23 @@ async function getDAPExecutable(
return undefined;
}
+function getDAPArguments(session: vscode.DebugSession): string[] {
+ // Check the debug configuration for arguments first
+ const debugConfigArgs = session.configuration.debugAdapterArgs;
+ if (
+ Array.isArray(debugConfigArgs) &&
+ debugConfigArgs.findIndex((entry) => typeof entry !== "string") === -1
+ ) {
+ return debugConfigArgs;
+ }
+ // Fall back on the workspace configuration
+ return vscode.workspace
+ .getConfiguration("lldb-dap")
+ .get<string[]>("arguments", []);
+}
+
async function isServerModeSupported(exe: string): Promise<boolean> {
- const { stdout } = await exec(exe, ['--help']);
+ const { stdout } = await exec(exe, ["--help"]);
return /--connection/.test(stdout);
}
@@ -103,8 +118,13 @@ async function isServerModeSupported(exe: string): Promise<boolean> {
* depending on the session configuration.
*/
export class LLDBDapDescriptorFactory
- implements vscode.DebugAdapterDescriptorFactory, vscode.Disposable {
- private server?: Promise<{ process: child_process.ChildProcess, host: string, port: number }>;
+ implements vscode.DebugAdapterDescriptorFactory, vscode.Disposable
+{
+ private server?: Promise<{
+ process: child_process.ChildProcess;
+ host: string;
+ port: number;
+ }>;
dispose() {
this.server?.then(({ process }) => {
@@ -114,7 +134,7 @@ export class LLDBDapDescriptorFactory
async createDebugAdapterDescriptor(
session: vscode.DebugSession,
- executable: vscode.DebugAdapterExecutable | undefined,
+ _executable: vscode.DebugAdapterExecutable | undefined,
): Promise<vscode.DebugAdapterDescriptor | undefined> {
const config = vscode.workspace.getConfiguration(
"lldb-dap",
@@ -128,7 +148,7 @@ export class LLDBDapDescriptorFactory
}
const configEnvironment =
config.get<{ [key: string]: string }>("environment") || {};
- const dapPath = (await getDAPExecutable(session)) ?? executable?.command;
+ const dapPath = await getDAPExecutable(session);
if (!dapPath) {
LLDBDapDescriptorFactory.showLLDBDapNotFoundMessage();
@@ -142,32 +162,38 @@ export class LLDBDapDescriptorFactory
const dbgOptions = {
env: {
- ...executable?.options?.env,
...configEnvironment,
...env,
},
};
- const dbgArgs = executable?.args ?? [];
-
- const serverMode = config.get<boolean>('serverMode', false);
- if (serverMode && await isServerModeSupported(dapPath)) {
- const { host, port } = await this.startServer(dapPath, dbgArgs, dbgOptions);
+ const dbgArgs = getDAPArguments(session);
+
+ const serverMode = config.get<boolean>("serverMode", false);
+ if (serverMode && (await isServerModeSupported(dapPath))) {
+ const { host, port } = await this.startServer(
+ dapPath,
+ dbgArgs,
+ dbgOptions,
+ );
return new vscode.DebugAdapterServer(port, host);
}
return new vscode.DebugAdapterExecutable(dapPath, dbgArgs, dbgOptions);
}
- startServer(dapPath: string, args: string[], options: child_process.CommonSpawnOptions): Promise<{ host: string, port: number }> {
- if (this.server) return this.server;
+ startServer(
+ dapPath: string,
+ args: string[],
+ options: child_process.CommonSpawnOptions,
+ ): Promise<{ host: string; port: number }> {
+ if (this.server) {
+ return this.server;
+ }
- this.server = new Promise(resolve => {
- args.push(
- '--connection',
- 'connect://localhost:0'
- );
+ this.server = new Promise((resolve) => {
+ args.push("--connection", "connect://localhost:0");
const server = child_process.spawn(dapPath, args, options);
- server.stdout!.setEncoding('utf8').once('data', (data: string) => {
+ server.stdout!.setEncoding("utf8").once("data", (data: string) => {
const connection = /connection:\/\/\[([^\]]+)\]:(\d+)/.exec(data);
if (connection) {
const host = connection[1];
@@ -175,9 +201,9 @@ export class LLDBDapDescriptorFactory
resolve({ process: server, host, port });
}
});
- server.on('exit', () => {
+ server.on("exit", () => {
this.server = undefined;
- })
+ });
});
return this.server;
}
@@ -185,11 +211,11 @@ export class LLDBDapDescriptorFactory
/**
* Shows a message box when the debug adapter's path is not found
*/
- static async showLLDBDapNotFoundMessage(path?: string) {
+ static async showLLDBDapNotFoundMessage(path?: string | undefined) {
const message =
- path
- ? `Debug adapter path: ${path} is not a valid file.`
- : "Unable to find the path to the LLDB debug adapter executable.";
+ path !== undefined
+ ? `Debug adapter path: ${path} is not a valid file`
+ : "Unable to find the LLDB debug adapter executable.";
const openSettingsAction = "Open Settings";
const callbackValue = await vscode.window.showErrorMessage(
message,
>From 635adc9bb8f896e5bcdbe91656f87681e3789a38 Mon Sep 17 00:00:00 2001
From: Matthew Bastien <matthew_bastien at apple.com>
Date: Fri, 28 Feb 2025 11:22:41 -0500
Subject: [PATCH 02/13] update wording
---
lldb/tools/lldb-dap/package.json | 6 +++---
lldb/tools/lldb-dap/src-ts/debug-adapter-factory.ts | 2 +-
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/lldb/tools/lldb-dap/package.json b/lldb/tools/lldb-dap/package.json
index 05e9540df17cf..cd28b8326a4fb 100644
--- a/lldb/tools/lldb-dap/package.json
+++ b/lldb/tools/lldb-dap/package.json
@@ -83,7 +83,7 @@
"items": {
"type": "string"
},
- "description": "The arguments provided to the lldb-dap process."
+ "description": "The list of additional arguments used to launch the debug adapter executable."
},
"lldb-dap.log-path": {
"scope": "resource",
@@ -173,7 +173,7 @@
"items": {
"type": "string"
},
- "markdownDescription": "The list of arguments used to launch the debug adapter executable."
+ "markdownDescription": "The list of additional arguments used to launch the debug adapter executable."
},
"program": {
"type": "string",
@@ -370,7 +370,7 @@
"items": {
"type": "string"
},
- "markdownDescription": "The list of arguments used to launch the debug adapter executable."
+ "markdownDescription": "The list of additional arguments used to launch the debug adapter executable."
},
"program": {
"type": "string",
diff --git a/lldb/tools/lldb-dap/src-ts/debug-adapter-factory.ts b/lldb/tools/lldb-dap/src-ts/debug-adapter-factory.ts
index 85c34b773e047..e36fde9b61473 100644
--- a/lldb/tools/lldb-dap/src-ts/debug-adapter-factory.ts
+++ b/lldb/tools/lldb-dap/src-ts/debug-adapter-factory.ts
@@ -215,7 +215,7 @@ export class LLDBDapDescriptorFactory
const message =
path !== undefined
? `Debug adapter path: ${path} is not a valid file`
- : "Unable to find the LLDB debug adapter executable.";
+ : "Unable to find the path to the LLDB debug adapter executable.";
const openSettingsAction = "Open Settings";
const callbackValue = await vscode.window.showErrorMessage(
message,
>From c775afd5db829214819f5d359b939e5043429545 Mon Sep 17 00:00:00 2001
From: Matthew Bastien <matthew_bastien at apple.com>
Date: Fri, 7 Mar 2025 17:45:19 -0500
Subject: [PATCH 03/13] prompt the user to restart the server if the executable
or arguments change
---
lldb/tools/lldb-dap/package.json | 16 ++
.../lldb-dap/src-ts/debug-adapter-factory.ts | 208 ++++++++----------
.../src-ts/debug-configuration-provider.ts | 106 +++++++++
lldb/tools/lldb-dap/src-ts/extension.ts | 40 ++--
lldb/tools/lldb-dap/src-ts/lldb-dap-server.ts | 130 +++++++++++
5 files changed, 363 insertions(+), 137 deletions(-)
create mode 100644 lldb/tools/lldb-dap/src-ts/debug-configuration-provider.ts
create mode 100644 lldb/tools/lldb-dap/src-ts/lldb-dap-server.ts
diff --git a/lldb/tools/lldb-dap/package.json b/lldb/tools/lldb-dap/package.json
index cd28b8326a4fb..5290834c3c5ed 100644
--- a/lldb/tools/lldb-dap/package.json
+++ b/lldb/tools/lldb-dap/package.json
@@ -164,6 +164,14 @@
"program"
],
"properties": {
+ "debugAdapterHostname": {
+ "type": "string",
+ "markdownDescription": "The hostname that an existing lldb-dap executable is listening on."
+ },
+ "debugAdapterPort": {
+ "type": "number",
+ "markdownDescription": "The port that an existing lldb-dap executable is listening on."
+ },
"debugAdapterExecutable": {
"type": "string",
"markdownDescription": "The absolute path to the LLDB debug adapter executable to use."
@@ -361,6 +369,14 @@
},
"attach": {
"properties": {
+ "debugAdapterHostname": {
+ "type": "string",
+ "markdownDescription": "The hostname that an existing lldb-dap executable is listening on."
+ },
+ "debugAdapterPort": {
+ "type": "number",
+ "markdownDescription": "The port that an existing lldb-dap executable is listening on."
+ },
"debugAdapterExecutable": {
"type": "string",
"markdownDescription": "The absolute path to the LLDB debug adapter executable to use."
diff --git a/lldb/tools/lldb-dap/src-ts/debug-adapter-factory.ts b/lldb/tools/lldb-dap/src-ts/debug-adapter-factory.ts
index e36fde9b61473..61c4b95efb8a7 100644
--- a/lldb/tools/lldb-dap/src-ts/debug-adapter-factory.ts
+++ b/lldb/tools/lldb-dap/src-ts/debug-adapter-factory.ts
@@ -6,7 +6,7 @@ import * as fs from "node:fs/promises";
const exec = util.promisify(child_process.execFile);
-export async function isExecutable(path: string): Promise<Boolean> {
+async function isExecutable(path: string): Promise<Boolean> {
try {
await fs.access(path, fs.constants.X_OK);
} catch {
@@ -66,19 +66,17 @@ async function findDAPExecutable(): Promise<string | undefined> {
}
async function getDAPExecutable(
- session: vscode.DebugSession,
+ folder: vscode.WorkspaceFolder | undefined,
+ configuration: vscode.DebugConfiguration,
): Promise<string | undefined> {
// Check if the executable was provided in the launch configuration.
- const launchConfigPath = session.configuration["debugAdapterExecutable"];
+ const launchConfigPath = configuration["debugAdapterExecutable"];
if (typeof launchConfigPath === "string" && launchConfigPath.length !== 0) {
return launchConfigPath;
}
// Check if the executable was provided in the extension's configuration.
- const config = vscode.workspace.getConfiguration(
- "lldb-dap",
- session.workspaceFolder,
- );
+ const config = vscode.workspace.getConfiguration("lldb-dap", folder);
const configPath = config.get<string>("executable-path");
if (configPath && configPath.length !== 0) {
return configPath;
@@ -93,9 +91,12 @@ async function getDAPExecutable(
return undefined;
}
-function getDAPArguments(session: vscode.DebugSession): string[] {
+function getDAPArguments(
+ folder: vscode.WorkspaceFolder | undefined,
+ configuration: vscode.DebugConfiguration,
+): string[] {
// Check the debug configuration for arguments first
- const debugConfigArgs = session.configuration.debugAdapterArgs;
+ const debugConfigArgs = configuration.debugAdapterArgs;
if (
Array.isArray(debugConfigArgs) &&
debugConfigArgs.findIndex((entry) => typeof entry !== "string") === -1
@@ -104,129 +105,110 @@ function getDAPArguments(session: vscode.DebugSession): string[] {
}
// Fall back on the workspace configuration
return vscode.workspace
- .getConfiguration("lldb-dap")
+ .getConfiguration("lldb-dap", folder)
.get<string[]>("arguments", []);
}
-async function isServerModeSupported(exe: string): Promise<boolean> {
- const { stdout } = await exec(exe, ["--help"]);
- return /--connection/.test(stdout);
+/**
+ * Shows a modal when the debug adapter's path is not found
+ */
+async function showLLDBDapNotFoundMessage(path?: string) {
+ const message =
+ path !== undefined
+ ? `Debug adapter path: ${path} is not a valid file`
+ : "Unable to find the path to the LLDB debug adapter executable.";
+ const openSettingsAction = "Open Settings";
+ const callbackValue = await vscode.window.showErrorMessage(
+ message,
+ { modal: true },
+ openSettingsAction,
+ );
+
+ if (openSettingsAction === callbackValue) {
+ vscode.commands.executeCommand(
+ "workbench.action.openSettings",
+ "lldb-dap.executable-path",
+ );
+ }
}
/**
- * This class defines a factory used to find the lldb-dap binary to use
- * depending on the session configuration.
+ * Creates a new {@link vscode.DebugAdapterExecutable} based on the provided workspace folder and
+ * debug configuration. Assumes that the given debug configuration is for a local launch of lldb-dap.
+ *
+ * @param folder The {@link vscode.WorkspaceFolder} that the debug session will be launched within
+ * @param configuration The {@link vscode.DebugConfiguration}
+ * @param userInteractive Whether or not this was called due to user interaction (determines if modals should be shown)
+ * @returns
*/
-export class LLDBDapDescriptorFactory
- implements vscode.DebugAdapterDescriptorFactory, vscode.Disposable
-{
- private server?: Promise<{
- process: child_process.ChildProcess;
- host: string;
- port: number;
- }>;
-
- dispose() {
- this.server?.then(({ process }) => {
- process.kill();
- });
+export async function createDebugAdapterExecutable(
+ folder: vscode.WorkspaceFolder | undefined,
+ configuration: vscode.DebugConfiguration,
+ userInteractive?: boolean,
+): Promise<vscode.DebugAdapterExecutable | undefined> {
+ const config = vscode.workspace.getConfiguration("lldb-dap", folder);
+ const log_path = config.get<string>("log-path");
+ let env: { [key: string]: string } = {};
+ if (log_path) {
+ env["LLDBDAP_LOG"] = log_path;
}
+ const configEnvironment =
+ config.get<{ [key: string]: string }>("environment") || {};
+ const dapPath = await getDAPExecutable(folder, configuration);
- async createDebugAdapterDescriptor(
- session: vscode.DebugSession,
- _executable: vscode.DebugAdapterExecutable | undefined,
- ): Promise<vscode.DebugAdapterDescriptor | undefined> {
- const config = vscode.workspace.getConfiguration(
- "lldb-dap",
- session.workspaceFolder,
- );
-
- const log_path = config.get<string>("log-path");
- let env: { [key: string]: string } = {};
- if (log_path) {
- env["LLDBDAP_LOG"] = log_path;
+ if (!dapPath) {
+ if (userInteractive) {
+ showLLDBDapNotFoundMessage();
}
- const configEnvironment =
- config.get<{ [key: string]: string }>("environment") || {};
- const dapPath = await getDAPExecutable(session);
+ return undefined;
+ }
- if (!dapPath) {
- LLDBDapDescriptorFactory.showLLDBDapNotFoundMessage();
- return undefined;
+ if (!(await isExecutable(dapPath))) {
+ if (userInteractive) {
+ showLLDBDapNotFoundMessage(dapPath);
}
+ return undefined;
+ }
- if (!(await isExecutable(dapPath))) {
- LLDBDapDescriptorFactory.showLLDBDapNotFoundMessage(dapPath);
- return;
- }
+ const dbgOptions = {
+ env: {
+ ...configEnvironment,
+ ...env,
+ },
+ };
+ const dbgArgs = getDAPArguments(folder, configuration);
+
+ return new vscode.DebugAdapterExecutable(dapPath, dbgArgs, dbgOptions);
+}
- const dbgOptions = {
- env: {
- ...configEnvironment,
- ...env,
- },
- };
- const dbgArgs = getDAPArguments(session);
-
- const serverMode = config.get<boolean>("serverMode", false);
- if (serverMode && (await isServerModeSupported(dapPath))) {
- const { host, port } = await this.startServer(
- dapPath,
- dbgArgs,
- dbgOptions,
+/**
+ * This class defines a factory used to find the lldb-dap binary to use
+ * depending on the session configuration.
+ */
+export class LLDBDapDescriptorFactory
+ implements vscode.DebugAdapterDescriptorFactory
+{
+ async createDebugAdapterDescriptor(
+ session: vscode.DebugSession,
+ executable: vscode.DebugAdapterExecutable | undefined,
+ ): Promise<vscode.DebugAdapterDescriptor | undefined> {
+ if (executable) {
+ throw new Error(
+ "Setting the debug adapter executable in the package.json is not supported.",
);
- return new vscode.DebugAdapterServer(port, host);
}
- return new vscode.DebugAdapterExecutable(dapPath, dbgArgs, dbgOptions);
- }
-
- startServer(
- dapPath: string,
- args: string[],
- options: child_process.CommonSpawnOptions,
- ): Promise<{ host: string; port: number }> {
- if (this.server) {
- return this.server;
+ // Use a server connection if the debugAdapterPort is provided
+ if (session.configuration.debugAdapterPort) {
+ return new vscode.DebugAdapterServer(
+ session.configuration.debugAdapterPort,
+ session.configuration.debugAdapterHost,
+ );
}
- this.server = new Promise((resolve) => {
- args.push("--connection", "connect://localhost:0");
- const server = child_process.spawn(dapPath, args, options);
- server.stdout!.setEncoding("utf8").once("data", (data: string) => {
- const connection = /connection:\/\/\[([^\]]+)\]:(\d+)/.exec(data);
- if (connection) {
- const host = connection[1];
- const port = Number(connection[2]);
- resolve({ process: server, host, port });
- }
- });
- server.on("exit", () => {
- this.server = undefined;
- });
- });
- return this.server;
- }
-
- /**
- * Shows a message box when the debug adapter's path is not found
- */
- static async showLLDBDapNotFoundMessage(path?: string | undefined) {
- const message =
- path !== undefined
- ? `Debug adapter path: ${path} is not a valid file`
- : "Unable to find the path to the LLDB debug adapter executable.";
- const openSettingsAction = "Open Settings";
- const callbackValue = await vscode.window.showErrorMessage(
- message,
- openSettingsAction,
+ return createDebugAdapterExecutable(
+ session.workspaceFolder,
+ session.configuration,
);
-
- if (openSettingsAction === callbackValue) {
- vscode.commands.executeCommand(
- "workbench.action.openSettings",
- "lldb-dap.executable-path",
- );
- }
}
}
diff --git a/lldb/tools/lldb-dap/src-ts/debug-configuration-provider.ts b/lldb/tools/lldb-dap/src-ts/debug-configuration-provider.ts
new file mode 100644
index 0000000000000..aa8c1af6d1e00
--- /dev/null
+++ b/lldb/tools/lldb-dap/src-ts/debug-configuration-provider.ts
@@ -0,0 +1,106 @@
+import * as vscode from "vscode";
+import * as child_process from "child_process";
+import * as util from "util";
+import { LLDBDapServer } from "./lldb-dap-server";
+import { createDebugAdapterExecutable } from "./debug-adapter-factory";
+
+const exec = util.promisify(child_process.execFile);
+
+/**
+ * Shows an error message to the user that optionally allows them to open their
+ * launch.json to configure it.
+ *
+ * @param message The error message to display to the user
+ * @returns `undefined` if the debug session should stop or `null` if the launch.json should be opened
+ */
+async function showErrorWithConfigureButton(
+ message: string,
+): Promise<null | undefined> {
+ const userSelection = await vscode.window.showErrorMessage(
+ message,
+ { modal: true },
+ "Configure",
+ );
+
+ if (userSelection === "Configure") {
+ return null; // Stops the debug session and opens the launch.json for editing
+ }
+
+ return undefined; // Only stops the debug session
+}
+
+/**
+ * Determines whether or not the given lldb-dap executable supports executing
+ * in server mode.
+ *
+ * @param exe the path to the lldb-dap executable
+ * @returns a boolean indicating whether or not lldb-dap supports server mode
+ */
+async function isServerModeSupported(exe: string): Promise<boolean> {
+ const { stdout } = await exec(exe, ["--help"]);
+ return /--connection/.test(stdout);
+}
+
+export class LLDBDapConfigurationProvider
+ implements vscode.DebugConfigurationProvider
+{
+ constructor(private readonly server: LLDBDapServer) {}
+
+ async resolveDebugConfiguration(
+ folder: vscode.WorkspaceFolder | undefined,
+ debugConfiguration: vscode.DebugConfiguration,
+ _token?: vscode.CancellationToken,
+ ): Promise<vscode.DebugConfiguration | null | undefined> {
+ if (
+ "debugAdapterHost" in debugConfiguration &&
+ !("debugAdapterPort" in debugConfiguration)
+ ) {
+ return showErrorWithConfigureButton(
+ "A debugAdapterPort must be provided when debugAdapterHost is set. Please update your launch configuration.",
+ );
+ }
+
+ if (
+ "debugAdapterPort" in debugConfiguration &&
+ ("debugAdapterExecutable" in debugConfiguration ||
+ "debugAdapterArgs" in debugConfiguration)
+ ) {
+ return showErrorWithConfigureButton(
+ "The debugAdapterPort property is incompatible with debugAdapterExecutable and debugAdapterArgs. Please update your launch configuration.",
+ );
+ }
+
+ // Server mode needs to be handled here since DebugAdapterDescriptorFactory
+ // will show an unhelpful error if it returns undefined. We'd rather show a
+ // nicer error message here and allow stopping the debug session gracefully.
+ const config = vscode.workspace.getConfiguration("lldb-dap", folder);
+ if (config.get<boolean>("serverMode", false)) {
+ const executable = await createDebugAdapterExecutable(
+ folder,
+ debugConfiguration,
+ /* userInteractive */ true,
+ );
+ if (!executable) {
+ return undefined;
+ }
+ if (await isServerModeSupported(executable.command)) {
+ const serverInfo = await this.server.start(
+ executable.command,
+ executable.args,
+ executable.options,
+ );
+ if (!serverInfo) {
+ return undefined;
+ }
+ // Use a debug adapter host and port combination rather than an executable
+ // and list of arguments.
+ delete debugConfiguration.debugAdapterExecutable;
+ delete debugConfiguration.debugAdapterArgs;
+ debugConfiguration.debugAdapterHost = serverInfo.host;
+ debugConfiguration.debugAdapterPort = serverInfo.port;
+ }
+ }
+
+ return debugConfiguration;
+ }
+}
diff --git a/lldb/tools/lldb-dap/src-ts/extension.ts b/lldb/tools/lldb-dap/src-ts/extension.ts
index f0c7fb5bd1a71..0b014f953d5ba 100644
--- a/lldb/tools/lldb-dap/src-ts/extension.ts
+++ b/lldb/tools/lldb-dap/src-ts/extension.ts
@@ -1,11 +1,10 @@
import * as vscode from "vscode";
-import {
- LLDBDapDescriptorFactory,
- isExecutable,
-} from "./debug-adapter-factory";
+import { LLDBDapDescriptorFactory } from "./debug-adapter-factory";
import { DisposableContext } from "./disposable-context";
import { LaunchUriHandler } from "./uri-launch-handler";
+import { LLDBDapConfigurationProvider } from "./debug-configuration-provider";
+import { LLDBDapServer } from "./lldb-dap-server";
/**
* This class represents the extension and manages its life cycle. Other extensions
@@ -14,33 +13,26 @@ import { LaunchUriHandler } from "./uri-launch-handler";
export class LLDBDapExtension extends DisposableContext {
constructor() {
super();
- const factory = new LLDBDapDescriptorFactory();
- this.pushSubscription(factory);
+
+ const lldbDapServer = new LLDBDapServer();
+ this.pushSubscription(lldbDapServer);
+
this.pushSubscription(
- vscode.debug.registerDebugAdapterDescriptorFactory(
+ vscode.debug.registerDebugConfigurationProvider(
"lldb-dap",
- factory,
- )
+ new LLDBDapConfigurationProvider(lldbDapServer),
+ ),
);
- this.pushSubscription(
- vscode.workspace.onDidChangeConfiguration(async (event) => {
- if (event.affectsConfiguration("lldb-dap.executable-path")) {
- const dapPath = vscode.workspace
- .getConfiguration("lldb-dap")
- .get<string>("executable-path");
- if (dapPath) {
- if (await isExecutable(dapPath)) {
- return;
- }
- }
- LLDBDapDescriptorFactory.showLLDBDapNotFoundMessage(dapPath || "");
- }
- }),
+ this.pushSubscription(
+ vscode.debug.registerDebugAdapterDescriptorFactory(
+ "lldb-dap",
+ new LLDBDapDescriptorFactory(),
+ ),
);
this.pushSubscription(
- vscode.window.registerUriHandler(new LaunchUriHandler())
+ vscode.window.registerUriHandler(new LaunchUriHandler()),
);
}
}
diff --git a/lldb/tools/lldb-dap/src-ts/lldb-dap-server.ts b/lldb/tools/lldb-dap/src-ts/lldb-dap-server.ts
new file mode 100644
index 0000000000000..2241a8676e46f
--- /dev/null
+++ b/lldb/tools/lldb-dap/src-ts/lldb-dap-server.ts
@@ -0,0 +1,130 @@
+import * as child_process from "node:child_process";
+import * as vscode from "vscode";
+
+function areArraysEqual<T>(lhs: T[], rhs: T[]): boolean {
+ if (lhs.length !== rhs.length) {
+ return false;
+ }
+ for (let i = 0; i < lhs.length; i++) {
+ if (lhs[i] !== rhs[i]) {
+ return false;
+ }
+ }
+ return true;
+}
+
+/**
+ * Represents a running lldb-dap process that is accepting connections (i.e. in "server mode").
+ *
+ * Handles startup of the process if it isn't running already as well as prompting the user
+ * to restart when arguments have changed.
+ */
+export class LLDBDapServer implements vscode.Disposable {
+ private serverProcess?: child_process.ChildProcessWithoutNullStreams;
+ private serverInfo?: Promise<{ host: string; port: number }>;
+
+ /**
+ * Starts the server with the provided options. The server will be restarted or reused as
+ * necessary.
+ *
+ * @param dapPath the path to the debug adapter executable
+ * @param args the list of arguments to provide to the debug adapter
+ * @param options the options to provide to the debug adapter process
+ * @returns a promise that resolves with the host and port information or `undefined` if unable to launch the server.
+ */
+ async start(
+ dapPath: string,
+ args: string[],
+ options?: child_process.SpawnOptionsWithoutStdio,
+ ): Promise<{ host: string; port: number } | undefined> {
+ const dapArgs = [...args, "--connection", "connect://localhost:0"];
+ if (!(await this.shouldContinueStartup(dapPath, dapArgs))) {
+ return undefined;
+ }
+
+ if (this.serverInfo) {
+ return this.serverInfo;
+ }
+
+ this.serverInfo = new Promise((resolve, reject) => {
+ const process = child_process.spawn(dapPath, dapArgs, options);
+ process.on("error", (error) => {
+ reject(error);
+ this.serverProcess = undefined;
+ this.serverInfo = undefined;
+ });
+ process.on("exit", (code, signal) => {
+ let errorMessage = "Server process exited early";
+ if (code !== undefined) {
+ errorMessage += ` with code ${code}`;
+ } else if (signal !== undefined) {
+ errorMessage += ` due to signal ${signal}`;
+ }
+ reject(new Error(errorMessage));
+ this.serverProcess = undefined;
+ this.serverInfo = undefined;
+ });
+ process.stdout.setEncoding("utf8").on("data", (data) => {
+ const connection = /connection:\/\/\[([^\]]+)\]:(\d+)/.exec(
+ data.toString(),
+ );
+ if (connection) {
+ const host = connection[1];
+ const port = Number(connection[2]);
+ resolve({ host, port });
+ process.stdout.removeAllListeners();
+ }
+ });
+ this.serverProcess = process;
+ });
+ return this.serverInfo;
+ }
+
+ /**
+ * Checks to see if the server needs to be restarted. If so, it will prompt the user
+ * to ask if they wish to restart.
+ *
+ * @param dapPath the path to the debug adapter
+ * @param args the arguments for the debug adapter
+ * @returns whether or not startup should continue depending on user input
+ */
+ private async shouldContinueStartup(
+ dapPath: string,
+ args: string[],
+ ): Promise<boolean> {
+ if (!this.serverProcess || !this.serverInfo) {
+ return true;
+ }
+
+ if (areArraysEqual(this.serverProcess.spawnargs, [dapPath, ...args])) {
+ return true;
+ }
+
+ const userInput = await vscode.window.showInformationMessage(
+ "A server mode instance of lldb-dap is already running, but the arguments are different from what is requested in your debug configuration or settings. Would you like to restart the server?",
+ { modal: true },
+ "Restart",
+ "Use Existing",
+ );
+ switch (userInput) {
+ case "Restart":
+ this.serverProcess.kill();
+ this.serverProcess = undefined;
+ this.serverInfo = undefined;
+ return true;
+ case "Use Existing":
+ return true;
+ case undefined:
+ return false;
+ }
+ }
+
+ dispose() {
+ if (!this.serverProcess) {
+ return;
+ }
+ this.serverProcess.kill();
+ this.serverProcess = undefined;
+ this.serverInfo = undefined;
+ }
+}
>From 4ec5f842f10d783c46ebcc4e1838d520700e3e6a Mon Sep 17 00:00:00 2001
From: Matthew Bastien <matthew_bastien at apple.com>
Date: Fri, 7 Mar 2025 18:00:03 -0500
Subject: [PATCH 04/13] add more checks and error messages
---
.../lldb-dap/src-ts/debug-adapter-factory.ts | 51 +++++++++----------
.../src-ts/debug-configuration-provider.ts | 24 +--------
.../lldb-dap/src-ts/ui/error-messages.ts | 49 ++++++++++++++++++
3 files changed, 75 insertions(+), 49 deletions(-)
create mode 100644 lldb/tools/lldb-dap/src-ts/ui/error-messages.ts
diff --git a/lldb/tools/lldb-dap/src-ts/debug-adapter-factory.ts b/lldb/tools/lldb-dap/src-ts/debug-adapter-factory.ts
index 61c4b95efb8a7..11a1cb776b0a3 100644
--- a/lldb/tools/lldb-dap/src-ts/debug-adapter-factory.ts
+++ b/lldb/tools/lldb-dap/src-ts/debug-adapter-factory.ts
@@ -3,6 +3,10 @@ import * as util from "util";
import * as vscode from "vscode";
import * as child_process from "child_process";
import * as fs from "node:fs/promises";
+import {
+ showErrorWithConfigureButton,
+ showLLDBDapNotFoundMessage,
+} from "./ui/error-messages";
const exec = util.promisify(child_process.execFile);
@@ -91,12 +95,27 @@ async function getDAPExecutable(
return undefined;
}
-function getDAPArguments(
+async function getDAPArguments(
folder: vscode.WorkspaceFolder | undefined,
configuration: vscode.DebugConfiguration,
-): string[] {
+ userInteractive?: boolean,
+): Promise<string[] | null | undefined> {
// Check the debug configuration for arguments first
const debugConfigArgs = configuration.debugAdapterArgs;
+ if (debugConfigArgs) {
+ if (
+ !Array.isArray(debugConfigArgs) ||
+ debugConfigArgs.findIndex((entry) => typeof entry !== "string") !== -1
+ ) {
+ if (!userInteractive) {
+ return undefined;
+ }
+ return showErrorWithConfigureButton(
+ "The debugAdapterArgs property must be an array of string values.",
+ );
+ }
+ return debugConfigArgs;
+ }
if (
Array.isArray(debugConfigArgs) &&
debugConfigArgs.findIndex((entry) => typeof entry !== "string") === -1
@@ -109,29 +128,6 @@ function getDAPArguments(
.get<string[]>("arguments", []);
}
-/**
- * Shows a modal when the debug adapter's path is not found
- */
-async function showLLDBDapNotFoundMessage(path?: string) {
- const message =
- path !== undefined
- ? `Debug adapter path: ${path} is not a valid file`
- : "Unable to find the path to the LLDB debug adapter executable.";
- const openSettingsAction = "Open Settings";
- const callbackValue = await vscode.window.showErrorMessage(
- message,
- { modal: true },
- openSettingsAction,
- );
-
- if (openSettingsAction === callbackValue) {
- vscode.commands.executeCommand(
- "workbench.action.openSettings",
- "lldb-dap.executable-path",
- );
- }
-}
-
/**
* Creates a new {@link vscode.DebugAdapterExecutable} based on the provided workspace folder and
* debug configuration. Assumes that the given debug configuration is for a local launch of lldb-dap.
@@ -176,7 +172,10 @@ export async function createDebugAdapterExecutable(
...env,
},
};
- const dbgArgs = getDAPArguments(folder, configuration);
+ const dbgArgs = await getDAPArguments(folder, configuration, userInteractive);
+ if (!dbgArgs) {
+ return undefined;
+ }
return new vscode.DebugAdapterExecutable(dapPath, dbgArgs, dbgOptions);
}
diff --git a/lldb/tools/lldb-dap/src-ts/debug-configuration-provider.ts b/lldb/tools/lldb-dap/src-ts/debug-configuration-provider.ts
index aa8c1af6d1e00..b2eef56726f22 100644
--- a/lldb/tools/lldb-dap/src-ts/debug-configuration-provider.ts
+++ b/lldb/tools/lldb-dap/src-ts/debug-configuration-provider.ts
@@ -3,32 +3,10 @@ import * as child_process from "child_process";
import * as util from "util";
import { LLDBDapServer } from "./lldb-dap-server";
import { createDebugAdapterExecutable } from "./debug-adapter-factory";
+import { showErrorWithConfigureButton } from "./ui/error-messages";
const exec = util.promisify(child_process.execFile);
-/**
- * Shows an error message to the user that optionally allows them to open their
- * launch.json to configure it.
- *
- * @param message The error message to display to the user
- * @returns `undefined` if the debug session should stop or `null` if the launch.json should be opened
- */
-async function showErrorWithConfigureButton(
- message: string,
-): Promise<null | undefined> {
- const userSelection = await vscode.window.showErrorMessage(
- message,
- { modal: true },
- "Configure",
- );
-
- if (userSelection === "Configure") {
- return null; // Stops the debug session and opens the launch.json for editing
- }
-
- return undefined; // Only stops the debug session
-}
-
/**
* Determines whether or not the given lldb-dap executable supports executing
* in server mode.
diff --git a/lldb/tools/lldb-dap/src-ts/ui/error-messages.ts b/lldb/tools/lldb-dap/src-ts/ui/error-messages.ts
new file mode 100644
index 0000000000000..0127ca5e288cc
--- /dev/null
+++ b/lldb/tools/lldb-dap/src-ts/ui/error-messages.ts
@@ -0,0 +1,49 @@
+import * as vscode from "vscode";
+
+/**
+ * Shows a modal when the debug adapter's path is not found
+ */
+export async function showLLDBDapNotFoundMessage(path?: string) {
+ const message =
+ path !== undefined
+ ? `Debug adapter path: ${path} is not a valid file`
+ : "Unable to find the path to the LLDB debug adapter executable.";
+ const openSettingsAction = "Open Settings";
+ const callbackValue = await vscode.window.showErrorMessage(
+ message,
+ { modal: true },
+ openSettingsAction,
+ );
+
+ if (openSettingsAction === callbackValue) {
+ vscode.commands.executeCommand(
+ "workbench.action.openSettings",
+ "lldb-dap.executable-path",
+ );
+ }
+}
+
+/**
+ * Shows an error message to the user that optionally allows them to open their
+ * launch.json to configure it.
+ *
+ * Expected to be used in the context of a {@link vscode.DebugConfigurationProvider}.
+ *
+ * @param message The error message to display to the user
+ * @returns `undefined` if the debug session should stop or `null` if the launch.json should be opened
+ */
+export async function showErrorWithConfigureButton(
+ message: string,
+): Promise<null | undefined> {
+ const userSelection = await vscode.window.showErrorMessage(
+ message,
+ { modal: true },
+ "Configure",
+ );
+
+ if (userSelection === "Configure") {
+ return null; // Stops the debug session and opens the launch.json for editing
+ }
+
+ return undefined; // Only stops the debug session
+}
>From bf27cb2fe61a2aa3c15fc652f4b65353399d5cf0 Mon Sep 17 00:00:00 2001
From: Matthew Bastien <matthew_bastien at apple.com>
Date: Fri, 7 Mar 2025 18:09:15 -0500
Subject: [PATCH 05/13] mention that debug configuration properties override VS
Code settings
---
lldb/tools/lldb-dap/package.json | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/lldb/tools/lldb-dap/package.json b/lldb/tools/lldb-dap/package.json
index 5290834c3c5ed..c3da458d8a0f4 100644
--- a/lldb/tools/lldb-dap/package.json
+++ b/lldb/tools/lldb-dap/package.json
@@ -174,14 +174,14 @@
},
"debugAdapterExecutable": {
"type": "string",
- "markdownDescription": "The absolute path to the LLDB debug adapter executable to use."
+ "markdownDescription": "The absolute path to the LLDB debug adapter executable to use. Overrides any user or workspace settings."
},
"debugAdapterArgs": {
"type": "array",
"items": {
"type": "string"
},
- "markdownDescription": "The list of additional arguments used to launch the debug adapter executable."
+ "markdownDescription": "The list of additional arguments used to launch the debug adapter executable. Overrides any user or workspace settings."
},
"program": {
"type": "string",
@@ -379,14 +379,14 @@
},
"debugAdapterExecutable": {
"type": "string",
- "markdownDescription": "The absolute path to the LLDB debug adapter executable to use."
+ "markdownDescription": "The absolute path to the LLDB debug adapter executable to use. Overrides any user or workspace settings."
},
"debugAdapterArgs": {
"type": "array",
"items": {
"type": "string"
},
- "markdownDescription": "The list of additional arguments used to launch the debug adapter executable."
+ "markdownDescription": "The list of additional arguments used to launch the debug adapter executable. Overrides any user or workspace settings."
},
"program": {
"type": "string",
>From 6e5005cc68f042964f7086d4b23fdeb9fce0fc37 Mon Sep 17 00:00:00 2001
From: Matthew Bastien <matthew_bastien at apple.com>
Date: Fri, 7 Mar 2025 18:32:38 -0500
Subject: [PATCH 06/13] tell the user if lldb-dap doesn't exist when a debug
session is started
---
.../src-ts/debug-configuration-provider.ts | 38 +++++++++++--------
1 file changed, 22 insertions(+), 16 deletions(-)
diff --git a/lldb/tools/lldb-dap/src-ts/debug-configuration-provider.ts b/lldb/tools/lldb-dap/src-ts/debug-configuration-provider.ts
index b2eef56726f22..3f76610f09752 100644
--- a/lldb/tools/lldb-dap/src-ts/debug-configuration-provider.ts
+++ b/lldb/tools/lldb-dap/src-ts/debug-configuration-provider.ts
@@ -38,21 +38,19 @@ export class LLDBDapConfigurationProvider
);
}
- if (
- "debugAdapterPort" in debugConfiguration &&
- ("debugAdapterExecutable" in debugConfiguration ||
- "debugAdapterArgs" in debugConfiguration)
- ) {
- return showErrorWithConfigureButton(
- "The debugAdapterPort property is incompatible with debugAdapterExecutable and debugAdapterArgs. Please update your launch configuration.",
- );
- }
-
- // Server mode needs to be handled here since DebugAdapterDescriptorFactory
- // will show an unhelpful error if it returns undefined. We'd rather show a
- // nicer error message here and allow stopping the debug session gracefully.
- const config = vscode.workspace.getConfiguration("lldb-dap", folder);
- if (config.get<boolean>("serverMode", false)) {
+ // Check if we're going to launch a debug session or use an existing process
+ if ("debugAdapterPort" in debugConfiguration) {
+ if (
+ "debugAdapterExecutable" in debugConfiguration ||
+ "debugAdapterArgs" in debugConfiguration
+ ) {
+ return showErrorWithConfigureButton(
+ "The debugAdapterPort property is incompatible with debugAdapterExecutable and debugAdapterArgs. Please update your launch configuration.",
+ );
+ }
+ } else {
+ // Always try to create the debug adapter executable as this will show the user errors
+ // if there are any.
const executable = await createDebugAdapterExecutable(
folder,
debugConfiguration,
@@ -61,7 +59,15 @@ export class LLDBDapConfigurationProvider
if (!executable) {
return undefined;
}
- if (await isServerModeSupported(executable.command)) {
+
+ // Server mode needs to be handled here since DebugAdapterDescriptorFactory
+ // will show an unhelpful error if it returns undefined. We'd rather show a
+ // nicer error message here and allow stopping the debug session gracefully.
+ const config = vscode.workspace.getConfiguration("lldb-dap", folder);
+ if (
+ config.get<boolean>("serverMode", false) &&
+ (await isServerModeSupported(executable.command))
+ ) {
const serverInfo = await this.server.start(
executable.command,
executable.args,
>From 157417c6113e72ff4af9db18af7174dc3e754c43 Mon Sep 17 00:00:00 2001
From: Matthew Bastien <matthew_bastien at apple.com>
Date: Tue, 11 Mar 2025 13:14:38 -0400
Subject: [PATCH 07/13] change `folder` arguments to `workspaceFolder`
---
.../lldb-dap/src-ts/debug-adapter-factory.ts | 22 +++++++++++--------
1 file changed, 13 insertions(+), 9 deletions(-)
diff --git a/lldb/tools/lldb-dap/src-ts/debug-adapter-factory.ts b/lldb/tools/lldb-dap/src-ts/debug-adapter-factory.ts
index 11a1cb776b0a3..8cc78fe93c83d 100644
--- a/lldb/tools/lldb-dap/src-ts/debug-adapter-factory.ts
+++ b/lldb/tools/lldb-dap/src-ts/debug-adapter-factory.ts
@@ -70,7 +70,7 @@ async function findDAPExecutable(): Promise<string | undefined> {
}
async function getDAPExecutable(
- folder: vscode.WorkspaceFolder | undefined,
+ workspaceFolder: vscode.WorkspaceFolder | undefined,
configuration: vscode.DebugConfiguration,
): Promise<string | undefined> {
// Check if the executable was provided in the launch configuration.
@@ -80,7 +80,7 @@ async function getDAPExecutable(
}
// Check if the executable was provided in the extension's configuration.
- const config = vscode.workspace.getConfiguration("lldb-dap", folder);
+ const config = vscode.workspace.getConfiguration("lldb-dap", workspaceFolder);
const configPath = config.get<string>("executable-path");
if (configPath && configPath.length !== 0) {
return configPath;
@@ -96,7 +96,7 @@ async function getDAPExecutable(
}
async function getDAPArguments(
- folder: vscode.WorkspaceFolder | undefined,
+ workspaceFolder: vscode.WorkspaceFolder | undefined,
configuration: vscode.DebugConfiguration,
userInteractive?: boolean,
): Promise<string[] | null | undefined> {
@@ -124,7 +124,7 @@ async function getDAPArguments(
}
// Fall back on the workspace configuration
return vscode.workspace
- .getConfiguration("lldb-dap", folder)
+ .getConfiguration("lldb-dap", workspaceFolder)
.get<string[]>("arguments", []);
}
@@ -132,17 +132,17 @@ async function getDAPArguments(
* Creates a new {@link vscode.DebugAdapterExecutable} based on the provided workspace folder and
* debug configuration. Assumes that the given debug configuration is for a local launch of lldb-dap.
*
- * @param folder The {@link vscode.WorkspaceFolder} that the debug session will be launched within
+ * @param workspaceFolder The {@link vscode.WorkspaceFolder} that the debug session will be launched within
* @param configuration The {@link vscode.DebugConfiguration}
* @param userInteractive Whether or not this was called due to user interaction (determines if modals should be shown)
* @returns
*/
export async function createDebugAdapterExecutable(
- folder: vscode.WorkspaceFolder | undefined,
+ workspaceFolder: vscode.WorkspaceFolder | undefined,
configuration: vscode.DebugConfiguration,
userInteractive?: boolean,
): Promise<vscode.DebugAdapterExecutable | undefined> {
- const config = vscode.workspace.getConfiguration("lldb-dap", folder);
+ const config = vscode.workspace.getConfiguration("lldb-dap", workspaceFolder);
const log_path = config.get<string>("log-path");
let env: { [key: string]: string } = {};
if (log_path) {
@@ -150,7 +150,7 @@ export async function createDebugAdapterExecutable(
}
const configEnvironment =
config.get<{ [key: string]: string }>("environment") || {};
- const dapPath = await getDAPExecutable(folder, configuration);
+ const dapPath = await getDAPExecutable(workspaceFolder, configuration);
if (!dapPath) {
if (userInteractive) {
@@ -172,7 +172,11 @@ export async function createDebugAdapterExecutable(
...env,
},
};
- const dbgArgs = await getDAPArguments(folder, configuration, userInteractive);
+ const dbgArgs = await getDAPArguments(
+ workspaceFolder,
+ configuration,
+ userInteractive,
+ );
if (!dbgArgs) {
return undefined;
}
>From f0b9838e936e77b3ed8860a28a10770b925ea4f6 Mon Sep 17 00:00:00 2001
From: Matthew Bastien <matthew_bastien at apple.com>
Date: Thu, 13 Mar 2025 16:55:28 -0400
Subject: [PATCH 08/13] show more detailed error messages depending on what
went wrong
---
.../lldb-dap/src-ts/debug-adapter-factory.ts | 84 +++++++-------
.../src-ts/debug-configuration-provider.ts | 109 ++++++++++--------
.../lldb-dap/src-ts/ui/error-messages.ts | 49 --------
.../src-ts/ui/error-with-notification.ts | 68 +++++++++++
.../lldb-dap/src-ts/ui/show-error-message.ts | 98 ++++++++++++++++
5 files changed, 269 insertions(+), 139 deletions(-)
delete mode 100644 lldb/tools/lldb-dap/src-ts/ui/error-messages.ts
create mode 100644 lldb/tools/lldb-dap/src-ts/ui/error-with-notification.ts
create mode 100644 lldb/tools/lldb-dap/src-ts/ui/show-error-message.ts
diff --git a/lldb/tools/lldb-dap/src-ts/debug-adapter-factory.ts b/lldb/tools/lldb-dap/src-ts/debug-adapter-factory.ts
index 8cc78fe93c83d..79fe6396416eb 100644
--- a/lldb/tools/lldb-dap/src-ts/debug-adapter-factory.ts
+++ b/lldb/tools/lldb-dap/src-ts/debug-adapter-factory.ts
@@ -3,10 +3,8 @@ import * as util from "util";
import * as vscode from "vscode";
import * as child_process from "child_process";
import * as fs from "node:fs/promises";
-import {
- showErrorWithConfigureButton,
- showLLDBDapNotFoundMessage,
-} from "./ui/error-messages";
+import { ConfigureButton, OpenSettingsButton } from "./ui/show-error-message";
+import { ErrorWithNotification } from "./ui/error-with-notification";
const exec = util.promisify(child_process.execFile);
@@ -69,10 +67,19 @@ async function findDAPExecutable(): Promise<string | undefined> {
return undefined;
}
+/**
+ * Retrieves the lldb-dap executable path either from settings or the provided
+ * {@link vscode.DebugConfiguration}.
+ *
+ * @param workspaceFolder The {@link vscode.WorkspaceFolder} that the debug session will be launched within
+ * @param configuration The {@link vscode.DebugConfiguration} that will be launched
+ * @throws An {@link ErrorWithNotification} if something went wrong
+ * @returns The path to the lldb-dap executable
+ */
async function getDAPExecutable(
workspaceFolder: vscode.WorkspaceFolder | undefined,
configuration: vscode.DebugConfiguration,
-): Promise<string | undefined> {
+): Promise<string> {
// Check if the executable was provided in the launch configuration.
const launchConfigPath = configuration["debugAdapterExecutable"];
if (typeof launchConfigPath === "string" && launchConfigPath.length !== 0) {
@@ -92,14 +99,25 @@ async function getDAPExecutable(
return foundPath;
}
- return undefined;
+ throw new ErrorWithNotification(
+ "Unable to find the path to the LLDB debug adapter executable.",
+ new OpenSettingsButton("lldb-dap.executable-path"),
+ );
}
+/**
+ * Retrieves the arguments that will be provided to lldb-dap either from settings or the provided
+ * {@link vscode.DebugConfiguration}.
+ *
+ * @param workspaceFolder The {@link vscode.WorkspaceFolder} that the debug session will be launched within
+ * @param configuration The {@link vscode.DebugConfiguration} that will be launched
+ * @throws An {@link ErrorWithNotification} if something went wrong
+ * @returns The arguments that will be provided to lldb-dap
+ */
async function getDAPArguments(
workspaceFolder: vscode.WorkspaceFolder | undefined,
configuration: vscode.DebugConfiguration,
- userInteractive?: boolean,
-): Promise<string[] | null | undefined> {
+): Promise<string[]> {
// Check the debug configuration for arguments first
const debugConfigArgs = configuration.debugAdapterArgs;
if (debugConfigArgs) {
@@ -107,21 +125,13 @@ async function getDAPArguments(
!Array.isArray(debugConfigArgs) ||
debugConfigArgs.findIndex((entry) => typeof entry !== "string") !== -1
) {
- if (!userInteractive) {
- return undefined;
- }
- return showErrorWithConfigureButton(
- "The debugAdapterArgs property must be an array of string values.",
+ throw new ErrorWithNotification(
+ "The debugAdapterArgs property must be an array of string values. Please update your launch configuration",
+ new ConfigureButton(),
);
}
return debugConfigArgs;
}
- if (
- Array.isArray(debugConfigArgs) &&
- debugConfigArgs.findIndex((entry) => typeof entry !== "string") === -1
- ) {
- return debugConfigArgs;
- }
// Fall back on the workspace configuration
return vscode.workspace
.getConfiguration("lldb-dap", workspaceFolder)
@@ -133,15 +143,14 @@ async function getDAPArguments(
* debug configuration. Assumes that the given debug configuration is for a local launch of lldb-dap.
*
* @param workspaceFolder The {@link vscode.WorkspaceFolder} that the debug session will be launched within
- * @param configuration The {@link vscode.DebugConfiguration}
- * @param userInteractive Whether or not this was called due to user interaction (determines if modals should be shown)
- * @returns
+ * @param configuration The {@link vscode.DebugConfiguration} that will be launched
+ * @throws An {@link ErrorWithNotification} if something went wrong
+ * @returns The {@link vscode.DebugAdapterExecutable} that can be used to launch lldb-dap
*/
export async function createDebugAdapterExecutable(
workspaceFolder: vscode.WorkspaceFolder | undefined,
configuration: vscode.DebugConfiguration,
- userInteractive?: boolean,
-): Promise<vscode.DebugAdapterExecutable | undefined> {
+): Promise<vscode.DebugAdapterExecutable> {
const config = vscode.workspace.getConfiguration("lldb-dap", workspaceFolder);
const log_path = config.get<string>("log-path");
let env: { [key: string]: string } = {};
@@ -152,18 +161,16 @@ export async function createDebugAdapterExecutable(
config.get<{ [key: string]: string }>("environment") || {};
const dapPath = await getDAPExecutable(workspaceFolder, configuration);
- if (!dapPath) {
- if (userInteractive) {
- showLLDBDapNotFoundMessage();
- }
- return undefined;
- }
-
if (!(await isExecutable(dapPath))) {
- if (userInteractive) {
- showLLDBDapNotFoundMessage(dapPath);
+ let message = `Debug adapter path "${dapPath}" is not a valid file.`;
+ let buttons: (OpenSettingsButton | ConfigureButton)[] = [
+ new OpenSettingsButton("lldb-dap.executable-path"),
+ ];
+ if ("debugAdapterPath" in configuration) {
+ message += " The path comes from your launch configuration.";
+ buttons = [new ConfigureButton()];
}
- return undefined;
+ throw new ErrorWithNotification(message, ...buttons);
}
const dbgOptions = {
@@ -172,14 +179,7 @@ export async function createDebugAdapterExecutable(
...env,
},
};
- const dbgArgs = await getDAPArguments(
- workspaceFolder,
- configuration,
- userInteractive,
- );
- if (!dbgArgs) {
- return undefined;
- }
+ const dbgArgs = await getDAPArguments(workspaceFolder, configuration);
return new vscode.DebugAdapterExecutable(dapPath, dbgArgs, dbgOptions);
}
diff --git a/lldb/tools/lldb-dap/src-ts/debug-configuration-provider.ts b/lldb/tools/lldb-dap/src-ts/debug-configuration-provider.ts
index 3f76610f09752..46c9ea77c0d22 100644
--- a/lldb/tools/lldb-dap/src-ts/debug-configuration-provider.ts
+++ b/lldb/tools/lldb-dap/src-ts/debug-configuration-provider.ts
@@ -3,7 +3,8 @@ import * as child_process from "child_process";
import * as util from "util";
import { LLDBDapServer } from "./lldb-dap-server";
import { createDebugAdapterExecutable } from "./debug-adapter-factory";
-import { showErrorWithConfigureButton } from "./ui/error-messages";
+import { ConfigureButton, showErrorMessage } from "./ui/show-error-message";
+import { ErrorWithNotification } from "./ui/error-with-notification";
const exec = util.promisify(child_process.execFile);
@@ -29,62 +30,74 @@ export class LLDBDapConfigurationProvider
debugConfiguration: vscode.DebugConfiguration,
_token?: vscode.CancellationToken,
): Promise<vscode.DebugConfiguration | null | undefined> {
- if (
- "debugAdapterHost" in debugConfiguration &&
- !("debugAdapterPort" in debugConfiguration)
- ) {
- return showErrorWithConfigureButton(
- "A debugAdapterPort must be provided when debugAdapterHost is set. Please update your launch configuration.",
- );
- }
-
- // Check if we're going to launch a debug session or use an existing process
- if ("debugAdapterPort" in debugConfiguration) {
+ try {
if (
- "debugAdapterExecutable" in debugConfiguration ||
- "debugAdapterArgs" in debugConfiguration
+ "debugAdapterHost" in debugConfiguration &&
+ !("debugAdapterPort" in debugConfiguration)
) {
- return showErrorWithConfigureButton(
- "The debugAdapterPort property is incompatible with debugAdapterExecutable and debugAdapterArgs. Please update your launch configuration.",
+ throw new ErrorWithNotification(
+ "A debugAdapterPort must be provided when debugAdapterHost is set. Please update your launch configuration.",
+ new ConfigureButton(),
);
}
- } else {
- // Always try to create the debug adapter executable as this will show the user errors
- // if there are any.
- const executable = await createDebugAdapterExecutable(
- folder,
- debugConfiguration,
- /* userInteractive */ true,
- );
- if (!executable) {
- return undefined;
- }
- // Server mode needs to be handled here since DebugAdapterDescriptorFactory
- // will show an unhelpful error if it returns undefined. We'd rather show a
- // nicer error message here and allow stopping the debug session gracefully.
- const config = vscode.workspace.getConfiguration("lldb-dap", folder);
- if (
- config.get<boolean>("serverMode", false) &&
- (await isServerModeSupported(executable.command))
- ) {
- const serverInfo = await this.server.start(
- executable.command,
- executable.args,
- executable.options,
+ // Check if we're going to launch a debug session or use an existing process
+ if ("debugAdapterPort" in debugConfiguration) {
+ if (
+ "debugAdapterExecutable" in debugConfiguration ||
+ "debugAdapterArgs" in debugConfiguration
+ ) {
+ throw new ErrorWithNotification(
+ "The debugAdapterPort property is incompatible with debugAdapterExecutable and debugAdapterArgs. Please update your launch configuration.",
+ new ConfigureButton(),
+ );
+ }
+ } else {
+ // Always try to create the debug adapter executable as this will show the user errors
+ // if there are any.
+ const executable = await createDebugAdapterExecutable(
+ folder,
+ debugConfiguration,
);
- if (!serverInfo) {
+ if (!executable) {
return undefined;
}
- // Use a debug adapter host and port combination rather than an executable
- // and list of arguments.
- delete debugConfiguration.debugAdapterExecutable;
- delete debugConfiguration.debugAdapterArgs;
- debugConfiguration.debugAdapterHost = serverInfo.host;
- debugConfiguration.debugAdapterPort = serverInfo.port;
+
+ // Server mode needs to be handled here since DebugAdapterDescriptorFactory
+ // will show an unhelpful error if it returns undefined. We'd rather show a
+ // nicer error message here and allow stopping the debug session gracefully.
+ const config = vscode.workspace.getConfiguration("lldb-dap", folder);
+ if (
+ config.get<boolean>("serverMode", false) &&
+ (await isServerModeSupported(executable.command))
+ ) {
+ const serverInfo = await this.server.start(
+ executable.command,
+ executable.args,
+ executable.options,
+ );
+ if (!serverInfo) {
+ return undefined;
+ }
+ // Use a debug adapter host and port combination rather than an executable
+ // and list of arguments.
+ delete debugConfiguration.debugAdapterExecutable;
+ delete debugConfiguration.debugAdapterArgs;
+ debugConfiguration.debugAdapterHost = serverInfo.host;
+ debugConfiguration.debugAdapterPort = serverInfo.port;
+ }
}
- }
- return debugConfiguration;
+ return debugConfiguration;
+ } catch (error) {
+ // Show a better error message to the user if possible
+ if (!(error instanceof ErrorWithNotification)) {
+ throw error;
+ }
+ return await error.showNotification({
+ modal: true,
+ showConfigureButton: true,
+ });
+ }
}
}
diff --git a/lldb/tools/lldb-dap/src-ts/ui/error-messages.ts b/lldb/tools/lldb-dap/src-ts/ui/error-messages.ts
deleted file mode 100644
index 0127ca5e288cc..0000000000000
--- a/lldb/tools/lldb-dap/src-ts/ui/error-messages.ts
+++ /dev/null
@@ -1,49 +0,0 @@
-import * as vscode from "vscode";
-
-/**
- * Shows a modal when the debug adapter's path is not found
- */
-export async function showLLDBDapNotFoundMessage(path?: string) {
- const message =
- path !== undefined
- ? `Debug adapter path: ${path} is not a valid file`
- : "Unable to find the path to the LLDB debug adapter executable.";
- const openSettingsAction = "Open Settings";
- const callbackValue = await vscode.window.showErrorMessage(
- message,
- { modal: true },
- openSettingsAction,
- );
-
- if (openSettingsAction === callbackValue) {
- vscode.commands.executeCommand(
- "workbench.action.openSettings",
- "lldb-dap.executable-path",
- );
- }
-}
-
-/**
- * Shows an error message to the user that optionally allows them to open their
- * launch.json to configure it.
- *
- * Expected to be used in the context of a {@link vscode.DebugConfigurationProvider}.
- *
- * @param message The error message to display to the user
- * @returns `undefined` if the debug session should stop or `null` if the launch.json should be opened
- */
-export async function showErrorWithConfigureButton(
- message: string,
-): Promise<null | undefined> {
- const userSelection = await vscode.window.showErrorMessage(
- message,
- { modal: true },
- "Configure",
- );
-
- if (userSelection === "Configure") {
- return null; // Stops the debug session and opens the launch.json for editing
- }
-
- return undefined; // Only stops the debug session
-}
diff --git a/lldb/tools/lldb-dap/src-ts/ui/error-with-notification.ts b/lldb/tools/lldb-dap/src-ts/ui/error-with-notification.ts
new file mode 100644
index 0000000000000..1f8676d3eb135
--- /dev/null
+++ b/lldb/tools/lldb-dap/src-ts/ui/error-with-notification.ts
@@ -0,0 +1,68 @@
+import * as vscode from "vscode";
+import {
+ ConfigureButton,
+ NotificationButton,
+ showErrorMessage,
+} from "./show-error-message";
+
+/** Options used to configure {@link ErrorWithNotification.showNotification}. */
+export interface ShowNotificationOptions extends vscode.MessageOptions {
+ /**
+ * Whether or not to show the configure launch configuration button.
+ *
+ * **IMPORTANT**: the configure launch configuration button will do nothing if the
+ * callee isn't a {@link vscode.DebugConfigurationProvider}.
+ */
+ showConfigureButton?: boolean;
+}
+
+/**
+ * An error that is able to be displayed to the user as a notification.
+ *
+ * Used in combination with {@link showErrorMessage showErrorMessage()} when whatever caused
+ * the error was the result of a direct action by the user. E.g. launching a debug session.
+ */
+export class ErrorWithNotification extends Error {
+ private readonly buttons: NotificationButton<any, null | undefined>[];
+
+ constructor(
+ message: string,
+ ...buttons: NotificationButton<any, null | undefined>[]
+ ) {
+ super(message);
+ this.buttons = buttons;
+ }
+
+ /**
+ * Shows the notification to the user including the configure launch configuration button.
+ *
+ * **IMPORTANT**: the configure launch configuration button will do nothing if the
+ * callee isn't a {@link vscode.DebugConfigurationProvider}.
+ *
+ * @param options Configure the behavior of the notification
+ */
+ showNotification(
+ options: ShowNotificationOptions & { showConfigureButton: true },
+ ): Promise<null | undefined>;
+
+ /**
+ * Shows the notification to the user.
+ *
+ * @param options Configure the behavior of the notification
+ */
+ showNotification(options?: ShowNotificationOptions): Promise<undefined>;
+
+ // Actual implementation of showNotification()
+ async showNotification(
+ options: ShowNotificationOptions = {},
+ ): Promise<null | undefined> {
+ // Filter out the configure button unless explicitly requested
+ let buttons = this.buttons;
+ if (options.showConfigureButton !== true) {
+ buttons = buttons.filter(
+ (button) => !(button instanceof ConfigureButton),
+ );
+ }
+ return showErrorMessage(this.message, options, ...buttons);
+ }
+}
diff --git a/lldb/tools/lldb-dap/src-ts/ui/show-error-message.ts b/lldb/tools/lldb-dap/src-ts/ui/show-error-message.ts
new file mode 100644
index 0000000000000..de7b07dc97a64
--- /dev/null
+++ b/lldb/tools/lldb-dap/src-ts/ui/show-error-message.ts
@@ -0,0 +1,98 @@
+import * as vscode from "vscode";
+
+/**
+ * A button with a particular label that can perform an action when clicked.
+ *
+ * Used to add buttons to {@link showErrorMessage showErrorMessage()}.
+ */
+export interface NotificationButton<T, Result> {
+ readonly label: T;
+ action(): Promise<Result>;
+}
+
+/**
+ * Represents a button that, when clicked, will open a particular VS Code setting.
+ */
+export class OpenSettingsButton
+ implements NotificationButton<"Open Settings", undefined>
+{
+ readonly label = "Open Settings";
+
+ constructor(private readonly settingId?: string) {}
+
+ async action(): Promise<undefined> {
+ await vscode.commands.executeCommand(
+ "workbench.action.openSettings",
+ this.settingId ?? "@ext:llvm-vs-code-extensions.lldb-dap ",
+ );
+ }
+}
+
+/**
+ * Represents a button that, when clicked, will return `null`.
+ *
+ * Used by a {@link vscode.DebugConfigurationProvider} to indicate that VS Code should
+ * cancel a debug session and open its launch configuration.
+ *
+ * **IMPORTANT**: this button will do nothing if the callee isn't a
+ * {@link vscode.DebugConfigurationProvider}.
+ */
+export class ConfigureButton
+ implements NotificationButton<"Configure", null | undefined>
+{
+ readonly label = "Configure";
+
+ async action(): Promise<null | undefined> {
+ return null; // Opens the launch.json if returned from a DebugConfigurationProvider
+ }
+}
+
+/** Gets the Result type from a {@link NotificationButton} or string value. */
+type ResultOf<T> = T extends string
+ ? T
+ : T extends NotificationButton<any, infer Result>
+ ? Result
+ : never;
+
+/**
+ * Shows an error message to the user with an optional array of buttons.
+ *
+ * This can be used with common buttons such as {@link OpenSettingsButton} or plain
+ * strings as would normally be accepted by {@link vscode.window.showErrorMessage}.
+ *
+ * @param message The error message to display to the user
+ * @param options Configures the behaviour of the message.
+ * @param buttons An array of {@link NotificationButton buttons} or strings that the user can click on
+ * @returns `undefined` or the result of a button's action
+ */
+export async function showErrorMessage<
+ T extends string | NotificationButton<any, any>,
+>(
+ message: string,
+ options: vscode.MessageOptions = {},
+ ...buttons: T[]
+): Promise<ResultOf<T> | undefined> {
+ const userSelection = await vscode.window.showErrorMessage(
+ message,
+ options,
+ ...buttons.map((button) => {
+ if (typeof button === "string") {
+ return button;
+ }
+ return button.label;
+ }),
+ );
+
+ for (const button of buttons) {
+ if (typeof button === "string") {
+ if (userSelection === button) {
+ // Type assertion is required to let TypeScript know that "button" isn't just any old string.
+ return button as ResultOf<T>;
+ }
+ } else if (userSelection === button.label) {
+ return await button.action();
+ }
+ }
+
+ return undefined;
+}
>From abd2026bfee6e077cc60bec09bc757f8478d4d48 Mon Sep 17 00:00:00 2001
From: Matthew Bastien <matthew_bastien at apple.com>
Date: Fri, 14 Mar 2025 13:10:18 -0400
Subject: [PATCH 09/13] fix some review comments
---
lldb/tools/lldb-dap/src-ts/debug-adapter-factory.ts | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/lldb/tools/lldb-dap/src-ts/debug-adapter-factory.ts b/lldb/tools/lldb-dap/src-ts/debug-adapter-factory.ts
index 79fe6396416eb..7a207a95c2762 100644
--- a/lldb/tools/lldb-dap/src-ts/debug-adapter-factory.ts
+++ b/lldb/tools/lldb-dap/src-ts/debug-adapter-factory.ts
@@ -118,7 +118,7 @@ async function getDAPArguments(
workspaceFolder: vscode.WorkspaceFolder | undefined,
configuration: vscode.DebugConfiguration,
): Promise<string[]> {
- // Check the debug configuration for arguments first
+ // Check the debug configuration for arguments first.
const debugConfigArgs = configuration.debugAdapterArgs;
if (debugConfigArgs) {
if (
@@ -132,7 +132,7 @@ async function getDAPArguments(
}
return debugConfigArgs;
}
- // Fall back on the workspace configuration
+ // Fall back on the workspace configuration.
return vscode.workspace
.getConfiguration("lldb-dap", workspaceFolder)
.get<string[]>("arguments", []);
>From 71ffc3b6cc6b6ca5d88d9947803667af7b3c1929 Mon Sep 17 00:00:00 2001
From: Matthew Bastien <matthew_bastien at apple.com>
Date: Thu, 20 Mar 2025 17:10:25 -0400
Subject: [PATCH 10/13] use resolveDebugConfigurationWithSubstitutedVariables
---
lldb/tools/lldb-dap/src-ts/debug-configuration-provider.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lldb/tools/lldb-dap/src-ts/debug-configuration-provider.ts b/lldb/tools/lldb-dap/src-ts/debug-configuration-provider.ts
index 46c9ea77c0d22..0272509ee55f7 100644
--- a/lldb/tools/lldb-dap/src-ts/debug-configuration-provider.ts
+++ b/lldb/tools/lldb-dap/src-ts/debug-configuration-provider.ts
@@ -25,7 +25,7 @@ export class LLDBDapConfigurationProvider
{
constructor(private readonly server: LLDBDapServer) {}
- async resolveDebugConfiguration(
+ async resolveDebugConfigurationWithSubstitutedVariables(
folder: vscode.WorkspaceFolder | undefined,
debugConfiguration: vscode.DebugConfiguration,
_token?: vscode.CancellationToken,
>From 35bafccde3ab1f4a4f97b1f440d78f10c0edaad7 Mon Sep 17 00:00:00 2001
From: Matthew Bastien <matthew_bastien at apple.com>
Date: Thu, 20 Mar 2025 17:10:47 -0400
Subject: [PATCH 11/13] move isExecutable() error logic into getDAPExecutable()
---
.../lldb-dap/src-ts/debug-adapter-factory.ts | 30 +++++++++++--------
1 file changed, 18 insertions(+), 12 deletions(-)
diff --git a/lldb/tools/lldb-dap/src-ts/debug-adapter-factory.ts b/lldb/tools/lldb-dap/src-ts/debug-adapter-factory.ts
index 7a207a95c2762..e23d717a70101 100644
--- a/lldb/tools/lldb-dap/src-ts/debug-adapter-factory.ts
+++ b/lldb/tools/lldb-dap/src-ts/debug-adapter-factory.ts
@@ -83,6 +83,12 @@ async function getDAPExecutable(
// Check if the executable was provided in the launch configuration.
const launchConfigPath = configuration["debugAdapterExecutable"];
if (typeof launchConfigPath === "string" && launchConfigPath.length !== 0) {
+ if (!(await isExecutable(launchConfigPath))) {
+ throw new ErrorWithNotification(
+ `Debug adapter path "${launchConfigPath}" is not a valid file. The path comes from your launch configuration.`,
+ new ConfigureButton(),
+ );
+ }
return launchConfigPath;
}
@@ -90,12 +96,24 @@ async function getDAPExecutable(
const config = vscode.workspace.getConfiguration("lldb-dap", workspaceFolder);
const configPath = config.get<string>("executable-path");
if (configPath && configPath.length !== 0) {
+ if (!(await isExecutable(configPath))) {
+ throw new ErrorWithNotification(
+ `Debug adapter path "${configPath}" is not a valid file. The path comes from your settings.`,
+ new OpenSettingsButton("lldb-dap.executable-path"),
+ );
+ }
return configPath;
}
// Try finding the lldb-dap binary.
const foundPath = await findDAPExecutable();
if (foundPath) {
+ if (!(await isExecutable(foundPath))) {
+ throw new ErrorWithNotification(
+ `Found a potential debug adapter on your system at "${configPath}", but it is not a valid file.`,
+ new OpenSettingsButton("lldb-dap.executable-path"),
+ );
+ }
return foundPath;
}
@@ -161,18 +179,6 @@ export async function createDebugAdapterExecutable(
config.get<{ [key: string]: string }>("environment") || {};
const dapPath = await getDAPExecutable(workspaceFolder, configuration);
- if (!(await isExecutable(dapPath))) {
- let message = `Debug adapter path "${dapPath}" is not a valid file.`;
- let buttons: (OpenSettingsButton | ConfigureButton)[] = [
- new OpenSettingsButton("lldb-dap.executable-path"),
- ];
- if ("debugAdapterPath" in configuration) {
- message += " The path comes from your launch configuration.";
- buttons = [new ConfigureButton()];
- }
- throw new ErrorWithNotification(message, ...buttons);
- }
-
const dbgOptions = {
env: {
...configEnvironment,
>From 87a2fbe505c792c064c9e7b7a9f1e4186b26bf73 Mon Sep 17 00:00:00 2001
From: Matthew Bastien <matthew_bastien at apple.com>
Date: Thu, 20 Mar 2025 17:13:13 -0400
Subject: [PATCH 12/13] use util.isDeepStrictEqual() instead of custom equality
logic
---
lldb/tools/lldb-dap/src-ts/lldb-dap-server.ts | 15 ++-------------
1 file changed, 2 insertions(+), 13 deletions(-)
diff --git a/lldb/tools/lldb-dap/src-ts/lldb-dap-server.ts b/lldb/tools/lldb-dap/src-ts/lldb-dap-server.ts
index 2241a8676e46f..dca8fc2f18274 100644
--- a/lldb/tools/lldb-dap/src-ts/lldb-dap-server.ts
+++ b/lldb/tools/lldb-dap/src-ts/lldb-dap-server.ts
@@ -1,18 +1,7 @@
import * as child_process from "node:child_process";
+import { isDeepStrictEqual } from "util";
import * as vscode from "vscode";
-function areArraysEqual<T>(lhs: T[], rhs: T[]): boolean {
- if (lhs.length !== rhs.length) {
- return false;
- }
- for (let i = 0; i < lhs.length; i++) {
- if (lhs[i] !== rhs[i]) {
- return false;
- }
- }
- return true;
-}
-
/**
* Represents a running lldb-dap process that is accepting connections (i.e. in "server mode").
*
@@ -96,7 +85,7 @@ export class LLDBDapServer implements vscode.Disposable {
return true;
}
- if (areArraysEqual(this.serverProcess.spawnargs, [dapPath, ...args])) {
+ if (isDeepStrictEqual(this.serverProcess.spawnargs, [dapPath, ...args])) {
return true;
}
>From 2c6a8c10d585c10be08cce92fcafc0cd349403bb Mon Sep 17 00:00:00 2001
From: Matthew Bastien <matthew_bastien at apple.com>
Date: Thu, 20 Mar 2025 17:22:48 -0400
Subject: [PATCH 13/13] update wording in server restart modal
---
lldb/tools/lldb-dap/src-ts/lldb-dap-server.ts | 17 +++++++++++++++--
1 file changed, 15 insertions(+), 2 deletions(-)
diff --git a/lldb/tools/lldb-dap/src-ts/lldb-dap-server.ts b/lldb/tools/lldb-dap/src-ts/lldb-dap-server.ts
index dca8fc2f18274..f40dbf049a4bb 100644
--- a/lldb/tools/lldb-dap/src-ts/lldb-dap-server.ts
+++ b/lldb/tools/lldb-dap/src-ts/lldb-dap-server.ts
@@ -90,8 +90,21 @@ export class LLDBDapServer implements vscode.Disposable {
}
const userInput = await vscode.window.showInformationMessage(
- "A server mode instance of lldb-dap is already running, but the arguments are different from what is requested in your debug configuration or settings. Would you like to restart the server?",
- { modal: true },
+ "The arguments to lldb-dap have changed. Would you like to restart the server?",
+ {
+ modal: true,
+ detail: `An existing lldb-dap server (${this.serverProcess.pid}) is running with different arguments.
+
+The previous lldb-dap server was started with:
+
+${this.serverProcess.spawnargs.join(" ")}
+
+The new lldb-dap server will be started with:
+
+${dapPath} ${args.join(" ")}
+
+Restarting the server will interrupt any existing debug sessions and start a new server.`,
+ },
"Restart",
"Use Existing",
);
More information about the lldb-commits
mailing list