From 6e4d8cc21ef5c81a8ad663acefbc502e75f8bd2f Mon Sep 17 00:00:00 2001 From: Andrew Schwartzmeyer Date: Tue, 18 Jan 2022 15:43:45 -0800 Subject: [PATCH 1/4] Use `enum` in `provideDebugConfigurations` --- src/features/DebugSession.ts | 101 +++++++++++++++++------------------ 1 file changed, 50 insertions(+), 51 deletions(-) diff --git a/src/features/DebugSession.ts b/src/features/DebugSession.ts index bd5bf7d77d..e98bd899c3 100644 --- a/src/features/DebugSession.ts +++ b/src/features/DebugSession.ts @@ -75,29 +75,31 @@ export class DebugSessionFeature extends LanguageClientConsumer folder: WorkspaceFolder | undefined, token?: CancellationToken): Promise { - const launchCurrentFileId = 0; - const launchScriptId = 1; - const interactiveSessionId = 2; - const attachHostProcessId = 3; + enum DebugConfig { + LaunchCurrentFile, + LaunchScript, + InteractiveSession, + AttachHostProcess, + } const debugConfigPickItems = [ { - id: launchCurrentFileId, + id: DebugConfig.LaunchCurrentFile, label: "Launch Current File", description: "Launch and debug the file in the currently active editor window", }, { - id: launchScriptId, + id: DebugConfig.LaunchScript, label: "Launch Script", description: "Launch and debug the specified file or command", }, { - id: interactiveSessionId, + id: DebugConfig.InteractiveSession, label: "Interactive Session", description: "Debug commands executed from the Integrated Console", }, { - id: attachHostProcessId, + id: DebugConfig.AttachHostProcess, label: "Attach", description: "Attach the debugger to a running PowerShell Host Process", }, @@ -108,50 +110,46 @@ export class DebugSessionFeature extends LanguageClientConsumer debugConfigPickItems, { placeHolder: "Select a PowerShell debug configuration" }); - if (launchSelection.id === launchCurrentFileId) { - return [ - { - name: "PowerShell: Launch Current File", - type: "PowerShell", - request: "launch", - script: "${file}", - cwd: "${file}", - }, - ]; - } - - if (launchSelection.id === launchScriptId) { - return [ - { - name: "PowerShell: Launch Script", - type: "PowerShell", - request: "launch", - script: "enter path or command to execute e.g.: ${workspaceFolder}/src/foo.ps1 or Invoke-Pester", - cwd: "${workspaceFolder}", - }, - ]; - } - - if (launchSelection.id === interactiveSessionId) { - return [ - { - name: "PowerShell: Interactive Session", - type: "PowerShell", - request: "launch", - cwd: "", - }, - ]; + switch (launchSelection.id) { + case DebugConfig.LaunchCurrentFile: + return [ + { + name: "PowerShell: Launch Current File", + type: "PowerShell", + request: "launch", + script: "${file}", + cwd: "${file}", + }, + ]; + case DebugConfig.LaunchScript: + return [ + { + name: "PowerShell: Launch Script", + type: "PowerShell", + request: "launch", + script: "enter path or command to execute e.g.: ${workspaceFolder}/src/foo.ps1 or Invoke-Pester", + cwd: "${workspaceFolder}", + }, + ]; + case DebugConfig.InteractiveSession: + return [ + { + name: "PowerShell: Interactive Session", + type: "PowerShell", + request: "launch", + cwd: "", + }, + ]; + case DebugConfig.AttachHostProcess: + return [ + { + name: "PowerShell: Attach to PowerShell Host Process", + type: "PowerShell", + request: "attach", + runspaceId: 1, + }, + ]; } - - // Last remaining possibility is attach to host process - return [ - { - name: "PowerShell: Attach to PowerShell Host Process", - type: "PowerShell", - request: "attach", - runspaceId: 1, - }, - ]; } // DebugConfigurationProvider method @@ -161,6 +159,7 @@ export class DebugSessionFeature extends LanguageClientConsumer token?: CancellationToken): Promise { // Make sure there is a session running before attempting to debug/run a program + // TODO: Perhaps this should just wait until it's running or aborted. if (this.sessionManager.getSessionStatus() !== SessionStatus.Running) { const msg = "Cannot debug or run a PowerShell script until the PowerShell session has started. " + "Wait for the PowerShell session to finish starting and try again."; From 76b1ec39de0a667c61bab633a56d73d7ea9e3dc3 Mon Sep 17 00:00:00 2001 From: Andrew Schwartzmeyer Date: Tue, 18 Jan 2022 16:31:37 -0800 Subject: [PATCH 2/4] Revert default `cwd` to `${file}` --- package.json | 2 +- src/features/DebugSession.ts | 11 +++++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index d7d627f3aa..afd99183cf 100644 --- a/package.json +++ b/package.json @@ -442,7 +442,7 @@ "type": "PowerShell", "request": "launch", "script": "^\"\\${file}\"", - "cwd": "^\"\\${workspaceFolder}\"" + "cwd": "^\"\\${file}\"" } }, { diff --git a/src/features/DebugSession.ts b/src/features/DebugSession.ts index e98bd899c3..32a618e886 100644 --- a/src/features/DebugSession.ts +++ b/src/features/DebugSession.ts @@ -175,10 +175,10 @@ export class DebugSessionFeature extends LanguageClientConsumer const settings = Settings.load(); // If the createTemporaryIntegratedConsole field is not specified in the launch config, set the field using - // the value from the corresponding setting. Otherwise, the launch config value overrides the setting. - if (config.createTemporaryIntegratedConsole === undefined) { - config.createTemporaryIntegratedConsole = settings.debugging.createTemporaryIntegratedConsole; - } + // the value from the corresponding setting. Otherwise, the launch config value overrides the setting. + config.createTemporaryIntegratedConsole = + config.createTemporaryIntegratedConsole ?? + settings.debugging.createTemporaryIntegratedConsole; if (config.request === "attach") { const platformDetails = getPlatformDetails(); @@ -300,6 +300,9 @@ export class DebugSessionFeature extends LanguageClientConsumer } } + // NOTE: There is a tight coupling to a weird setting in + // `package.json` for the Launch Current File configuration where + // the default cwd is set to ${file}. if ((currentDocument !== undefined) && (config.cwd === "${file}")) { config.cwd = currentDocument.fileName; } From 6e9ac341ee81e52148472c12a47e6abb0cae7140 Mon Sep 17 00:00:00 2001 From: Andrew Schwartzmeyer Date: Thu, 20 Jan 2022 18:06:27 -0800 Subject: [PATCH 3/4] Automatically fix `Thenable` to `Promise` in `DebugSession.ts` --- src/features/DebugSession.ts | 157 ++++++++++++++++------------------- 1 file changed, 72 insertions(+), 85 deletions(-) diff --git a/src/features/DebugSession.ts b/src/features/DebugSession.ts index 32a618e886..ce1951802f 100644 --- a/src/features/DebugSession.ts +++ b/src/features/DebugSession.ts @@ -35,7 +35,10 @@ export class DebugSessionFeature extends LanguageClientConsumer context.subscriptions.push(vscode.debug.registerDebugAdapterDescriptorFactory("PowerShell", this)) } - createDebugAdapterDescriptor(session: vscode.DebugSession, executable: vscode.DebugAdapterExecutable): vscode.ProviderResult { + createDebugAdapterDescriptor( + session: vscode.DebugSession, + _executable: vscode.DebugAdapterExecutable): vscode.ProviderResult { + const sessionDetails = session.configuration.createTemporaryIntegratedConsole ? this.tempSessionDetails : this.sessionManager.getSessionDetails(); @@ -58,10 +61,11 @@ export class DebugSessionFeature extends LanguageClientConsumer languageClient.onNotification( StartDebuggerNotificationType, () => + // TODO: Use a named debug configuration. vscode.debug.startDebugging(undefined, { request: "launch", type: "PowerShell", - name: "PowerShell Interactive Session", + name: "PowerShell: Interactive Session", })); languageClient.onNotification( @@ -110,6 +114,7 @@ export class DebugSessionFeature extends LanguageClientConsumer debugConfigPickItems, { placeHolder: "Select a PowerShell debug configuration" }); + // TODO: Make these available in a dictionary and share them. switch (launchSelection.id) { case DebugConfig.LaunchCurrentFile: return [ @@ -154,10 +159,9 @@ export class DebugSessionFeature extends LanguageClientConsumer // DebugConfigurationProvider method public async resolveDebugConfiguration( - folder: WorkspaceFolder | undefined, + _folder: WorkspaceFolder | undefined, config: DebugConfiguration, - token?: CancellationToken): Promise { - + _token?: CancellationToken): Promise { // Make sure there is a session running before attempting to debug/run a program // TODO: Perhaps this should just wait until it's running or aborted. if (this.sessionManager.getSessionStatus() !== SessionStatus.Running) { @@ -213,10 +217,11 @@ export class DebugSessionFeature extends LanguageClientConsumer } } + // TODO: Use a named debug configuration. if (generateLaunchConfig) { // No launch.json, create the default configuration for both unsaved (Untitled) and saved documents. config.type = "PowerShell"; - config.name = "PowerShell Launch Current File"; + config.name = "PowerShell: Launch Current File"; config.request = "launch"; config.args = []; @@ -240,7 +245,6 @@ export class DebugSessionFeature extends LanguageClientConsumer } if (config.request === "launch") { - // For debug launch of "current script" (saved or unsaved), warn before starting the debugger if either // A) there is not an active document // B) the unsaved document's language type is not PowerShell @@ -355,7 +359,7 @@ export class SpecifyScriptArgsFeature implements vscode.Disposable { this.command.dispose(); } - private specifyScriptArguments(): Thenable { + private async specifyScriptArguments(): Promise { const powerShellDbgScriptArgsKey = "powerShellDebugScriptArgs"; const options: vscode.InputBoxOptions = { @@ -368,15 +372,13 @@ export class SpecifyScriptArgsFeature implements vscode.Disposable { options.value = prevArgs; } - return vscode.window.showInputBox(options).then((text) => { - // When user cancel's the input box (by pressing Esc), the text value is undefined. - // Let's not blow away the previous settting. - if (text !== undefined) { - this.context.workspaceState.update(powerShellDbgScriptArgsKey, text); - } - - return text; - }); + const text = await vscode.window.showInputBox(options); + // When user cancel's the input box (by pressing Esc), the text value is undefined. + // Let's not blow away the previous settting. + if (text !== undefined) { + this.context.workspaceState.update(powerShellDbgScriptArgsKey, text); + } + return text; } } @@ -402,7 +404,7 @@ export class PickPSHostProcessFeature extends LanguageClientConsumer { private command: vscode.Disposable; private waitingForClientToken: vscode.CancellationTokenSource; - private getLanguageClientResolve: (value?: LanguageClient | Thenable) => void; + private getLanguageClientResolve: (value?: LanguageClient | Promise) => void; constructor() { super(); @@ -427,7 +429,7 @@ export class PickPSHostProcessFeature extends LanguageClientConsumer { this.command.dispose(); } - private getLanguageClient(): Thenable { + private getLanguageClient(): Promise { if (this.languageClient) { return Promise.resolve(this.languageClient); } else { @@ -466,46 +468,38 @@ export class PickPSHostProcessFeature extends LanguageClientConsumer { } } - private pickPSHostProcess(): Thenable { - return this.languageClient.sendRequest(GetPSHostProcessesRequestType, {}).then((hostProcesses) => { - // Start with the current PowerShell process in the list. - const items: IProcessItem[] = [{ - label: "Current", - description: "The current PowerShell Integrated Console process.", - pid: "current", - }]; - - for (const p in hostProcesses) { - if (hostProcesses.hasOwnProperty(p)) { - let windowTitle = ""; - if (hostProcesses[p].mainWindowTitle) { - windowTitle = `, Title: ${hostProcesses[p].mainWindowTitle}`; - } - - items.push({ - label: hostProcesses[p].processName, - description: `PID: ${hostProcesses[p].processId.toString()}${windowTitle}`, - pid: hostProcesses[p].processId, - }); + private async pickPSHostProcess(): Promise { + const hostProcesses = await this.languageClient.sendRequest(GetPSHostProcessesRequestType, {}); + // Start with the current PowerShell process in the list. + const items: IProcessItem[] = [{ + label: "Current", + description: "The current PowerShell Integrated Console process.", + pid: "current", + }]; + for (const p in hostProcesses) { + if (hostProcesses.hasOwnProperty(p)) { + let windowTitle = ""; + if (hostProcesses[p].mainWindowTitle) { + windowTitle = `, Title: ${hostProcesses[p].mainWindowTitle}`; } - } - if (items.length === 0) { - return Promise.reject("There are no PowerShell host processes to attach to."); + items.push({ + label: hostProcesses[p].processName, + description: `PID: ${hostProcesses[p].processId.toString()}${windowTitle}`, + pid: hostProcesses[p].processId, + }); } - - const options: vscode.QuickPickOptions = { - placeHolder: "Select a PowerShell host process to attach to", - 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.pid}` : undefined; - }); - }); + } + if (items.length === 0) { + return Promise.reject("There are no PowerShell host processes to attach to."); + } + const options: vscode.QuickPickOptions = { + placeHolder: "Select a PowerShell host process to attach to", + matchOnDescription: true, + matchOnDetail: true, + }; + const item = await vscode.window.showQuickPick(items, options); + return item ? `${item.pid}` : undefined; } private clearWaitingToken() { @@ -533,7 +527,7 @@ export class PickRunspaceFeature extends LanguageClientConsumer { private command: vscode.Disposable; private waitingForClientToken: vscode.CancellationTokenSource; - private getLanguageClientResolve: (value?: LanguageClient | Thenable) => void; + private getLanguageClientResolve: (value?: LanguageClient | Promise) => void; constructor() { super(); @@ -557,7 +551,7 @@ export class PickRunspaceFeature extends LanguageClientConsumer { this.command.dispose(); } - private getLanguageClient(): Thenable { + private getLanguageClient(): Promise { if (this.languageClient) { return Promise.resolve(this.languageClient); } else { @@ -596,36 +590,29 @@ export class PickRunspaceFeature extends LanguageClientConsumer { } } - 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") - && processId === "current") { - continue; - } - - items.push({ - label: runspace.name, - description: `ID: ${runspace.id} - ${runspace.availability}`, - id: runspace.id.toString(), - }); + private async pickRunspace(processId: string): Promise { + const response = await this.languageClient.sendRequest(GetRunspaceRequestType, { processId }); + const items: IRunspaceItem[] = []; + for (const runspace of response) { + // Skip default runspace + if ((runspace.id === 1 || runspace.name === "PSAttachRunspace") + && processId === "current") { + continue; } - 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; + 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, + }; + const item = await vscode.window.showQuickPick(items, options); + return item ? `${item.id}` : undefined; } private clearWaitingToken() { From 4d8bc56fb66ac3bd6dc9cb7a711f9f62617a3e36 Mon Sep 17 00:00:00 2001 From: Andrew Schwartzmeyer Date: Fri, 21 Jan 2022 16:09:44 -0800 Subject: [PATCH 4/4] Fix `PowerShell.Debug.Start` to just launch current file This command previously used a private API `workbench.action.debug.start` which led to bad behavior. Namely it meant that while a PowerShell file was opened, if the triangular "Start" button was pressed, it would start Code's currently selected launch configuration which is often not what the user intended. 1. If there was no `launch.json` this worked accidentally in that we resolved a default configuration to launch the current file. 2. If a working PowerShell configuration was selected, it may work as intended, unless that configuration was to attach. 3. If any other configuration was selected, the user would be left bewildered as to why, say, a Python debugger was started for a PowerShell file. Instead we call the public API to start the debugger and give it a copy of our "Launch Current File" configuration, which is what the user intended do when clicking the "Start" button on a PowerShell file. This may introduce some breaking behavior if the user was relying on this button to start their current correctly configured (and selected) launch configuration with possible extra customizations. However, in that the case the user can use Code's built-in options to call the private API we were calling preivously, namely F5 or the triangular start button in the debugger workbench (instead of our button). --- src/features/ExtensionCommands.ts | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/features/ExtensionCommands.ts b/src/features/ExtensionCommands.ts index 0c00e72771..fc23bf9cdc 100644 --- a/src/features/ExtensionCommands.ts +++ b/src/features/ExtensionCommands.ts @@ -215,16 +215,25 @@ export class ExtensionCommandsFeature extends LanguageClientConsumer { }); this.command3 = vscode.commands.registerCommand('PowerShell.ClosePanel', - async () => { await vscode.commands.executeCommand('workbench.action.closePanel'); }), + () => { vscode.commands.executeCommand('workbench.action.closePanel'); }), this.command4 = vscode.commands.registerCommand('PowerShell.PositionPanelLeft', - async () => { await vscode.commands.executeCommand('workbench.action.positionPanelLeft'); }), + () => { vscode.commands.executeCommand('workbench.action.positionPanelLeft'); }), this.command5 = vscode.commands.registerCommand('PowerShell.PositionPanelBottom', - async () => { await vscode.commands.executeCommand('workbench.action.positionPanelBottom'); }), + () => { vscode.commands.executeCommand('workbench.action.positionPanelBottom'); }), this.command6 = vscode.commands.registerCommand('PowerShell.Debug.Start', - async () => { await vscode.commands.executeCommand('workbench.action.debug.start'); }) + () => { + // TODO: Use a named debug configuration. + vscode.debug.startDebugging(undefined, { + name: "PowerShell: Launch Current File", + type: "PowerShell", + request: "launch", + script: "${file}", + cwd: "${file}", + }) + }) } public setLanguageClient(languageclient: LanguageClient) {