Skip to content

Commit 949fa79

Browse files
Refactor the debug launch configuration resolvers (#4172)
Some much needed TLC, especially now that VS Code with resolve its predefined variables used in configurations. Do not set `cwd` for debug launch configurations Instead, respect it if it exists, and otherwise consistently do not change it. Debugging will run in the session's current `cwd`, or if and only if the user set `cwd` on the launch config will it change.
1 parent 3645b04 commit 949fa79

File tree

7 files changed

+111
-161
lines changed

7 files changed

+111
-161
lines changed

Diff for: package.json

+3-3
Original file line numberDiff line numberDiff line change
@@ -442,7 +442,7 @@
442442
"type": "PowerShell",
443443
"request": "launch",
444444
"script": "^\"\\${file}\"",
445-
"cwd": "^\"\\${file}\""
445+
"cwd": "^\"\\${cwd}\""
446446
}
447447
},
448448
{
@@ -453,7 +453,7 @@
453453
"type": "PowerShell",
454454
"request": "launch",
455455
"script": "^\"enter path or command to execute e.g.: \\${workspaceFolder}/src/foo.ps1 or Invoke-Pester\"",
456-
"cwd": "^\"\\${workspaceFolder}\""
456+
"cwd": "^\"\\${cwd}\""
457457
}
458458
},
459459
{
@@ -463,7 +463,7 @@
463463
"name": "PowerShell Interactive Session",
464464
"type": "PowerShell",
465465
"request": "launch",
466-
"cwd": ""
466+
"cwd": "^\"\\${cwd}\""
467467
}
468468
},
469469
{

Diff for: src/features/DebugSession.ts

+99-136
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { IEditorServicesSessionDetails, SessionManager, SessionStatus } from "..
1414
import Settings = require("../settings");
1515
import { Logger } from "../logging";
1616
import { LanguageClientConsumer } from "../languageClientConsumer";
17+
import path = require("path");
1718

1819
export const StartDebuggerNotificationType =
1920
new NotificationType<void>("powerShell/startDebugger");
@@ -121,7 +122,6 @@ export class DebugSessionFeature extends LanguageClientConsumer
121122
type: "PowerShell",
122123
request: "launch",
123124
script: "${file}",
124-
cwd: "${file}",
125125
},
126126
];
127127
case DebugConfig.LaunchScript:
@@ -130,8 +130,7 @@ export class DebugSessionFeature extends LanguageClientConsumer
130130
name: "PowerShell: Launch Script",
131131
type: "PowerShell",
132132
request: "launch",
133-
script: "enter path or command to execute e.g.: ${workspaceFolder}/src/foo.ps1 or Invoke-Pester",
134-
cwd: "${workspaceFolder}",
133+
script: "Enter path or command to execute, for example: \"${workspaceFolder}/src/foo.ps1\" or \"Invoke-Pester\"",
135134
},
136135
];
137136
case DebugConfig.InteractiveSession:
@@ -140,7 +139,6 @@ export class DebugSessionFeature extends LanguageClientConsumer
140139
name: "PowerShell: Interactive Session",
141140
type: "PowerShell",
142141
request: "launch",
143-
cwd: "",
144142
},
145143
];
146144
case DebugConfig.AttachHostProcess:
@@ -155,168 +153,133 @@ export class DebugSessionFeature extends LanguageClientConsumer
155153
}
156154
}
157155

