diff --git a/package.json b/package.json index 75f7f110..3a37ae4e 100644 --- a/package.json +++ b/package.json @@ -1719,7 +1719,12 @@ "type": "objectscript", "request": "launch", "name": "XDebug" - } + }, + { + "type": "objectscript", + "request": "attach", + "name": "Attach to running process" + } ], "configurationSnippets": [ { diff --git a/src/debug/debugAdapterFactory.ts b/src/debug/debugAdapterFactory.ts index 0e1b31cb..738a8870 100644 --- a/src/debug/debugAdapterFactory.ts +++ b/src/debug/debugAdapterFactory.ts @@ -5,33 +5,41 @@ import { ObjectScriptDebugSession } from "./debugSession"; export class ObjectScriptDebugAdapterDescriptorFactory implements vscode.DebugAdapterDescriptorFactory, vscode.Disposable { - private server?: net.Server; + private serverMap = new Map(); public createDebugAdapterDescriptor( session: vscode.DebugSession, executable: vscode.DebugAdapterExecutable | undefined ): vscode.ProviderResult { - if (!this.server) { - // start listening on a random port - const debugSession = new ObjectScriptDebugSession(); + const debugSession = new ObjectScriptDebugSession(); + + // pickProcess may have added a suffix to inform us which folder's connection it used + const workspaceFolderIndex = (session.configuration.processId as string)?.split("@")[1]; + const workspaceFolderUri = workspaceFolderIndex + ? vscode.workspace.workspaceFolders[parseInt(workspaceFolderIndex)]?.uri + : undefined; + debugSession.setupAPI(workspaceFolderUri); - this.server = net + const serverId = debugSession.serverId; + let server = this.serverMap.get(serverId); + if (!server) { + // start listening on a random port + server = net .createServer((socket) => { debugSession.setRunAsServer(true); debugSession.start(socket as NodeJS.ReadableStream, socket); }) .listen(0); + this.serverMap.set(serverId, server); } - // make VS Code connect to debug server - const address = this.server.address(); + // make VS Code connect to this debug server + const address = server.address(); const port = typeof address !== "string" ? address.port : 9000; return new vscode.DebugAdapterServer(port); } public dispose(): void { - if (this.server) { - this.server.close(); - } + this.serverMap.forEach((server) => server.close()); } } diff --git a/src/debug/debugSession.ts b/src/debug/debugSession.ts index 047739f3..bbfb6268 100644 --- a/src/debug/debugSession.ts +++ b/src/debug/debugSession.ts @@ -63,6 +63,15 @@ async function convertClientPathToDebugger(uri: vscode.Uri, namespace: string): } export class ObjectScriptDebugSession extends LoggingDebugSession { + /** After setupAPI() has been called this will return the serverId string */ + public get serverId(): string | undefined { + return this._api?.serverId; + } + + private _api?: AtelierAPI; + + private _workspaceFolderUri?: vscode.Uri; + private _statuses = new Map(); private _connection: xdebug.Connection; @@ -89,8 +98,6 @@ export class ObjectScriptDebugSession extends LoggingDebugSession { private _workspace: string; - private cookies: string[] = []; - /** If this is a CSPDEBUG session */ private _isCsp = false; @@ -124,6 +131,27 @@ export class ObjectScriptDebugSession extends LoggingDebugSession { } while (!this._debugTargetSet); } + /** To be called immediately after construction */ + public setupAPI(workspaceFolderUri?: vscode.Uri): void { + // Only effective the first time + if (this._api) { + return; + } + + this._workspaceFolderUri = workspaceFolderUri; + if (workspaceFolderUri) { + // The uri of the relevant workspace folder was set after construction + this._workspace = undefined; + this._api = new AtelierAPI(workspaceFolderUri); + } else { + // Fall back to old way of deciding where to connect + const file = currentFile(); + this._workspace = file?.workspaceFolder; + this._api = new AtelierAPI(file?.uri); + } + return; + } + /** Check if the target is stopped */ private async _isStopped(): Promise { return this._connection @@ -161,21 +189,16 @@ export class ObjectScriptDebugSession extends LoggingDebugSession { }; try { - const file = currentFile(); - this._workspace = file?.workspaceFolder; - - const api = new AtelierAPI(file?.uri); - this.cookies = api.cookies; - if (!api.active) { + if (!this._api.active) { throw new Error("Connection not active"); } - this._namespace = api.ns; - this._url = api.xdebugUrl(); + this._namespace = this._api.ns; + this._url = this._api.xdebugUrl(); const socket = new WebSocket(this._url, { rejectUnauthorized: vscode.workspace.getConfiguration("http").get("proxyStrictSSL"), headers: { - cookie: this.cookies, + cookie: this._api.cookies, }, }); @@ -240,7 +263,8 @@ export class ObjectScriptDebugSession extends LoggingDebugSession { protected async attachRequest(response: DebugProtocol.AttachResponse, args: AttachRequestArguments): Promise { try { this._debugTargetSet = this._isLaunch = false; - const debugTarget = args.cspDebugId != undefined ? `CSPDEBUG:${args.cspDebugId}` : `PID:${args.processId}`; + const debugTarget = + args.cspDebugId != undefined ? `CSPDEBUG:${args.cspDebugId}` : `PID:${args.processId.split("@")[0]}`; await this._connection.sendFeatureSetCommand("debug_target", debugTarget); if (args.cspDebugId != undefined) { if (args.isUnitTest) { @@ -590,7 +614,13 @@ export class ObjectScriptDebugSession extends LoggingDebugSession { stack.stack.map(async (stackFrame: xdebug.StackFrame, index): Promise => { const [, namespace, name] = decodeURI(stackFrame.fileUri).match(/^dbgp:\/\/\|([^|]+)\|(.*)$/); const routine = name; - const fileUri = DocumentContentProvider.getUri(routine, this._workspace, namespace); + const fileUri = DocumentContentProvider.getUri( + routine, + this._workspace, + namespace, + undefined, + this._workspaceFolderUri + ); const source = new Source(routine, fileUri.toString()); let line = stackFrame.line + 1; const place = `${stackFrame.method}+${stackFrame.methodOffset}`; diff --git a/src/extension.ts b/src/extension.ts index 415533c4..f4f4fce7 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1015,7 +1015,12 @@ export async function activate(context: vscode.ExtensionContext): Promise { matchOnDescription: true, }) .then((value) => { - if (value) return value.label; + if (value) { + const workspaceFolderIndex = vscode.workspace.workspaceFolders.findIndex( + (folder) => folder.uri.toString() === connectionUri.toString() + ); + return workspaceFolderIndex < 0 ? value.label : `${value.label}@${workspaceFolderIndex}`; + } }); }), vscode.commands.registerCommand("vscode-objectscript.jumpToTagAndOffset", jumpToTagAndOffset),