diff --git a/package.json b/package.json index 1346e30e48..9a9659626d 100644 --- a/package.json +++ b/package.json @@ -798,10 +798,11 @@ "Verbose", "Normal", "Warning", - "Error" + "Error", + "None" ], "default": "Normal", - "description": "Sets the logging verbosity level for the PowerShell Editor Services host executable. Valid values are 'Diagnostic', 'Verbose', 'Normal', 'Warning', and 'Error'" + "description": "Sets the logging verbosity level for the PowerShell Editor Services host executable. Valid values are 'Diagnostic', 'Verbose', 'Normal', 'Warning', 'Error', and 'None'" }, "powershell.developer.editorServicesWaitForDebugger": { "type": "boolean", diff --git a/src/features/ExternalApi.ts b/src/features/ExternalApi.ts index a715454c7d..9ae950bace 100644 --- a/src/features/ExternalApi.ts +++ b/src/features/ExternalApi.ts @@ -19,6 +19,7 @@ export interface IPowerShellExtensionClient { unregisterExternalExtension(uuid: string): boolean; getPowerShellVersionDetails(uuid: string): Promise; waitUntilStarted(uuid: string): Promise; + getStorageUri(): vscode.Uri; } /* @@ -166,6 +167,10 @@ export class ExternalApiFeature extends LanguageClientConsumer implements IPower return this.sessionManager.waitUntilStarted(); } + public getStorageUri(): vscode.Uri { + return this.extensionContext.storageUri; + } + public dispose() { // Nothing to dispose. } diff --git a/src/logging.ts b/src/logging.ts index 5faf23a3d2..b0438a3b48 100644 --- a/src/logging.ts +++ b/src/logging.ts @@ -13,6 +13,7 @@ export enum LogLevel { Normal, Warning, Error, + None, } /** Interface for logging operations. New features should use this interface for the "type" of logger. @@ -29,19 +30,24 @@ export interface ILogger { export class Logger implements ILogger { - public logBasePath: string; - public logSessionPath: string; + public logBasePath: vscode.Uri; + public logSessionPath: vscode.Uri; public MinimumLogLevel: LogLevel = LogLevel.Normal; private commands: vscode.Disposable[]; private logChannel: vscode.OutputChannel; - private logFilePath: string; + private logFilePath: vscode.Uri; - constructor() { + constructor(logBasePath: vscode.Uri) { this.logChannel = vscode.window.createOutputChannel("PowerShell Extension Logs"); - this.logBasePath = path.resolve(__dirname, "../logs"); - utils.ensurePathExists(this.logBasePath); + if (logBasePath === undefined) { + // No workspace, we have to use another folder. + this.logBasePath = vscode.Uri.file(path.resolve(__dirname, "../logs")); + utils.ensurePathExists(this.logBasePath.fsPath); + } else { + this.logBasePath = vscode.Uri.joinPath(logBasePath, "logs"); + } this.commands = [ vscode.commands.registerCommand( @@ -59,8 +65,8 @@ export class Logger implements ILogger { this.logChannel.dispose(); } - public getLogFilePath(baseName: string): string { - return path.resolve(this.logSessionPath, `${baseName}.log`); + public getLogFilePath(baseName: string): vscode.Uri { + return vscode.Uri.joinPath(this.logSessionPath, `${baseName}.log`); } public writeAtLevel(logLevel: LogLevel, message: string, ...additionalMessages: string[]) { @@ -136,17 +142,16 @@ export class Logger implements ILogger { } } - public startNewLog(minimumLogLevel: string = "Normal") { + public async startNewLog(minimumLogLevel: string = "Normal") { this.MinimumLogLevel = this.logLevelNameToValue(minimumLogLevel.trim()); this.logSessionPath = - path.resolve( + vscode.Uri.joinPath( this.logBasePath, `${Math.floor(Date.now() / 1000)}-${vscode.env.sessionId}`); this.logFilePath = this.getLogFilePath("vscode-powershell"); - - utils.ensurePathExists(this.logSessionPath); + await vscode.workspace.fs.createDirectory(this.logSessionPath); } private logLevelNameToValue(logLevelName: string): LogLevel { @@ -156,6 +161,7 @@ export class Logger implements ILogger { case "normal": return LogLevel.Normal; case "warning": return LogLevel.Warning; case "error": return LogLevel.Error; + case "none": return LogLevel.None; default: return LogLevel.Normal; } } @@ -168,10 +174,7 @@ export class Logger implements ILogger { if (this.logSessionPath) { // Open the folder in VS Code since there isn't an easy way to // open the folder in the platform's file browser - vscode.commands.executeCommand( - "vscode.openFolder", - vscode.Uri.file(this.logSessionPath), - true); + vscode.commands.executeCommand("vscode.openFolder", this.logSessionPath, true); } } @@ -181,9 +184,9 @@ export class Logger implements ILogger { `${now.toLocaleDateString()} ${now.toLocaleTimeString()} [${LogLevel[level].toUpperCase()}] - ${message}`; this.logChannel.appendLine(timestampedMessage); - if (this.logFilePath) { + if (this.logFilePath && this.MinimumLogLevel !== LogLevel.None) { fs.appendFile( - this.logFilePath, + this.logFilePath.fsPath, timestampedMessage + os.EOL, (err) => { if (err) { diff --git a/src/main.ts b/src/main.ts index ad0665e4a9..8a48463662 100644 --- a/src/main.ts +++ b/src/main.ts @@ -125,7 +125,7 @@ export async function activate(context: vscode.ExtensionContext): Promise externalApi.unregisterExternalExtension(uuid), getPowerShellVersionDetails: uuid => externalApi.getPowerShellVersionDetails(uuid), waitUntilStarted: uuid => externalApi.waitUntilStarted(uuid), + getStorageUri: () => externalApi.getStorageUri(), }; } diff --git a/src/process.ts b/src/process.ts index 2a8dfcf05c..6c3dba72f5 100644 --- a/src/process.ts +++ b/src/process.ts @@ -51,7 +51,7 @@ export class PowerShellProcess { : ""; this.startPsesArgs += - `-LogPath '${PowerShellProcess.escapeSingleQuotes(editorServicesLogPath)}' ` + + `-LogPath '${PowerShellProcess.escapeSingleQuotes(editorServicesLogPath.fsPath)}' ` + `-SessionDetailsPath '${PowerShellProcess.escapeSingleQuotes(this.sessionFilePath)}' ` + `-FeatureFlags @(${featureFlags}) `; diff --git a/src/session.ts b/src/session.ts index 814de572a7..09d93e3cb6 100644 --- a/src/session.ts +++ b/src/session.ts @@ -111,7 +111,7 @@ export class SessionManager implements Middleware { this.sessionSettings.powerShellDefaultVersion = exeNameOverride; } - this.log.startNewLog(this.sessionSettings.developer.editorServicesLogLevel); + await this.log.startNewLog(this.sessionSettings.developer.editorServicesLogLevel); // Create the PowerShell executable finder now this.powershellExeFinder = new PowerShellExeFinder( diff --git a/test/core/paths.test.ts b/test/core/paths.test.ts index 99f31a3639..8559db5f79 100644 --- a/test/core/paths.test.ts +++ b/test/core/paths.test.ts @@ -5,10 +5,15 @@ import * as assert from "assert"; import * as fs from "fs"; import * as path from "path"; import * as vscode from "vscode"; +import { IPowerShellExtensionClient } from "../../src/features/ExternalApi"; import utils = require("../utils"); describe("Path assumptions", function () { - before(utils.ensureEditorServicesIsConnected); + let storageUri: vscode.Uri; + before(async () => { + const extension: IPowerShellExtensionClient = await utils.ensureEditorServicesIsConnected(); + storageUri = extension.getStorageUri(); + }); // TODO: This is skipped because it interferes with other tests. Either // need to find a way to close the opened folder via a Code API, or find @@ -22,6 +27,6 @@ describe("Path assumptions", function () { }); it("Creates the log folder at the correct path", function () { - assert(fs.existsSync(path.resolve(utils.rootPath, "logs"))); + assert(fs.existsSync(vscode.Uri.joinPath(storageUri, "logs").fsPath)); }); }); diff --git a/test/features/ExternalApi.test.ts b/test/features/ExternalApi.test.ts index 0bc44c2811..4c946096d9 100644 --- a/test/features/ExternalApi.test.ts +++ b/test/features/ExternalApi.test.ts @@ -7,51 +7,50 @@ import { IExternalPowerShellDetails, IPowerShellExtensionClient } from "../../sr describe("ExternalApi feature", function () { describe("External extension registration", function () { - let powerShellExtensionClient: IPowerShellExtensionClient; + let extension: IPowerShellExtensionClient; before(async function () { - const powershellExtension = await utils.ensureExtensionIsActivated(); - powerShellExtensionClient = powershellExtension!.exports as IPowerShellExtensionClient; + extension = await utils.ensureExtensionIsActivated(); }); it("Registers and unregisters an extension", function () { - const sessionId: string = powerShellExtensionClient.registerExternalExtension(utils.extensionId); + const sessionId: string = extension.registerExternalExtension(utils.extensionId); assert.notStrictEqual(sessionId, ""); assert.notStrictEqual(sessionId, null); assert.strictEqual( - powerShellExtensionClient.unregisterExternalExtension(sessionId), + extension.unregisterExternalExtension(sessionId), true); }); it("Registers and unregisters an extension with a version", function () { - const sessionId: string = powerShellExtensionClient.registerExternalExtension(utils.extensionId, "v2"); + const sessionId: string = extension.registerExternalExtension(utils.extensionId, "v2"); assert.notStrictEqual(sessionId, ""); assert.notStrictEqual(sessionId, null); assert.strictEqual( - powerShellExtensionClient.unregisterExternalExtension(sessionId), + extension.unregisterExternalExtension(sessionId), true); }); it("Rejects if not registered", async function () { assert.rejects( - async () => await powerShellExtensionClient.getPowerShellVersionDetails("")) + async () => await extension.getPowerShellVersionDetails("")) }); it("Throws if attempting to register an extension more than once", async function () { - const sessionId: string = powerShellExtensionClient.registerExternalExtension(utils.extensionId); + const sessionId: string = extension.registerExternalExtension(utils.extensionId); try { assert.throws( - () => powerShellExtensionClient.registerExternalExtension(utils.extensionId), + () => extension.registerExternalExtension(utils.extensionId), { message: `The extension '${utils.extensionId}' is already registered.` }); } finally { - powerShellExtensionClient.unregisterExternalExtension(sessionId); + extension.unregisterExternalExtension(sessionId); } }); it("Throws when unregistering an extension that isn't registered", async function () { assert.throws( - () => powerShellExtensionClient.unregisterExternalExtension("not-real"), + () => extension.unregisterExternalExtension("not-real"), { message: `No extension registered with session UUID: not-real` }); @@ -60,18 +59,17 @@ describe("ExternalApi feature", function () { describe("PowerShell version details", () => { let sessionId: string; - let powerShellExtensionClient: IPowerShellExtensionClient; + let extension: IPowerShellExtensionClient; before(async function () { - const powershellExtension = await utils.ensureExtensionIsActivated(); - powerShellExtensionClient = powershellExtension!.exports as IPowerShellExtensionClient; - sessionId = powerShellExtensionClient.registerExternalExtension(utils.extensionId); + extension = await utils.ensureExtensionIsActivated(); + sessionId = extension.registerExternalExtension(utils.extensionId); }); - after(function () { powerShellExtensionClient.unregisterExternalExtension(sessionId); }); + after(function () { extension.unregisterExternalExtension(sessionId); }); it("Gets non-empty version details from the PowerShell Editor Services", async function () { - const versionDetails: IExternalPowerShellDetails = await powerShellExtensionClient.getPowerShellVersionDetails(sessionId); + const versionDetails: IExternalPowerShellDetails = await extension.getPowerShellVersionDetails(sessionId); assert.notStrictEqual(versionDetails.architecture, ""); assert.notStrictEqual(versionDetails.architecture, null); diff --git a/test/utils.ts b/test/utils.ts index c9710b5b71..b47c113e9c 100644 --- a/test/utils.ts +++ b/test/utils.ts @@ -14,16 +14,16 @@ export const rootPath = path.resolve(__dirname, "../../") const packageJSON: any = require(path.resolve(rootPath, "package.json")); export const extensionId = `${packageJSON.publisher}.${packageJSON.name}`; -export async function ensureExtensionIsActivated(): Promise> { +export async function ensureExtensionIsActivated(): Promise { const extension = vscode.extensions.getExtension(extensionId); if (!extension.isActive) { await extension.activate(); } - return extension; + return extension!.exports as IPowerShellExtensionClient; } -export async function ensureEditorServicesIsConnected(): Promise { - const powershellExtension = await ensureExtensionIsActivated(); - const client = powershellExtension!.exports as IPowerShellExtensionClient; - const sessionId = client.registerExternalExtension(extensionId); - await client.waitUntilStarted(sessionId); - client.unregisterExternalExtension(sessionId); +export async function ensureEditorServicesIsConnected(): Promise { + const extension = await ensureExtensionIsActivated(); + const sessionId = extension.registerExternalExtension(extensionId); + await extension.waitUntilStarted(sessionId); + extension.unregisterExternalExtension(sessionId); + return extension; }