158-
// DebugConfigurationProvider method
156+
// DebugConfigurationProvider methods
159157
public async resolveDebugConfiguration(
160158
_folder: WorkspaceFolder | undefined,
161159
config: DebugConfiguration,
162160
_token?: CancellationToken): Promise<DebugConfiguration> {
163161

164-
if (this.sessionManager.getSessionStatus() !== SessionStatus.Running) {
165-
await this.sessionManager.start();
166-
}
162+
// Prevent the Debug Console from opening
163+
config.internalConsoleOptions = "neverOpen";
167164

168-
// Starting a debug session can be done when there is no document open e.g. attach to PS host process
169-
const currentDocument = vscode.window.activeTextEditor ? vscode.window.activeTextEditor.document : undefined;
170-
const debugCurrentScript = (config.script === "${file}") || !config.request;
171-
const generateLaunchConfig = !config.request;
165+
// NOTE: We intentionally do not touch the `cwd` setting of the config.
172166

167+
// If the createTemporaryIntegratedConsole field is not specified in the
168+
// launch config, set the field using the value from the corresponding
169+
// setting. Otherwise, the launch config value overrides the setting.
170+
//
171+
// Also start the temporary process and console for this configuration.
173172
const settings = Settings.load();
174-
175-
// If the createTemporaryIntegratedConsole field is not specified in the launch config, set the field using
176-
// the value from the corresponding setting. Otherwise, the launch config value overrides the setting.
177173
config.createTemporaryIntegratedConsole =
178174
config.createTemporaryIntegratedConsole ??
179175
settings.debugging.createTemporaryIntegratedConsole;
180176

181-
if (config.request === "attach") {
182-
const platformDetails = getPlatformDetails();
183-
const versionDetails = this.sessionManager.getPowerShellVersionDetails();
184-
185-
// Cross-platform attach to process was added in 6.2.0-preview.4
186-
if (versionDetails.version < "6.2.0" && platformDetails.operatingSystem !== OperatingSystem.Windows) {
187-
const msg = `Attaching to a PowerShell Host Process on ${
188-
OperatingSystem[platformDetails.operatingSystem] } requires PowerShell 6.2 or higher.`;
189-
return vscode.window.showErrorMessage(msg).then((_) => {
190-
return undefined;
191-
});
192-
}
193-
194-
// if nothing is set, prompt for the processId
195-
if (!config.customPipeName && !config.processId) {
196-
config.processId = await vscode.commands.executeCommand("PowerShell.PickPSHostProcess");
197-
198-
// No process selected. Cancel attach.
199-
if (!config.processId) {
200-
return null;
201-
}
202-
}
203-
204-
if (!config.runspaceId && !config.runspaceName) {
205-
config.runspaceId = await vscode.commands.executeCommand("PowerShell.PickRunspace", config.processId);
206-
207-
// No runspace selected. Cancel attach.
208-
if (!config.runspaceId) {
209-
return null;
210-
}
211-
}
177+
if (config.createTemporaryIntegratedConsole) {
178+
this.tempDebugProcess = this.sessionManager.createDebugSessionProcess(settings);
179+
this.tempSessionDetails = await this.tempDebugProcess.start(`DebugSession-${this.sessionCount++}`);
212180
}
213181

214-
// TODO: Use a named debug configuration.
215-
if (generateLaunchConfig) {
216-
// No launch.json, create the default configuration for both unsaved (Untitled) and saved documents.
182+
if (!config.request) {
183+
// No launch.json, create the default configuration for both unsaved
184+
// (Untitled) and saved documents.
185+
config.current_document = true;
217186
config.type = "PowerShell";
218187
config.name = "PowerShell: Launch Current File";
219188
config.request = "launch";
220189
config.args = [];
190+
config.script = "${file}"
191+
}
221192

222-
config.script =
223-
currentDocument.isUntitled
224-
? currentDocument.uri.toString()
225-
: currentDocument.fileName;
226-
227-
if (config.createTemporaryIntegratedConsole) {
228-
// For a folder-less workspace, vscode.workspace.rootPath will be undefined.
229-
// PSES will convert that undefined to a reasonable working dir.
230-
config.cwd =
231-
currentDocument.isUntitled
232-
? vscode.workspace.rootPath
233-
: currentDocument.fileName;
234-
235-
} else {
236-
// If the non-temp Extension Terminal is being used, default to the current working dir.
237-
config.cwd = "";
193+
if (config.script === "${file}" || config.script === "${relativeFile}") {
194+
if (vscode.window.activeTextEditor === undefined) {
195+
vscode.window.showErrorMessage("To debug the 'Current File', you must first open a PowerShell script file in the editor.");
196+
return undefined;
197+
}
198+
config.current_document = true;
199+
// Special case using the URI for untitled documents.
200+
const currentDocument = vscode.window.activeTextEditor.document;
201+
if (currentDocument.isUntitled) {
202+
config.untitled_document = true;
203+
config.script = currentDocument.uri.toString();
238204
}
239205
}
240206

241-
if (config.request === "launch") {
242-
// For debug launch of "current script" (saved or unsaved), warn before starting the debugger if either
243-
// A) there is not an active document
244-
// B) the unsaved document's language type is not PowerShell
245-
// C) the saved document's extension is a type that PowerShell can't debug.
246-
if (debugCurrentScript) {
247-
248-
if (currentDocument === undefined) {
249-
const msg = "To debug the \"Current File\", you must first open a " +
250-
"PowerShell script file in the editor.";
251-
vscode.window.showErrorMessage(msg);
252-
return;
253-
}
254-
255-
if (currentDocument.isUntitled) {
256-
if (config.createTemporaryIntegratedConsole) {
257-
const msg = "Debugging Untitled files in a temporary console is currently not supported.";
258-
vscode.window.showErrorMessage(msg);
259-
return;
260-
}
261-
262-
if (currentDocument.languageId === "powershell") {
263-
if (!generateLaunchConfig) {
264-
// Cover the case of existing launch.json but unsaved (Untitled) document.
265-
// In this case, vscode.workspace.rootPath will not be undefined.
266-
config.script = currentDocument.uri.toString();
267-
config.cwd = vscode.workspace.rootPath;
268-
}
269-
} else {
270-
const msg = "To debug '" + currentDocument.fileName + "', change the document's " +
271-
"language mode to PowerShell or save the file with a PowerShell extension.";
272-
vscode.window.showErrorMessage(msg);
273-
return;
274-
}
275-
} else {
276-
let isValidExtension = false;
277-
const extIndex = currentDocument.fileName.lastIndexOf(".");
278-
if (extIndex !== -1) {
279-
const ext = currentDocument.fileName.substr(extIndex + 1).toUpperCase();
280-
isValidExtension = (ext === "PS1" || ext === "PSM1");
281-
}
282-
283-
if ((currentDocument.languageId !== "powershell") || !isValidExtension) {
284-
let docPath = currentDocument.fileName;
285-
const workspaceRootPath = vscode.workspace.rootPath;
286-
if (currentDocument.fileName.startsWith(workspaceRootPath)) {
287-
docPath = currentDocument.fileName.substring(vscode.workspace.rootPath.length + 1);
288-
}
289-
290-
const msg = "PowerShell does not support debugging this file type: '" + docPath + "'.";
291-
vscode.window.showErrorMessage(msg);
292-
return;
293-
}
207+
return config;
208+
}
294209

295-
if (config.script === "${file}") {
296-
config.script = currentDocument.fileName;
297-
}
298-
}
299-
}
210+
public async resolveDebugConfigurationWithSubstitutedVariables(
211+
_folder: WorkspaceFolder | undefined,
212+
config: DebugConfiguration,
213+
_token?: CancellationToken): Promise<DebugConfiguration> {
300214

301-
// NOTE: There is a tight coupling to a weird setting in
302-
// `package.json` for the Launch Current File configuration where
303-
// the default cwd is set to ${file}.
304-
if ((currentDocument !== undefined) && (config.cwd === "${file}")) {
305-
config.cwd = currentDocument.fileName;
306-
}
215+
if (config.request === "attach") {
216+
config = await this.resolveAttachDebugConfiguration(config);
217+
} else if (config.request === "launch") {
218+
config = await this.resolveLaunchDebugConfiguration(config);
219+
} else {
220+
vscode.window.showErrorMessage(`The request type was invalid: '${config.request}'`);
221+
return null;
307222
}
308223

309-
// Prevent the Debug Console from opening
310-
config.internalConsoleOptions = "neverOpen";
311-
312-
// Create or show the interactive console
224+
// Start the PowerShell session if needed.
225+
if (this.sessionManager.getSessionStatus() !== SessionStatus.Running) {
226+
await this.sessionManager.start();
227+
}
228+
// Create or show the Extension Terminal.
313229
vscode.commands.executeCommand("PowerShell.ShowSessionConsole", true);
314230

315-
if (config.createTemporaryIntegratedConsole) {
316-
this.tempDebugProcess = this.sessionManager.createDebugSessionProcess(settings);
317-
this.tempSessionDetails = await this.tempDebugProcess.start(`DebugSession-${this.sessionCount++}`);
231+
return config;
232+
}
233+
234+
private async resolveLaunchDebugConfiguration(config: DebugConfiguration): Promise<DebugConfiguration> {
235+
// Check the languageId only for current documents (which includes untitled documents).
236+
if (config.current_document) {
237+
const currentDocument = vscode.window.activeTextEditor.document;
238+
if (currentDocument.languageId !== "powershell") {
239+
vscode.window.showErrorMessage("Please change the current document's language mode to PowerShell.");
240+
return undefined;
241+
}
242+
}
243+
// Check the temporary console setting for untitled documents only, and
244+
// check the document extension for everything else.
245+
if (config.untitled_document) {
246+
if (config.createTemporaryIntegratedConsole) {
247+
vscode.window.showErrorMessage("Debugging untitled files in a temporary console is not supported.");
248+
return undefined;
249+
}
250+
} else {
251+
const ext = path.extname(config.script).toLowerCase();
252+
if (!(ext === ".ps1" || ext === ".psm1")) {
253+
vscode.window.showErrorMessage(`PowerShell does not support debugging this file type: '${path.basename(config.script)}'`);
254+
return undefined;
255+
}
318256
}
257+
return config;
258+
}
319259

260+
private async resolveAttachDebugConfiguration(config: DebugConfiguration): Promise<DebugConfiguration> {
261+
const platformDetails = getPlatformDetails();
262+
const versionDetails = this.sessionManager.getPowerShellVersionDetails();
263+
// Cross-platform attach to process was added in 6.2.0-preview.4.
264+
if (versionDetails.version < "7.0.0" && platformDetails.operatingSystem !== OperatingSystem.Windows) {
265+
vscode.window.showErrorMessage(`Attaching to a PowerShell Host Process on ${OperatingSystem[platformDetails.operatingSystem]} requires PowerShell 7.0 or higher.`);
266+
return undefined;
267+
}
268+
// If nothing is set, prompt for the processId.
269+
if (!config.customPipeName && !config.processId) {
270+
config.processId = await vscode.commands.executeCommand("PowerShell.PickPSHostProcess");
271+
// No process selected. Cancel attach.
272+
if (!config.processId) {
273+
return null;
274+
}
275+
}
276+
if (!config.runspaceId && !config.runspaceName) {
277+
config.runspaceId = await vscode.commands.executeCommand("PowerShell.PickRunspace", config.processId);
278+
// No runspace selected. Cancel attach.
279+
if (!config.runspaceId) {
280+
return null;
281+
}
282+
}
320283
return config;
321284
}
322285
}

Diff for: src/features/ExtensionCommands.ts

-1
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,6 @@ export class ExtensionCommandsFeature extends LanguageClientConsumer {
222222
type: "PowerShell",
223223
request: "launch",
224224
script: "${file}",
225-
cwd: "${file}",
226225
})
227226
})
228227
]

