From be326b5644501da80c8abb5415a46d4156cf3d9a Mon Sep 17 00:00:00 2001 From: Adam Driscoll Date: Wed, 6 Mar 2019 22:55:32 -0700 Subject: [PATCH 1/5] Add Debug Runspace command --- package.json | 27 +++++++- src/features/DebugSession.ts | 127 +++++++++++++++++++++++++++++++++++ src/main.ts | 2 + 3 files changed, 155 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index f8e9726f35..446eede40d 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,8 @@ "onCommand:PowerShell.ShowSessionConsole", "onCommand:PowerShell.ShowSessionMenu", "onCommand:PowerShell.RestartSession", - "onView:PowerShellCommands" + "onView:PowerShellCommands", + "onCommand:PowerShell.PickRunspace" ], "dependencies": { "vscode-languageclient": "~5.1.1" @@ -318,6 +319,7 @@ "runtime": "node", "variables": { "PickPSHostProcess": "PowerShell.PickPSHostProcess", + "PickRunspace": "PowerShell.PickRunspace", "SpecifyScriptArgs": "PowerShell.SpecifyScriptArgs" }, "languages": [ @@ -407,6 +409,17 @@ "request": "launch", "cwd": "" } + }, + { + "label": "PowerShell: Debug Runspace", + "description": "Open runspace picker to select runspace to attach debugger to", + "body": { + "name": "PowerShell Debug Runspace", + "type": "PowerShell", + "request": "attach", + "processId": "current", + "localRunspaceId": "^\"\\${command:PickRunspace}\"" + } } ], "configurationAttributes": { @@ -451,6 +464,11 @@ "type": "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 + }, + "localRunspaceId": { + "type": "string", + "description": "The ID of the runspace to debug. Defaults to 1. Works only on PowerShell 5 and above.", + "default": "${command:PickRunspace}" } } } @@ -495,6 +513,13 @@ "type": "powershell", "request": "launch", "cwd": "" + }, + { + "name": "PowerShell Debug Runspace", + "type": "powershell", + "request": "attach", + "processId": "current", + "localRunspaceId": "${command:PickRunspace}" } ] } diff --git a/src/features/DebugSession.ts b/src/features/DebugSession.ts index a8c55322f8..ae2163b85a 100644 --- a/src/features/DebugSession.ts +++ b/src/features/DebugSession.ts @@ -381,3 +381,130 @@ export class PickPSHostProcessFeature implements IFeature { } } } + +interface IRunspaceItem extends vscode.QuickPickItem { + id: string; // payload for the QuickPick UI +} + +interface IRunspace { + id: string; + name: string; + availability: string; +} + +export const GetRunspaceRequestType = + new RequestType("powerShell/getRunspace"); + +interface IGetRunspaceResponseBody { + runspaces: IRunspace[]; +} + +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", () => { + return this.getLanguageClient() + .then((_) => this.pickRunspace(), (_) => undefined); + }); + } + + 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(): Thenable { + return this.languageClient.sendRequest(GetRunspaceRequestType, null).then((runspaces) => { + const items: IRunspaceItem[] = []; + + for (const p in runspaces) { + if (runspaces.hasOwnProperty(p)) { + + // Skip default runspace + if (runspaces[p].id === 1) { + continue; + } + + items.push({ + label: runspaces[p].name, + description: `ID: ${runspaces[p].id} - ${runspaces[p].availability}`, + id: runspaces[p].id, + }); + } + } + + 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); From e7d72b54c19d97e1a138e2a1292323ad5aa66392 Mon Sep 17 00:00:00 2001 From: Adam Driscoll Date: Fri, 8 Mar 2019 23:07:53 -0700 Subject: [PATCH 2/5] Update package.json based on feedback. --- package.json | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/package.json b/package.json index 446eede40d..c780b4ca99 100644 --- a/package.json +++ b/package.json @@ -32,12 +32,12 @@ "onCommand:PowerShell.NewProjectFromTemplate", "onCommand:PowerShell.OpenExamplesFolder", "onCommand:PowerShell.PickPSHostProcess", + "onCommand:PowerShell.PickRunspace", "onCommand:PowerShell.SpecifyScriptArgs", "onCommand:PowerShell.ShowSessionConsole", "onCommand:PowerShell.ShowSessionMenu", "onCommand:PowerShell.RestartSession", - "onView:PowerShellCommands", - "onCommand:PowerShell.PickRunspace" + "onView:PowerShellCommands" ], "dependencies": { "vscode-languageclient": "~5.1.1" @@ -411,14 +411,14 @@ } }, { - "label": "PowerShell: Debug Runspace", + "label": "PowerShell: Attach Interactive Session Runspace", "description": "Open runspace picker to select runspace to attach debugger to", "body": { - "name": "PowerShell Debug Runspace", + "name": "PowerShell Attach Interactive Session Runspace", "type": "PowerShell", "request": "attach", "processId": "current", - "localRunspaceId": "^\"\\${command:PickRunspace}\"" + "runspaceId": "^\"\\${command:PickRunspace}\"" } } ], @@ -461,14 +461,9 @@ "default": "${command:PickPSHostProcess}" }, "runspaceId": { - "type": "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 - }, - "localRunspaceId": { "type": "string", - "description": "The ID of the runspace to debug. Defaults to 1. Works only on PowerShell 5 and above.", - "default": "${command:PickRunspace}" + "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" } } } @@ -506,7 +501,7 @@ "type": "powershell", "request": "attach", "processId": "${command:PickPSHostProcess}", - "runspaceId": 1 + "runspaceId": "1" }, { "name": "PowerShell Interactive Session", @@ -515,11 +510,11 @@ "cwd": "" }, { - "name": "PowerShell Debug Runspace", + "name": "PowerShell Attach Interactive Session Runspace", "type": "powershell", "request": "attach", "processId": "current", - "localRunspaceId": "${command:PickRunspace}" + "runspaceId": "${command:PickRunspace}" } ] } From 35deb7a61483974135442154c0cbba0fc81593ba Mon Sep 17 00:00:00 2001 From: Adam Driscoll Date: Mon, 11 Mar 2019 15:16:33 -0600 Subject: [PATCH 3/5] Update response to match PSES response. --- src/features/DebugSession.ts | 34 +++++++++++++--------------------- 1 file changed, 13 insertions(+), 21 deletions(-) diff --git a/src/features/DebugSession.ts b/src/features/DebugSession.ts index cd7d7de123..3cccc9dcda 100644 --- a/src/features/DebugSession.ts +++ b/src/features/DebugSession.ts @@ -392,17 +392,13 @@ interface IRunspaceItem extends vscode.QuickPickItem { } interface IRunspace { - id: string; + id: number; name: string; availability: string; } export const GetRunspaceRequestType = - new RequestType("powerShell/getRunspace"); - -interface IGetRunspaceResponseBody { - runspaces: IRunspace[]; -} + new RequestType("powerShell/getRunspace"); export class PickRunspaceFeature implements IFeature { @@ -412,7 +408,6 @@ export class PickRunspaceFeature implements IFeature { private getLanguageClientResolve: (value?: LanguageClient | Thenable) => void; constructor() { - this.command = vscode.commands.registerCommand("PowerShell.PickRunspace", () => { return this.getLanguageClient() @@ -473,23 +468,20 @@ export class PickRunspaceFeature implements IFeature { } private pickRunspace(): Thenable { - return this.languageClient.sendRequest(GetRunspaceRequestType, null).then((runspaces) => { + return this.languageClient.sendRequest(GetRunspaceRequestType, null).then((response) => { const items: IRunspaceItem[] = []; - for (const p in runspaces) { - if (runspaces.hasOwnProperty(p)) { - - // Skip default runspace - if (runspaces[p].id === 1) { - continue; - } - - items.push({ - label: runspaces[p].name, - description: `ID: ${runspaces[p].id} - ${runspaces[p].availability}`, - id: runspaces[p].id, - }); + for (const runspace of response) { + // Skip default runspace + if (runspace.id === 1) { + continue; } + + items.push({ + label: runspace.name, + description: `ID: ${runspace.id} - ${runspace.availability}`, + id: runspace.id.toString(), + }); } const options: vscode.QuickPickOptions = { From 64bb51cdd96fbde5b7dcf102e9e335edbeccf82a Mon Sep 17 00:00:00 2001 From: Adam Driscoll Date: Tue, 12 Mar 2019 10:21:47 -0600 Subject: [PATCH 4/5] Change how we ask for RunspaceId. --- package.json | 8 +++----- src/features/DebugSession.ts | 26 ++++++++++++++++++++------ 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/package.json b/package.json index 2395dba1a4..b54d94b992 100644 --- a/package.json +++ b/package.json @@ -462,7 +462,7 @@ "runspaceId": { "type": "string", "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", @@ -503,8 +503,7 @@ { "name": "PowerShell Attach to Host Process", "type": "PowerShell", - "request": "attach", - "runspaceId": "1" + "request": "attach" }, { "name": "PowerShell Interactive Session", @@ -516,8 +515,7 @@ "name": "PowerShell Attach Interactive Session Runspace", "type": "PowerShell", "request": "attach", - "processId": "current", - "runspaceId": "${command:PickRunspace}" + "processId": "current" } ] } diff --git a/src/features/DebugSession.ts b/src/features/DebugSession.ts index 3cccc9dcda..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; + } } } @@ -409,10 +423,10 @@ export class PickRunspaceFeature implements IFeature { constructor() { this.command = - vscode.commands.registerCommand("PowerShell.PickRunspace", () => { + vscode.commands.registerCommand("PowerShell.PickRunspace", (processId) => { return this.getLanguageClient() - .then((_) => this.pickRunspace(), (_) => undefined); - }); + .then((_) => this.pickRunspace(processId), (_) => undefined); + }, this); } public setLanguageClient(languageClient: LanguageClient) { @@ -467,13 +481,13 @@ export class PickRunspaceFeature implements IFeature { } } - private pickRunspace(): Thenable { - return this.languageClient.sendRequest(GetRunspaceRequestType, null).then((response) => { + 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) { + if (runspace.id === 1 || runspace.name === "PSAttachRunspace") { continue; } From 0a3106422a20ce838c3bf3f4ba0c3c8bc1afd619 Mon Sep 17 00:00:00 2001 From: Adam Driscoll Date: Tue, 12 Mar 2019 19:30:18 -0600 Subject: [PATCH 5/5] Clean up. --- package.json | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index b54d94b992..afa62b90e8 100644 --- a/package.json +++ b/package.json @@ -416,8 +416,7 @@ "name": "PowerShell Attach Interactive Session Runspace", "type": "PowerShell", "request": "attach", - "processId": "current", - "runspaceId": "^\"\\${command:PickRunspace}\"" + "processId": "current" } } ], @@ -460,7 +459,7 @@ "default": null }, "runspaceId": { - "type": "string", + "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": null },