From 46e0e775444ca7860541777ac04d1621859008ac Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Thu, 1 Dec 2022 13:00:59 -0800 Subject: [PATCH] Fully support multi-root workspaces --- src/features/ExtensionCommands.ts | 68 +++++++++---------------------- src/features/PesterTests.ts | 10 ++--- src/features/RunCode.ts | 9 ++-- src/main.ts | 4 +- src/settings.ts | 8 ++-- src/utils.ts | 3 ++ 6 files changed, 39 insertions(+), 63 deletions(-) diff --git a/src/features/ExtensionCommands.ts b/src/features/ExtensionCommands.ts index 9d43ce2cf1..8399a24996 100644 --- a/src/features/ExtensionCommands.ts +++ b/src/features/ExtensionCommands.ts @@ -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 { @@ -368,32 +368,20 @@ export class ExtensionCommandsFeature extends LanguageClientConsumer { return EditorOperationResponse.Completed; } - private openFile(openFileDetails: IOpenFileDetails): Thenable { - 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 { + 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 { - let promise: Thenable; - 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 { + 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; } /** @@ -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; } @@ -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; } @@ -511,14 +484,13 @@ export class ExtensionCommandsFeature extends LanguageClientConsumer { await vscode.window.showTextDocument(newFile, { preview: true }); } - private normalizeFilePath(filePath: string): string { + private async normalizeFilePath(filePath: string): Promise { + 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 @@ -526,9 +498,7 @@ export class ExtensionCommandsFeature extends LanguageClientConsumer { } 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 diff --git a/src/features/PesterTests.ts b/src/features/PesterTests.ts index 27f474d609..f515cd1857 100644 --- a/src/features/PesterTests.ts +++ b/src/features/PesterTests.ts @@ -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 { @@ -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 @@ -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); } } diff --git a/src/features/RunCode.ts b/src/features/RunCode.ts index 2bd8424cbd..bb6f6b44f5 100644 --- a/src/features/RunCode.ts +++ b/src/features/RunCode.ts @@ -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, @@ -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[]) => { @@ -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); } } diff --git a/src/main.ts b/src/main.ts index 800b51a69f..7b977427ed 100644 --- a/src/main.ts +++ b/src/main.ts @@ -145,8 +145,8 @@ export async function activate(context: vscode.ExtensionContext): Promise { - let cwd = vscode.workspace.getConfiguration(utils.PowerShellLanguageId).get("cwd"); + let cwd: string | undefined = vscode.workspace.getConfiguration(utils.PowerShellLanguageId).get("cwd"); // Only use the cwd setting if it exists. if (cwd !== undefined && await utils.checkIfDirectoryExists(cwd)) { @@ -227,9 +228,10 @@ export async function validateCwdSetting(logger: Logger): Promise { } 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)) { diff --git a/src/utils.ts b/src/utils.ts index 881b5b26c4..61c3875744 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -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 { + if (targetPath === "") { + return false; + } try { const stat: vscode.FileStat = await vscode.workspace.fs.stat( targetPath instanceof vscode.Uri