diff --git a/package.json b/package.json index db53b10032..afa62b90e8 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "onCommand:PowerShell.NewProjectFromTemplate", "onCommand:PowerShell.OpenExamplesFolder", "onCommand:PowerShell.PickPSHostProcess", + "onCommand:PowerShell.PickRunspace", "onCommand:PowerShell.SpecifyScriptArgs", "onCommand:PowerShell.ShowSessionConsole", "onCommand:PowerShell.ShowSessionMenu", @@ -318,6 +319,7 @@ "runtime": "node", "variables": { "PickPSHostProcess": "PowerShell.PickPSHostProcess", + "PickRunspace": "PowerShell.PickRunspace", "SpecifyScriptArgs": "PowerShell.SpecifyScriptArgs" }, "languages": [ @@ -406,6 +408,16 @@ "request": "launch", "cwd": "" } + }, + { + "label": "PowerShell: Attach Interactive Session Runspace", + "description": "Open runspace picker to select runspace to attach debugger to", + "body": { + "name": "PowerShell Attach Interactive Session Runspace", + "type": "PowerShell", + "request": "attach", + "processId": "current" + } } ], "configurationAttributes": { @@ -447,9 +459,9 @@ "default": null }, "runspaceId": { - "type": "number", + "type":["string","number"], "description": "Optional: The ID of the runspace to debug in the attached process. Defaults to 1. Works only on PowerShell 5 and above.", - "default": 1 + "default": null }, "customPipeName": { "type": "string", @@ -490,14 +502,19 @@ { "name": "PowerShell Attach to Host Process", "type": "PowerShell", - "request": "attach", - "runspaceId": 1 + "request": "attach" }, { "name": "PowerShell Interactive Session", "type": "PowerShell", "request": "launch", "cwd": "" + }, + { + "name": "PowerShell Attach Interactive Session Runspace", + "type": "PowerShell", + "request": "attach", + "processId": "current" } ] } diff --git a/src/features/DebugSession.ts b/src/features/DebugSession.ts index 0cfbefc17f..1c071af8ee 100644 --- a/src/features/DebugSession.ts +++ b/src/features/DebugSession.ts @@ -80,6 +80,20 @@ export class DebugSessionFeature implements IFeature, DebugConfigurationProvider // if nothing is set, prompt for the processId if (!config.customPipeName && !config.processId) { config.processId = await vscode.commands.executeCommand("PowerShell.PickPSHostProcess"); + + // No process selected. Cancel attach. + if (!config.processId) { + return null; + } + } + + if (!config.runspaceId) { + config.runspaceId = await vscode.commands.executeCommand("PowerShell.PickRunspace", config.processId); + + // No runspace selected. Cancel attach. + if (!config.runspaceId) { + return null; + } } } @@ -386,3 +400,122 @@ export class PickPSHostProcessFeature implements IFeature { } } } + +interface IRunspaceItem extends vscode.QuickPickItem { + id: string; // payload for the QuickPick UI +} + +interface IRunspace { + id: number; + name: string; + availability: string; +} + +export const GetRunspaceRequestType = + new RequestType("powerShell/getRunspace"); + +export class PickRunspaceFeature implements IFeature { + + private command: vscode.Disposable; + private languageClient: LanguageClient; + private waitingForClientToken: vscode.CancellationTokenSource; + private getLanguageClientResolve: (value?: LanguageClient | Thenable) => void; + + constructor() { + this.command = + vscode.commands.registerCommand("PowerShell.PickRunspace", (processId) => { + return this.getLanguageClient() + .then((_) => this.pickRunspace(processId), (_) => undefined); + }, this); + } + + public setLanguageClient(languageClient: LanguageClient) { + this.languageClient = languageClient; + + if (this.waitingForClientToken) { + this.getLanguageClientResolve(this.languageClient); + this.clearWaitingToken(); + } + } + + public dispose() { + this.command.dispose(); + } + + private getLanguageClient(): Thenable { + if (this.languageClient) { + return Promise.resolve(this.languageClient); + } else { + // If PowerShell isn't finished loading yet, show a loading message + // until the LanguageClient is passed on to us + this.waitingForClientToken = new vscode.CancellationTokenSource(); + + return new Promise( + (resolve, reject) => { + this.getLanguageClientResolve = resolve; + + vscode.window + .showQuickPick( + ["Cancel"], + { placeHolder: "Attach to PowerShell host process: Please wait, starting PowerShell..." }, + this.waitingForClientToken.token) + .then((response) => { + if (response === "Cancel") { + this.clearWaitingToken(); + reject(); + } + }); + + // Cancel the loading prompt after 60 seconds + setTimeout(() => { + if (this.waitingForClientToken) { + this.clearWaitingToken(); + reject(); + + vscode.window.showErrorMessage( + "Attach to PowerShell host process: PowerShell session took too long to start."); + } + }, 60000); + }, + ); + } + } + + private pickRunspace(processId): Thenable { + return this.languageClient.sendRequest(GetRunspaceRequestType, processId).then((response) => { + const items: IRunspaceItem[] = []; + + for (const runspace of response) { + // Skip default runspace + if (runspace.id === 1 || runspace.name === "PSAttachRunspace") { + continue; + } + + items.push({ + label: runspace.name, + description: `ID: ${runspace.id} - ${runspace.availability}`, + id: runspace.id.toString(), + }); + } + + const options: vscode.QuickPickOptions = { + placeHolder: "Select PowerShell runspace to debug", + matchOnDescription: true, + matchOnDetail: true, + }; + + return vscode.window.showQuickPick(items, options).then((item) => { + // Return undefined when user presses Esc. + // This prevents VSCode from opening launch.json in this case which happens if we return "". + return item ? `${item.id}` : undefined; + }); + }); + } + + private clearWaitingToken() { + if (this.waitingForClientToken) { + this.waitingForClientToken.dispose(); + this.waitingForClientToken = undefined; + } + } +} diff --git a/src/main.ts b/src/main.ts index b6166556cc..84e03f1608 100644 --- a/src/main.ts +++ b/src/main.ts @@ -13,6 +13,7 @@ import { ConsoleFeature } from "./features/Console"; import { CustomViewsFeature } from "./features/CustomViews"; import { DebugSessionFeature } from "./features/DebugSession"; import { PickPSHostProcessFeature } from "./features/DebugSession"; +import { PickRunspaceFeature } from "./features/DebugSession"; import { SpecifyScriptArgsFeature } from "./features/DebugSession"; import { DocumentFormatterFeature } from "./features/DocumentFormatter"; import { ExamplesFeature } from "./features/Examples"; @@ -142,6 +143,7 @@ export function activate(context: vscode.ExtensionContext): void { new SpecifyScriptArgsFeature(context), new HelpCompletionFeature(logger), new CustomViewsFeature(), + new PickRunspaceFeature(), ]; sessionManager.setExtensionFeatures(extensionFeatures);