Diff for: src/features/PesterTests.ts

+4-6
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33

44
import * as path from "path";
55
import vscode = require("vscode");
6-
import { SessionManager } from "../session";
76
import Settings = require("../settings");
87
import utils = require("../utils");
98

@@ -17,7 +16,7 @@ export class PesterTestsFeature implements vscode.Disposable {
1716
private command: vscode.Disposable;
1817
private invokePesterStubScriptPath: string;
1918

20-
constructor(private sessionManager: SessionManager) {
19+
constructor() {
2120
this.invokePesterStubScriptPath = path.resolve(__dirname, "../modules/PowerShellEditorServices/InvokePesterStub.ps1");
2221

2322
// File context-menu command - Run Pester Tests
@@ -70,10 +69,9 @@ export class PesterTestsFeature implements vscode.Disposable {
7069
launchType: LaunchType,
7170
testName?: string,
7271
lineNum?: number,
73-
outputPath?: string) {
72+
outputPath?: string): vscode.DebugConfiguration {
7473

7574
const uri = vscode.Uri.parse(uriString);
76-
const currentDocument = vscode.window.activeTextEditor.document;
7775
const settings = Settings.load();
7876

7977
// Since we pass the script path to PSES in single quotes to avoid issues with PowerShell
@@ -83,7 +81,7 @@ export class PesterTestsFeature implements vscode.Disposable {
8381
const launchConfig = {
8482
request: "launch",
8583
type: "PowerShell",
86-
name: "PowerShell Launch Pester Tests",
84+
name: "PowerShell: Launch Pester Tests",
8785
script: this.invokePesterStubScriptPath,
8886
args: [
8987
"-ScriptPath",
@@ -125,7 +123,7 @@ export class PesterTestsFeature implements vscode.Disposable {
125123
return launchConfig;
126124
}
127125

128-
private async launch(launchConfig): Promise<boolean> {
126+
private async launch(launchConfig: vscode.DebugConfiguration): Promise<boolean> {
129127
// Create or show the interactive console
130128
// TODO: #367 Check if "newSession" mode is configured
131129
await vscode.commands.executeCommand("PowerShell.ShowSessionConsole", true);

0 commit comments

Comments
 (0)