| 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"; |
| import { ConfigureButton, showErrorMessage } from "./ui/show-error-message"; |
| import { ErrorWithNotification } from "./ui/error-with-notification"; |
| |
| const exec = util.promisify(child_process.execFile); |
| |
| /** |
| * 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); |
| } |
| |
| interface BoolConfig { |
| type: "boolean"; |
| default: boolean; |
| } |
| interface StringConfig { |
| type: "string"; |
| default: string; |
| } |
| interface NumberConfig { |
| type: "number"; |
| default: number; |
| } |
| interface StringArrayConfig { |
| type: "stringArray"; |
| default: string[]; |
| } |
| type DefaultConfig = |
| | BoolConfig |
| | NumberConfig |
| | StringConfig |
| | StringArrayConfig; |
| |
| const configurations: Record<string, DefaultConfig> = { |
| // Keys for debugger configurations. |
| commandEscapePrefix: { type: "string", default: "`" }, |
| customFrameFormat: { type: "string", default: "" }, |
| customThreadFormat: { type: "string", default: "" }, |
| detachOnError: { type: "boolean", default: false }, |
| disableASLR: { type: "boolean", default: true }, |
| disableSTDIO: { type: "boolean", default: false }, |
| displayExtendedBacktrace: { type: "boolean", default: false }, |
| enableAutoVariableSummaries: { type: "boolean", default: false }, |
| enableSyntheticChildDebugging: { type: "boolean", default: false }, |
| timeout: { type: "number", default: 30 }, |
| |
| // Keys for platform / target configuration. |
| platformName: { type: "string", default: "" }, |
| targetTriple: { type: "string", default: "" }, |
| |
| // Keys for debugger command hooks. |
| initCommands: { type: "stringArray", default: [] }, |
| preRunCommands: { type: "stringArray", default: [] }, |
| postRunCommands: { type: "stringArray", default: [] }, |
| stopCommands: { type: "stringArray", default: [] }, |
| exitCommands: { type: "stringArray", default: [] }, |
| terminateCommands: { type: "stringArray", default: [] }, |
| }; |
| |
| 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> { |
| let config = vscode.workspace.getConfiguration("lldb-dap"); |
| for (const [key, cfg] of Object.entries(configurations)) { |
| if (Reflect.has(debugConfiguration, key)) { |
| continue; |
| } |
| const value = config.get(key); |
| if (!value || value === cfg.default) { |
| continue; |
| } |
| switch (cfg.type) { |
| case "string": |
| if (typeof value !== "string") { |
| throw new Error(`Expected ${key} to be a string, got ${value}`); |
| } |
| break; |
| case "number": |
| if (typeof value !== "number") { |
| throw new Error(`Expected ${key} to be a number, got ${value}`); |
| } |
| break; |
| case "boolean": |
| if (typeof value !== "boolean") { |
| throw new Error(`Expected ${key} to be a boolean, got ${value}`); |
| } |
| break; |
| case "stringArray": |
| if (typeof value !== "object" && Array.isArray(value)) { |
| throw new Error( |
| `Expected ${key} to be a array of strings, got ${value}`, |
| ); |
| } |
| if ((value as string[]).length === 0) { |
| continue; |
| } |
| break; |
| } |
| |
| debugConfiguration[key] = value; |
| } |
| |
| return debugConfiguration; |
| } |
| |
| async resolveDebugConfigurationWithSubstitutedVariables( |
| folder: vscode.WorkspaceFolder | undefined, |
| debugConfiguration: vscode.DebugConfiguration, |
| _token?: vscode.CancellationToken, |
| ): Promise<vscode.DebugConfiguration | null | undefined> { |
| try { |
| if ( |
| "debugAdapterHostname" in debugConfiguration && |
| !("debugAdapterPort" in debugConfiguration) |
| ) { |
| throw new ErrorWithNotification( |
| "A debugAdapterPort must be provided when debugAdapterHostname is set. Please update your launch configuration.", |
| new ConfigureButton(), |
| ); |
| } |
| |
| // 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 (!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, |
| ); |
| 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.debugAdapterHostname = serverInfo.host; |
| debugConfiguration.debugAdapterPort = serverInfo.port; |
| } |
| } |
| |
| 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, |
| }); |
| } |
| } |
| } |