Skip to content

Commit 46e0e77

Browse files
committed
Fully support multi-root workspaces
1 parent d4f2164 commit 46e0e77

File tree

6 files changed

+39
-63
lines changed

6 files changed

+39
-63
lines changed

src/features/ExtensionCommands.ts

+19-49
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {
1010
} from "vscode-languageclient";
1111
import { LanguageClient } from "vscode-languageclient/node";
1212
import { Logger } from "../logging";
13-
import { getSettings } from "../settings";
13+
import { getSettings, validateCwdSetting } from "../settings";
1414
import { LanguageClientConsumer } from "../languageClientConsumer";
1515

1616
export interface IExtensionCommand {
@@ -368,32 +368,20 @@ export class ExtensionCommandsFeature extends LanguageClientConsumer {
368368
return EditorOperationResponse.Completed;
369369
}
370370

371-
private openFile(openFileDetails: IOpenFileDetails): Thenable<EditorOperationResponse> {
372-
const filePath = this.normalizeFilePath(openFileDetails.filePath);
373-
374-
const promise =
375-
vscode.workspace.openTextDocument(filePath)
376-
.then((doc) => vscode.window.showTextDocument(
377-
doc,
378-
{ preview: openFileDetails.preview }))
379-
.then((_) => EditorOperationResponse.Completed);
380-
381-
return promise;
371+
private async openFile(openFileDetails: IOpenFileDetails): Promise<EditorOperationResponse> {
372+
const filePath = await this.normalizeFilePath(openFileDetails.filePath);
373+
const doc = await vscode.workspace.openTextDocument(filePath);
374+
await vscode.window.showTextDocument(doc, { preview: openFileDetails.preview });
375+
return EditorOperationResponse.Completed;
382376
}
383377

384-
private closeFile(filePath: string): Thenable<EditorOperationResponse> {
385-
let promise: Thenable<EditorOperationResponse>;
386-
if (this.findTextDocument(this.normalizeFilePath(filePath))) {
387-
promise =
388-
vscode.workspace.openTextDocument(filePath)
389-
.then((doc) => vscode.window.showTextDocument(doc))
390-
.then((_) => vscode.commands.executeCommand("workbench.action.closeActiveEditor"))
391-
.then((_) => EditorOperationResponse.Completed);
392-
} else {
393-
promise = Promise.resolve(EditorOperationResponse.Completed);
378+
private async closeFile(filePath: string): Promise<EditorOperationResponse> {
379+
if (this.findTextDocument(await this.normalizeFilePath(filePath))) {
380+
const doc = await vscode.workspace.openTextDocument(filePath);
381+
await vscode.window.showTextDocument(doc);
382+
await vscode.commands.executeCommand("workbench.action.closeActiveEditor");
394383
}
395-
396-
return promise;
384+
return EditorOperationResponse.Completed;
397385
}
398386

399387
/**
@@ -413,7 +401,7 @@ export class ExtensionCommandsFeature extends LanguageClientConsumer {
413401
switch (currentFileUri.scheme) {
414402
case "file": {
415403
// If the file to save can't be found, just complete the request
416-
if (!this.findTextDocument(this.normalizeFilePath(currentFileUri.fsPath))) {
404+
if (!this.findTextDocument(await this.normalizeFilePath(currentFileUri.fsPath))) {
417405
void this.logger.writeAndShowError(`File to save not found: ${currentFileUri.fsPath}.`);
418406
return EditorOperationResponse.Completed;
419407
}
@@ -449,23 +437,8 @@ export class ExtensionCommandsFeature extends LanguageClientConsumer {
449437
if (path.isAbsolute(saveFileDetails.newPath)) {
450438
newFileAbsolutePath = saveFileDetails.newPath;
451439
} else {
452-
// In fresh contexts, workspaceFolders is not defined...
453-
if (!vscode.workspace.workspaceFolders || vscode.workspace.workspaceFolders.length === 0) {
454-
void this.logger.writeAndShowWarning("Cannot save file to relative path: no workspaces are open. " +
455-
"Try saving to an absolute path, or open a workspace.");
456-
return EditorOperationResponse.Completed;
457-
}
458-
459-
// If not, interpret the path as relative to the workspace root
460-
const workspaceRootUri = vscode.workspace.workspaceFolders[0].uri;
461-
// We don't support saving to a non-file URI-schemed workspace
462-
if (workspaceRootUri.scheme !== "file") {
463-
void this.logger.writeAndShowWarning(
464-
"Cannot save untitled file to a relative path in an untitled workspace. " +
465-
"Try saving to an absolute path or opening a workspace folder.");
466-
return EditorOperationResponse.Completed;
467-
}
468-
newFileAbsolutePath = path.join(workspaceRootUri.fsPath, saveFileDetails.newPath);
440+
const cwd = await validateCwdSetting(this.logger);
441+
newFileAbsolutePath = path.join(cwd, saveFileDetails.newPath);
469442
}
470443
break; }
471444

@@ -511,24 +484,21 @@ export class ExtensionCommandsFeature extends LanguageClientConsumer {
511484
await vscode.window.showTextDocument(newFile, { preview: true });
512485
}
513486

514-
private normalizeFilePath(filePath: string): string {
487+
private async normalizeFilePath(filePath: string): Promise<string> {
488+
const cwd = await validateCwdSetting(this.logger);
515489
const platform = os.platform();
516490
if (platform === "win32") {
517491
// Make sure the file path is absolute
518492
if (!path.win32.isAbsolute(filePath)) {
519-
filePath = path.win32.resolve(
520-
vscode.workspace.rootPath!,
521-
filePath);
493+
filePath = path.win32.resolve(cwd, filePath);
522494
}
523495

524496
// Normalize file path case for comparison for Windows
525497
return filePath.toLowerCase();
526498
} else {
527499
// Make sure the file path is absolute
528500
if (!path.isAbsolute(filePath)) {
529-
filePath = path.resolve(
530-
vscode.workspace.rootPath!,
531-
filePath);
501+
filePath = path.resolve(cwd, filePath);
532502
}
533503

534504
// macOS is case-insensitive

src/features/PesterTests.ts

+5-5
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@
33

44
import * as path from "path";
55
import vscode = require("vscode");
6+
import { Logger } from "../logging";
67
import { SessionManager } from "../session";
7-
import { getSettings } from "../settings";
8+
import { getSettings, chosenWorkspace, validateCwdSetting } from "../settings";
89
import utils = require("../utils");
910

1011
enum LaunchType {
@@ -16,7 +17,7 @@ export class PesterTestsFeature implements vscode.Disposable {
1617
private commands: vscode.Disposable[];
1718
private invokePesterStubScriptPath: string;
1819

19-
constructor(private sessionManager: SessionManager) {
20+
constructor(private sessionManager: SessionManager, private logger: Logger) {
2021
this.invokePesterStubScriptPath = path.resolve(__dirname, "../modules/PowerShellEditorServices/InvokePesterStub.ps1");
2122
this.commands = [
2223
// File context-menu command - Run Pester Tests
@@ -129,11 +130,10 @@ export class PesterTestsFeature implements vscode.Disposable {
129130
// TODO: #367 Check if "newSession" mode is configured
130131
this.sessionManager.showDebugTerminal(true);
131132

132-
// TODO: Update to handle multiple root workspaces.
133-
//
134133
// Ensure the necessary script exists (for testing). The debugger will
135134
// start regardless, but we also pass its success along.
135+
await validateCwdSetting(this.logger);
136136
return await utils.checkIfFileExists(this.invokePesterStubScriptPath)
137-
&& vscode.debug.startDebugging(vscode.workspace.workspaceFolders?.[0], launchConfig);
137+
&& vscode.debug.startDebugging(chosenWorkspace, launchConfig);
138138
}
139139
}

src/features/RunCode.ts

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

44
import vscode = require("vscode");
55
import { SessionManager } from "../session";
6-
import { getSettings } from "../settings";
6+
import { Logger } from "../logging";
7+
import { getSettings, chosenWorkspace, validateCwdSetting } from "../settings";
78

89
enum LaunchType {
910
Debug,
@@ -13,7 +14,7 @@ enum LaunchType {
1314
export class RunCodeFeature implements vscode.Disposable {
1415
private command: vscode.Disposable;
1516

16-
constructor(private sessionManager: SessionManager) {
17+
constructor(private sessionManager: SessionManager, private logger: Logger) {
1718
this.command = vscode.commands.registerCommand(
1819
"PowerShell.RunCode",
1920
async (runInDebugger: boolean, scriptToRun: string, args: string[]) => {
@@ -40,8 +41,8 @@ export class RunCodeFeature implements vscode.Disposable {
4041
// TODO: #367: Check if "newSession" mode is configured
4142
this.sessionManager.showDebugTerminal(true);
4243

43-
// TODO: Update to handle multiple root workspaces.
44-
await vscode.debug.startDebugging(vscode.workspace.workspaceFolders?.[0], launchConfig);
44+
await validateCwdSetting(this.logger);
45+
await vscode.debug.startDebugging(chosenWorkspace, launchConfig);
4546
}
4647
}
4748

src/main.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -145,8 +145,8 @@ export async function activate(context: vscode.ExtensionContext): Promise<IPower
145145
new GenerateBugReportFeature(sessionManager),
146146
new ISECompatibilityFeature(),
147147
new OpenInISEFeature(),
148-
new PesterTestsFeature(sessionManager),
149-
new RunCodeFeature(sessionManager),
148+
new PesterTestsFeature(sessionManager, logger),
149+
new RunCodeFeature(sessionManager, logger),
150150
new CodeActionsFeature(logger),
151151
new SpecifyScriptArgsFeature(context),
152152
];

src/settings.ts

+5-3
Original file line numberDiff line numberDiff line change
@@ -207,9 +207,10 @@ export async function changeSetting(
207207

208208
// We don't want to query the user more than once, so this is idempotent.
209209
let hasPrompted = false;
210+
export let chosenWorkspace: vscode.WorkspaceFolder | undefined = undefined;
210211

211212
export async function validateCwdSetting(logger: Logger): Promise<string> {
212-
let cwd = vscode.workspace.getConfiguration(utils.PowerShellLanguageId).get<string | undefined>("cwd");
213+
let cwd: string | undefined = vscode.workspace.getConfiguration(utils.PowerShellLanguageId).get<string>("cwd");
213214

214215
// Only use the cwd setting if it exists.
215216
if (cwd !== undefined && await utils.checkIfDirectoryExists(cwd)) {
@@ -227,9 +228,10 @@ export async function validateCwdSetting(logger: Logger): Promise<string> {
227228
} else if (vscode.workspace.workspaceFolders.length > 1 && !hasPrompted) {
228229
hasPrompted = true;
229230
const options: vscode.WorkspaceFolderPickOptions = {
230-
placeHolder: "Select a folder to use as the PowerShell extension's working directory.",
231+
placeHolder: "Select a workspace folder to use for the PowerShell Extension.",
231232
};
232-
cwd = (await vscode.window.showWorkspaceFolderPick(options))?.uri.fsPath;
233+
chosenWorkspace = await vscode.window.showWorkspaceFolderPick(options);
234+
cwd = chosenWorkspace?.uri.fsPath;
233235
// Save the picked 'cwd' to the workspace settings.
234236
// We have to check again because the user may not have picked.
235237
if (cwd !== undefined && await utils.checkIfDirectoryExists(cwd)) {

src/utils.ts

+3
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ export function getPipePath(pipeName: string) {
2424
// Check that the file or directory exists in an asynchronous manner that relies
2525
// solely on the VS Code API, not Node's fs library, ignoring symlinks.
2626
async function checkIfFileOrDirectoryExists(targetPath: string | vscode.Uri, type: vscode.FileType): Promise<boolean> {
27+
if (targetPath === "") {
28+
return false;
29+
}
2730
try {
2831
const stat: vscode.FileStat = await vscode.workspace.fs.stat(
2932
targetPath instanceof vscode.Uri

0 commit comments

Comments
 (0)