Skip to content

Fully support multi-root workspaces #3796

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Dec 1, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 19 additions & 49 deletions src/features/ExtensionCommands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
} from "vscode-languageclient";
import { LanguageClient } from "vscode-languageclient/node";
import { Logger } from "../logging";
import { getSettings } from "../settings";
import { getSettings, validateCwdSetting } from "../settings";
import { LanguageClientConsumer } from "../languageClientConsumer";

export interface IExtensionCommand {
Expand Down Expand Up @@ -368,32 +368,20 @@ export class ExtensionCommandsFeature extends LanguageClientConsumer {
return EditorOperationResponse.Completed;
}

private openFile(openFileDetails: IOpenFileDetails): Thenable<EditorOperationResponse> {
const filePath = this.normalizeFilePath(openFileDetails.filePath);

const promise =
vscode.workspace.openTextDocument(filePath)
.then((doc) => vscode.window.showTextDocument(
doc,
{ preview: openFileDetails.preview }))
.then((_) => EditorOperationResponse.Completed);

return promise;
private async openFile(openFileDetails: IOpenFileDetails): Promise<EditorOperationResponse> {
const filePath = await this.normalizeFilePath(openFileDetails.filePath);
const doc = await vscode.workspace.openTextDocument(filePath);
await vscode.window.showTextDocument(doc, { preview: openFileDetails.preview });
return EditorOperationResponse.Completed;
}

private closeFile(filePath: string): Thenable<EditorOperationResponse> {
let promise: Thenable<EditorOperationResponse>;
if (this.findTextDocument(this.normalizeFilePath(filePath))) {
promise =
vscode.workspace.openTextDocument(filePath)
.then((doc) => vscode.window.showTextDocument(doc))
.then((_) => vscode.commands.executeCommand("workbench.action.closeActiveEditor"))
.then((_) => EditorOperationResponse.Completed);
} else {
promise = Promise.resolve(EditorOperationResponse.Completed);
private async closeFile(filePath: string): Promise<EditorOperationResponse> {
if (this.findTextDocument(await this.normalizeFilePath(filePath))) {
const doc = await vscode.workspace.openTextDocument(filePath);
await vscode.window.showTextDocument(doc);
await vscode.commands.executeCommand("workbench.action.closeActiveEditor");
}

return promise;
return EditorOperationResponse.Completed;
}

/**
Expand All @@ -413,7 +401,7 @@ export class ExtensionCommandsFeature extends LanguageClientConsumer {
switch (currentFileUri.scheme) {
case "file": {
// If the file to save can't be found, just complete the request
if (!this.findTextDocument(this.normalizeFilePath(currentFileUri.fsPath))) {
if (!this.findTextDocument(await this.normalizeFilePath(currentFileUri.fsPath))) {
void this.logger.writeAndShowError(`File to save not found: ${currentFileUri.fsPath}.`);
return EditorOperationResponse.Completed;
}
Expand Down Expand Up @@ -449,23 +437,8 @@ export class ExtensionCommandsFeature extends LanguageClientConsumer {
if (path.isAbsolute(saveFileDetails.newPath)) {
newFileAbsolutePath = saveFileDetails.newPath;
} else {
// In fresh contexts, workspaceFolders is not defined...
if (!vscode.workspace.workspaceFolders || vscode.workspace.workspaceFolders.length === 0) {
void this.logger.writeAndShowWarning("Cannot save file to relative path: no workspaces are open. " +
"Try saving to an absolute path, or open a workspace.");
return EditorOperationResponse.Completed;
}

// If not, interpret the path as relative to the workspace root
const workspaceRootUri = vscode.workspace.workspaceFolders[0].uri;
// We don't support saving to a non-file URI-schemed workspace
if (workspaceRootUri.scheme !== "file") {
void this.logger.writeAndShowWarning(
"Cannot save untitled file to a relative path in an untitled workspace. " +
"Try saving to an absolute path or opening a workspace folder.");
return EditorOperationResponse.Completed;
}
newFileAbsolutePath = path.join(workspaceRootUri.fsPath, saveFileDetails.newPath);
const cwd = await validateCwdSetting(this.logger);
newFileAbsolutePath = path.join(cwd, saveFileDetails.newPath);
}
break; }

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

private normalizeFilePath(filePath: string): string {
private async normalizeFilePath(filePath: string): Promise<string> {
const cwd = await validateCwdSetting(this.logger);
const platform = os.platform();
if (platform === "win32") {
// Make sure the file path is absolute
if (!path.win32.isAbsolute(filePath)) {
filePath = path.win32.resolve(
vscode.workspace.rootPath!,
filePath);
filePath = path.win32.resolve(cwd, filePath);
}

// Normalize file path case for comparison for Windows
return filePath.toLowerCase();
} else {
// Make sure the file path is absolute
if (!path.isAbsolute(filePath)) {
filePath = path.resolve(
vscode.workspace.rootPath!,
filePath);
filePath = path.resolve(cwd, filePath);
}

// macOS is case-insensitive
Expand Down
10 changes: 5 additions & 5 deletions src/features/PesterTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@

import * as path from "path";
import vscode = require("vscode");
import { Logger } from "../logging";
import { SessionManager } from "../session";
import { getSettings } from "../settings";
import { getSettings, chosenWorkspace, validateCwdSetting } from "../settings";
import utils = require("../utils");

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

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

// TODO: Update to handle multiple root workspaces.
//
// Ensure the necessary script exists (for testing). The debugger will
// start regardless, but we also pass its success along.
await validateCwdSetting(this.logger);
return await utils.checkIfFileExists(this.invokePesterStubScriptPath)
&& vscode.debug.startDebugging(vscode.workspace.workspaceFolders?.[0], launchConfig);
&& vscode.debug.startDebugging(chosenWorkspace, launchConfig);
}
}
9 changes: 5 additions & 4 deletions src/features/RunCode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@

import vscode = require("vscode");
import { SessionManager } from "../session";
import { getSettings } from "../settings";
import { Logger } from "../logging";
import { getSettings, chosenWorkspace, validateCwdSetting } from "../settings";

enum LaunchType {
Debug,
Expand All @@ -13,7 +14,7 @@ enum LaunchType {
export class RunCodeFeature implements vscode.Disposable {
private command: vscode.Disposable;

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

// TODO: Update to handle multiple root workspaces.
await vscode.debug.startDebugging(vscode.workspace.workspaceFolders?.[0], launchConfig);
await validateCwdSetting(this.logger);
await vscode.debug.startDebugging(chosenWorkspace, launchConfig);
}
}

Expand Down
4 changes: 2 additions & 2 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,8 +145,8 @@ export async function activate(context: vscode.ExtensionContext): Promise<IPower
new GenerateBugReportFeature(sessionManager),
new ISECompatibilityFeature(),
new OpenInISEFeature(),
new PesterTestsFeature(sessionManager),
new RunCodeFeature(sessionManager),
new PesterTestsFeature(sessionManager, logger),
new RunCodeFeature(sessionManager, logger),
new CodeActionsFeature(logger),
new SpecifyScriptArgsFeature(context),
];
Expand Down
8 changes: 5 additions & 3 deletions src/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -207,9 +207,10 @@ export async function changeSetting(

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

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

// Only use the cwd setting if it exists.
if (cwd !== undefined && await utils.checkIfDirectoryExists(cwd)) {
Expand All @@ -227,9 +228,10 @@ export async function validateCwdSetting(logger: Logger): Promise<string> {
} else if (vscode.workspace.workspaceFolders.length > 1 && !hasPrompted) {
hasPrompted = true;
const options: vscode.WorkspaceFolderPickOptions = {
placeHolder: "Select a folder to use as the PowerShell extension's working directory.",
placeHolder: "Select a workspace folder to use for the PowerShell Extension.",
};
cwd = (await vscode.window.showWorkspaceFolderPick(options))?.uri.fsPath;
chosenWorkspace = await vscode.window.showWorkspaceFolderPick(options);
cwd = chosenWorkspace?.uri.fsPath;
// Save the picked 'cwd' to the workspace settings.
// We have to check again because the user may not have picked.
if (cwd !== undefined && await utils.checkIfDirectoryExists(cwd)) {
Expand Down
3 changes: 3 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ export function getPipePath(pipeName: string) {
// Check that the file or directory exists in an asynchronous manner that relies
// solely on the VS Code API, not Node's fs library, ignoring symlinks.
async function checkIfFileOrDirectoryExists(targetPath: string | vscode.Uri, type: vscode.FileType): Promise<boolean> {
if (targetPath === "") {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@SeeminglyScience since the settings overhaul PR, the default cwd was "" which conspicuously vscode.workspace.fs.stat was totally fine saying it existed.

return false;
}
try {
const stat: vscode.FileStat = await vscode.workspace.fs.stat(
targetPath instanceof vscode.Uri
Expand Down