diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md
index 3c341a6e19..c6ccb7dbdc 100644
--- a/docs/troubleshooting.md
+++ b/docs/troubleshooting.md
@@ -253,11 +253,11 @@ Logs provide context for what was happening when the issue occurred. **You shoul
your logs for any sensitive information you would not like to share online!**
* Before sending through logs, try and reproduce the issue with **log level set to
- Diagnostic**. You can set this in the [VS Code Settings][]
+ Trace**. You can set this in the [VS Code Settings][]
(Ctrl+,) with:
```json
- "powershell.developer.editorServicesLogLevel": "Diagnostic"
+ "powershell.developer.editorServicesLogLevel": "Trace"
```
* After you have captured the issue with the log level turned up, you may want to return
diff --git a/package.json b/package.json
index 55b397dab6..73687b5fae 100644
--- a/package.json
+++ b/package.json
@@ -916,24 +916,24 @@
},
"powershell.developer.editorServicesLogLevel": {
"type": "string",
- "default": "Normal",
+ "default": "Warning",
"enum": [
- "Diagnostic",
- "Verbose",
- "Normal",
+ "Trace",
+ "Debug",
+ "Information",
"Warning",
"Error",
"None"
],
"markdownEnumDescriptions": [
"Enables all logging possible, please use this setting when submitting logs for bug reports!",
- "Enables more logging than normal.",
- "The default logging level.",
- "Only log warnings and errors.",
+ "Enables more detailed logging of the extension",
+ "Logs high-level information about what the extension is doing.",
+ "Only log warnings and errors. This is the default setting",
"Only log errors.",
"Disable all logging possible. No log files will be written!"
],
- "markdownDescription": "Sets the log verbosity for both the extension and its LSP server, PowerShell Editor Services. **Please set to `Diagnostic` when recording logs for a bug report!**"
+ "markdownDescription": "Sets the log verbosity for both the extension and its LSP server, PowerShell Editor Services. **Please set to `Trace` when recording logs for a bug report!**"
},
"powershell.developer.editorServicesWaitForDebugger": {
"type": "boolean",
@@ -953,6 +953,21 @@
"default": [],
"markdownDescription": "An array of strings that enable experimental features in the PowerShell extension. **No flags are currently available!**"
},
+ "powershell.developer.traceDap": {
+ "type": "boolean",
+ "default": false,
+ "markdownDescription": "Traces the DAP communication between VS Code and the PowerShell Editor Services [DAP Server](https://microsoft.github.io/debug-adapter-protocol/). The output will be logged and also visible in the Output pane, where the verbosity is configurable. **For extension developers and issue troubleshooting only!**"
+ },
+ "powershell.trace.server": {
+ "type": "string",
+ "enum": [
+ "off",
+ "messages",
+ "verbose"
+ ],
+ "default": "off",
+ "markdownDescription": "Traces the communication between VS Code and the PowerShell Editor Services [LSP Server](https://microsoft.github.io/language-server-protocol/). The output will be logged and also visible in the Output pane, where the verbosity is configurable. **For extension developers and issue troubleshooting only!**"
+ },
"powershell.developer.waitForSessionFileTimeoutSeconds": {
"type": "number",
"default": 240,
@@ -1002,21 +1017,6 @@
"type": "boolean",
"default": false,
"markdownDescription": "Show buttons in the editor's title bar for moving the terminals pane (with the PowerShell Extension Terminal) around."
- },
- "powershell.trace.server": {
- "type": "string",
- "enum": [
- "off",
- "messages",
- "verbose"
- ],
- "default": "off",
- "markdownDescription": "Traces the communication between VS Code and the PowerShell Editor Services [LSP Server](https://microsoft.github.io/language-server-protocol/). **only for extension developers and issue troubleshooting!**"
- },
- "powershell.trace.dap": {
- "type": "boolean",
- "default": false,
- "markdownDescription": "Traces the communication between VS Code and the PowerShell Editor Services [DAP Server](https://microsoft.github.io/debug-adapter-protocol/). **This setting is only meant for extension developers and issue troubleshooting!**"
}
}
},
diff --git a/src/extension.ts b/src/extension.ts
index 0cb8f3f52e..8676724ab1 100644
--- a/src/extension.ts
+++ b/src/extension.ts
@@ -22,7 +22,7 @@ import { ShowHelpFeature } from "./features/ShowHelp";
import { SpecifyScriptArgsFeature } from "./features/DebugSession";
import { Logger } from "./logging";
import { SessionManager } from "./session";
-import { LogLevel, getSettings } from "./settings";
+import { getSettings } from "./settings";
import { PowerShellLanguageId } from "./utils";
import { LanguageClientConsumer } from "./languageClientConsumer";
@@ -43,14 +43,12 @@ const documentSelector: DocumentSelector = [
];
export async function activate(context: vscode.ExtensionContext): Promise {
- const logLevel = vscode.workspace.getConfiguration(`${PowerShellLanguageId}.developer`)
- .get("editorServicesLogLevel", LogLevel.Normal);
- logger = new Logger(logLevel, context.globalStorageUri);
+ logger = new Logger();
telemetryReporter = new TelemetryReporter(TELEMETRY_KEY);
const settings = getSettings();
- logger.writeVerbose(`Loaded settings:\n${JSON.stringify(settings, undefined, 2)}`);
+ logger.writeDebug(`Loaded settings:\n${JSON.stringify(settings, undefined, 2)}`);
languageConfigurationDisposable = vscode.languages.setLanguageConfiguration(
PowerShellLanguageId,
@@ -141,6 +139,19 @@ export async function activate(context: vscode.ExtensionContext): Promise {await vscode.commands.executeCommand(
+ "vscode.openFolder",
+ context.logUri,
+ { forceNewWindow: true }
+ );}
+ ),
+ vscode.commands.registerCommand(
+ "PowerShell.ShowLogs",
+ () => {logger.showLogPanel();}
+ )
];
const externalApi = new ExternalApiFeature(context, sessionManager, logger);
@@ -169,6 +180,7 @@ export async function activate(context: vscode.ExtensionContext): Promise externalApi.getPowerShellVersionDetails(uuid),
waitUntilStarted: uuid => externalApi.waitUntilStarted(uuid),
getStorageUri: () => externalApi.getStorageUri(),
+ getLogUri: () => externalApi.getLogUri(),
};
}
diff --git a/src/features/DebugSession.ts b/src/features/DebugSession.ts
index f9e4feae07..4af8c83b89 100644
--- a/src/features/DebugSession.ts
+++ b/src/features/DebugSession.ts
@@ -335,8 +335,8 @@ export class DebugSessionFeature extends LanguageClientConsumer
// Create or show the debug terminal (either temporary or session).
this.sessionManager.showDebugTerminal(true);
- this.logger.writeVerbose(`Connecting to pipe: ${sessionDetails.debugServicePipeName}`);
- this.logger.writeVerbose(`Debug configuration: ${JSON.stringify(session.configuration, undefined, 2)}`);
+ this.logger.writeDebug(`Connecting to pipe: ${sessionDetails.debugServicePipeName}`);
+ this.logger.writeDebug(`Debug configuration: ${JSON.stringify(session.configuration, undefined, 2)}`);
return new DebugAdapterNamedPipeServer(sessionDetails.debugServicePipeName);
}
@@ -424,7 +424,7 @@ export class DebugSessionFeature extends LanguageClientConsumer
// The dispose shorthand demonry for making an event one-time courtesy of: https://github.com/OmniSharp/omnisharp-vscode/blob/b8b07bb12557b4400198895f82a94895cb90c461/test/integrationTests/launchConfiguration.integration.test.ts#L41-L45
startDebugEvent.dispose();
- this.logger.writeVerbose(`Debugger session detected: ${dotnetAttachSession.name} (${dotnetAttachSession.id})`);
+ this.logger.writeDebug(`Debugger session detected: ${dotnetAttachSession.name} (${dotnetAttachSession.id})`);
tempConsoleDotnetAttachSession = dotnetAttachSession;
@@ -434,7 +434,7 @@ export class DebugSessionFeature extends LanguageClientConsumer
// Makes the event one-time
stopDebugEvent.dispose();
- this.logger.writeVerbose(`Debugger session terminated: ${tempConsoleSession.name} (${tempConsoleSession.id})`);
+ this.logger.writeDebug(`Debugger session terminated: ${tempConsoleSession.name} (${tempConsoleSession.id})`);
// HACK: As of 2023-08-17, there is no vscode debug API to request the C# debugger to detach, so we send it a custom DAP request instead.
const disconnectRequest: DebugProtocol.DisconnectRequest = {
@@ -462,8 +462,8 @@ export class DebugSessionFeature extends LanguageClientConsumer
// Start a child debug session to attach the dotnet debugger
// TODO: Accommodate multi-folder workspaces if the C# code is in a different workspace folder
await debug.startDebugging(undefined, dotnetAttachConfig, session);
- this.logger.writeVerbose(`Dotnet attach debug configuration: ${JSON.stringify(dotnetAttachConfig, undefined, 2)}`);
- this.logger.writeVerbose(`Attached dotnet debugger to process: ${pid}`);
+ this.logger.writeDebug(`Dotnet attach debug configuration: ${JSON.stringify(dotnetAttachConfig, undefined, 2)}`);
+ this.logger.writeDebug(`Attached dotnet debugger to process: ${pid}`);
}
return this.tempSessionDetails;
@@ -606,36 +606,27 @@ export class DebugSessionFeature extends LanguageClientConsumer
class PowerShellDebugAdapterTrackerFactory implements DebugAdapterTrackerFactory, Disposable {
disposables: Disposable[] = [];
- dapLogEnabled: boolean = workspace.getConfiguration("powershell").get("trace.dap") ?? false;
- constructor(private adapterName = "PowerShell") {
- this.disposables.push(workspace.onDidChangeConfiguration(change => {
- if (
- change.affectsConfiguration("powershell.trace.dap")
- ) {
- this.dapLogEnabled = workspace.getConfiguration("powershell").get("trace.dap") ?? false;
- if (this.dapLogEnabled) {
- // Trigger the output pane to appear. This gives the user time to position it before starting a debug.
- this.log?.show(true);
- }
- }
- }));
- }
+ constructor(private adapterName = "PowerShell") {}
- /* We want to use a shared output log for separate debug sessions as usually only one is running at a time and we
- * dont need an output window for every debug session. We also want to leave it active so user can copy and paste
- * even on run end. When user changes the setting and disables it getter will return undefined, which will result
+
+ _log: LogOutputChannel | undefined;
+ /** Lazily creates a {@link LogOutputChannel} for debug tracing, and presents it only when DAP logging is enabled.
+ *
+ * We want to use a shared output log for separate debug sessions as usually only one is running at a time and we
+ * dont need an output window for every debug session. We also want to leave it active so user can copy and paste
+ * even on run end. When user changes the setting and disables it getter will return undefined, which will result
* in a noop for the logging activities, effectively pausing logging but not disposing the output channel. If the
* user re-enables, then logging resumes.
*/
- _log: LogOutputChannel | undefined;
get log(): LogOutputChannel | undefined {
- if (this.dapLogEnabled && this._log === undefined) {
- this._log = window.createOutputChannel(`${this.adapterName} Trace - DAP`, { log: true });
+ if (workspace.getConfiguration("powershell.developer").get("traceDap") && this._log === undefined) {
+ this._log = window.createOutputChannel(`${this.adapterName}: Trace DAP`, { log: true });
this.disposables.push(this._log);
}
- return this.dapLogEnabled ? this._log : undefined;
+ return this._log;
}
+ // This tracker effectively implements the logging for the debug adapter to a LogOutputChannel
createDebugAdapterTracker(session: DebugSession): DebugAdapterTracker {
const sessionInfo = `${this.adapterName} Debug Session: ${session.name} [${session.id}]`;
return {
diff --git a/src/features/ExternalApi.ts b/src/features/ExternalApi.ts
index 7943bf8fa6..29e3427f88 100644
--- a/src/features/ExternalApi.ts
+++ b/src/features/ExternalApi.ts
@@ -19,6 +19,7 @@ export interface IPowerShellExtensionClient {
getPowerShellVersionDetails(uuid: string): Promise;
waitUntilStarted(uuid: string): Promise;
getStorageUri(): vscode.Uri;
+ getLogUri(): vscode.Uri;
}
/*
@@ -55,7 +56,7 @@ export class ExternalApiFeature implements IPowerShellExtensionClient {
string session uuid
*/
public registerExternalExtension(id: string, apiVersion = "v1"): string {
- this.logger.writeVerbose(`Registering extension '${id}' for use with API version '${apiVersion}'.`);
+ this.logger.writeDebug(`Registering extension '${id}' for use with API version '${apiVersion}'.`);
// eslint-disable-next-line @typescript-eslint/no-unused-vars
for (const [_name, externalExtension] of ExternalApiFeature.registeredExternalExtension) {
@@ -96,7 +97,7 @@ export class ExternalApiFeature implements IPowerShellExtensionClient {
true if it worked, otherwise throws an error.
*/
public unregisterExternalExtension(uuid = ""): boolean {
- this.logger.writeVerbose(`Unregistering extension with session UUID: ${uuid}`);
+ this.logger.writeDebug(`Unregistering extension with session UUID: ${uuid}`);
if (!ExternalApiFeature.registeredExternalExtension.delete(uuid)) {
throw new Error(`No extension registered with session UUID: ${uuid}`);
}
@@ -133,7 +134,7 @@ export class ExternalApiFeature implements IPowerShellExtensionClient {
*/
public async getPowerShellVersionDetails(uuid = ""): Promise {
const extension = this.getRegisteredExtension(uuid);
- this.logger.writeVerbose(`Extension '${extension.id}' called 'getPowerShellVersionDetails'.`);
+ this.logger.writeDebug(`Extension '${extension.id}' called 'getPowerShellVersionDetails'.`);
await this.sessionManager.waitUntilStarted();
const versionDetails = this.sessionManager.getPowerShellVersionDetails();
@@ -161,7 +162,7 @@ export class ExternalApiFeature implements IPowerShellExtensionClient {
*/
public async waitUntilStarted(uuid = ""): Promise {
const extension = this.getRegisteredExtension(uuid);
- this.logger.writeVerbose(`Extension '${extension.id}' called 'waitUntilStarted'.`);
+ this.logger.writeDebug(`Extension '${extension.id}' called 'waitUntilStarted'.`);
await this.sessionManager.waitUntilStarted();
}
@@ -171,6 +172,10 @@ export class ExternalApiFeature implements IPowerShellExtensionClient {
return this.extensionContext.globalStorageUri.with({ scheme: "file"});
}
+ public getLogUri(): vscode.Uri {
+ return this.extensionContext.logUri.with({ scheme: "file"});
+ }
+
public dispose(): void {
// Nothing to dispose.
}
diff --git a/src/features/UpdatePowerShell.ts b/src/features/UpdatePowerShell.ts
index 01c31eb385..6805272cc8 100644
--- a/src/features/UpdatePowerShell.ts
+++ b/src/features/UpdatePowerShell.ts
@@ -51,20 +51,20 @@ export class UpdatePowerShell {
private shouldCheckForUpdate(): boolean {
// Respect user setting.
if (!this.sessionSettings.promptToUpdatePowerShell) {
- this.logger.writeVerbose("Setting 'promptToUpdatePowerShell' was false.");
+ this.logger.writeDebug("Setting 'promptToUpdatePowerShell' was false.");
return false;
}
// Respect environment configuration.
if (process.env.POWERSHELL_UPDATECHECK?.toLowerCase() === "off") {
- this.logger.writeVerbose("Environment variable 'POWERSHELL_UPDATECHECK' was 'Off'.");
+ this.logger.writeDebug("Environment variable 'POWERSHELL_UPDATECHECK' was 'Off'.");
return false;
}
// Skip prompting when using Windows PowerShell for now.
if (this.localVersion.compare("6.0.0") === -1) {
// TODO: Maybe we should announce PowerShell Core?
- this.logger.writeVerbose("Not prompting to update Windows PowerShell.");
+ this.logger.writeDebug("Not prompting to update Windows PowerShell.");
return false;
}
@@ -78,13 +78,13 @@ export class UpdatePowerShell {
// Skip if PowerShell is self-built, that is, this contains a commit hash.
if (commit.length >= 40) {
- this.logger.writeVerbose("Not prompting to update development build.");
+ this.logger.writeDebug("Not prompting to update development build.");
return false;
}
// Skip if preview is a daily build.
if (daily.toLowerCase().startsWith("daily")) {
- this.logger.writeVerbose("Not prompting to update daily build.");
+ this.logger.writeDebug("Not prompting to update daily build.");
return false;
}
}
@@ -106,7 +106,7 @@ export class UpdatePowerShell {
// "ReleaseTag": "v7.2.7"
// }
const data = await response.json();
- this.logger.writeVerbose(`Received from '${url}':\n${JSON.stringify(data, undefined, 2)}`);
+ this.logger.writeDebug(`Received from '${url}':\n${JSON.stringify(data, undefined, 2)}`);
return data.ReleaseTag;
}
@@ -115,18 +115,18 @@ export class UpdatePowerShell {
return undefined;
}
- this.logger.writeVerbose("Checking for PowerShell update...");
+ this.logger.writeDebug("Checking for PowerShell update...");
const tags: string[] = [];
if (process.env.POWERSHELL_UPDATECHECK?.toLowerCase() === "lts") {
// Only check for update to LTS.
- this.logger.writeVerbose("Checking for LTS update...");
+ this.logger.writeDebug("Checking for LTS update...");
const tag = await this.getRemoteVersion(UpdatePowerShell.LTSBuildInfoURL);
if (tag != undefined) {
tags.push(tag);
}
} else {
// Check for update to stable.
- this.logger.writeVerbose("Checking for stable update...");
+ this.logger.writeDebug("Checking for stable update...");
const tag = await this.getRemoteVersion(UpdatePowerShell.StableBuildInfoURL);
if (tag != undefined) {
tags.push(tag);
@@ -134,7 +134,7 @@ export class UpdatePowerShell {
// Also check for a preview update.
if (this.localVersion.prerelease.length > 0) {
- this.logger.writeVerbose("Checking for preview update...");
+ this.logger.writeDebug("Checking for preview update...");
const tag = await this.getRemoteVersion(UpdatePowerShell.PreviewBuildInfoURL);
if (tag != undefined) {
tags.push(tag);
@@ -181,11 +181,11 @@ export class UpdatePowerShell {
// If the user cancels the notification.
if (!result) {
- this.logger.writeVerbose("User canceled PowerShell update prompt.");
+ this.logger.writeDebug("User canceled PowerShell update prompt.");
return;
}
- this.logger.writeVerbose(`User said '${UpdatePowerShell.promptOptions[result.id].title}'.`);
+ this.logger.writeDebug(`User said '${UpdatePowerShell.promptOptions[result.id].title}'.`);
switch (result.id) {
// Yes
diff --git a/src/logging.ts b/src/logging.ts
index a7176c08ef..7ce8d09e16 100644
--- a/src/logging.ts
+++ b/src/logging.ts
@@ -1,30 +1,16 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
-import utils = require("./utils");
-import os = require("os");
-import vscode = require("vscode");
-
-// NOTE: This is not a string enum because the order is used for comparison.
-export enum LogLevel {
- Diagnostic,
- Verbose,
- Normal,
- Warning,
- Error,
- None,
-}
+import { LogOutputChannel, LogLevel, window, Event } from "vscode";
/** Interface for logging operations. New features should use this interface for the "type" of logger.
* This will allow for easy mocking of the logger during unit tests.
*/
export interface ILogger {
- logDirectoryPath: vscode.Uri;
- updateLogLevel(logLevelName: string): void;
write(message: string, ...additionalMessages: string[]): void;
writeAndShowInformation(message: string, ...additionalMessages: string[]): Promise;
- writeDiagnostic(message: string, ...additionalMessages: string[]): void;
- writeVerbose(message: string, ...additionalMessages: string[]): void;
+ writeTrace(message: string, ...additionalMessages: string[]): void;
+ writeDebug(message: string, ...additionalMessages: string[]): void;
writeWarning(message: string, ...additionalMessages: string[]): void;
writeAndShowWarning(message: string, ...additionalMessages: string[]): Promise;
writeError(message: string, ...additionalMessages: string[]): void;
@@ -35,47 +21,16 @@ export interface ILogger {
}
export class Logger implements ILogger {
- public logDirectoryPath: vscode.Uri; // The folder for all the logs
- private logLevel: LogLevel;
- private commands: vscode.Disposable[];
- private logChannel: vscode.OutputChannel;
- private logFilePath: vscode.Uri; // The client's logs
- private logDirectoryCreated = false;
- private writingLog = false;
-
- constructor(logLevelName: string, globalStorageUri: vscode.Uri) {
- this.logLevel = Logger.logLevelNameToValue(logLevelName);
- this.logChannel = vscode.window.createOutputChannel("PowerShell Extension Logs");
- // We have to override the scheme because it defaults to
- // 'vscode-userdata' which breaks UNC paths.
- this.logDirectoryPath = vscode.Uri.joinPath(
- globalStorageUri.with({ scheme: "file" }),
- "logs",
- `${Math.floor(Date.now() / 1000)}-${vscode.env.sessionId}`);
- this.logFilePath = vscode.Uri.joinPath(this.logDirectoryPath, "vscode-powershell.log");
-
- // Early logging of the log paths for debugging.
- if (LogLevel.Diagnostic >= this.logLevel) {
- const uriMessage = Logger.timestampMessage(`Log file path: '${this.logFilePath}'`, LogLevel.Verbose);
- this.logChannel.appendLine(uriMessage);
- }
-
- this.commands = [
- vscode.commands.registerCommand(
- "PowerShell.ShowLogs",
- () => { this.showLogPanel(); }),
+ // Log output channel handles all the verbosity management so we don't have to.
+ private logChannel: LogOutputChannel;
+ public get logLevel(): LogLevel { return this.logChannel.logLevel;}
- vscode.commands.registerCommand(
- "PowerShell.OpenLogFolder",
- async () => { await this.openLogFolder(); }),
- ];
+ constructor(logChannel?: LogOutputChannel) {
+ this.logChannel = logChannel ?? window.createOutputChannel("PowerShell", {log: true});
}
public dispose(): void {
this.logChannel.dispose();
- for (const command of this.commands) {
- command.dispose();
- }
}
private writeAtLevel(logLevel: LogLevel, message: string, ...additionalMessages: string[]): void {
@@ -89,24 +44,24 @@ export class Logger implements ILogger {
}
public write(message: string, ...additionalMessages: string[]): void {
- this.writeAtLevel(LogLevel.Normal, message, ...additionalMessages);
+ this.writeAtLevel(LogLevel.Info, message, ...additionalMessages);
}
public async writeAndShowInformation(message: string, ...additionalMessages: string[]): Promise {
this.write(message, ...additionalMessages);
- const selection = await vscode.window.showInformationMessage(message, "Show Logs", "Okay");
+ const selection = await window.showInformationMessage(message, "Show Logs", "Okay");
if (selection === "Show Logs") {
this.showLogPanel();
}
}
- public writeDiagnostic(message: string, ...additionalMessages: string[]): void {
- this.writeAtLevel(LogLevel.Diagnostic, message, ...additionalMessages);
+ public writeTrace(message: string, ...additionalMessages: string[]): void {
+ this.writeAtLevel(LogLevel.Trace, message, ...additionalMessages);
}
- public writeVerbose(message: string, ...additionalMessages: string[]): void {
- this.writeAtLevel(LogLevel.Verbose, message, ...additionalMessages);
+ public writeDebug(message: string, ...additionalMessages: string[]): void {
+ this.writeAtLevel(LogLevel.Debug, message, ...additionalMessages);
}
public writeWarning(message: string, ...additionalMessages: string[]): void {
@@ -116,7 +71,7 @@ export class Logger implements ILogger {
public async writeAndShowWarning(message: string, ...additionalMessages: string[]): Promise {
this.writeWarning(message, ...additionalMessages);
- const selection = await vscode.window.showWarningMessage(message, "Show Logs");
+ const selection = await window.showWarningMessage(message, "Show Logs");
if (selection !== undefined) {
this.showLogPanel();
}
@@ -129,7 +84,7 @@ export class Logger implements ILogger {
public async writeAndShowError(message: string, ...additionalMessages: string[]): Promise {
this.writeError(message, ...additionalMessages);
- const choice = await vscode.window.showErrorMessage(message, "Show Logs");
+ const choice = await window.showErrorMessage(message, "Show Logs");
if (choice !== undefined) {
this.showLogPanel();
}
@@ -147,7 +102,7 @@ export class Logger implements ILogger {
const actionKeys: string[] = fullActions.map((action) => action.prompt);
- const choice = await vscode.window.showErrorMessage(message, ...actionKeys);
+ const choice = await window.showErrorMessage(message, ...actionKeys);
if (choice) {
for (const action of fullActions) {
if (choice === action.prompt && action.action !== undefined ) {
@@ -158,70 +113,177 @@ export class Logger implements ILogger {
}
}
- // TODO: Make the enum smarter about strings so this goes away.
- private static logLevelNameToValue(logLevelName: string): LogLevel {
- switch (logLevelName.trim().toLowerCase()) {
- case "diagnostic": return LogLevel.Diagnostic;
- case "verbose": return LogLevel.Verbose;
- case "normal": return LogLevel.Normal;
- case "warning": return LogLevel.Warning;
- case "error": return LogLevel.Error;
- case "none": return LogLevel.None;
- default: return LogLevel.Normal;
+ public showLogPanel(): void {
+ this.logChannel.show();
+ }
+
+ private async writeLine(message: string, level: LogLevel = LogLevel.Info): Promise {
+ return new Promise((resolve) => {
+ switch (level) {
+ case LogLevel.Off: break;
+ case LogLevel.Trace: this.logChannel.trace(message); break;
+ case LogLevel.Debug: this.logChannel.debug(message); break;
+ case LogLevel.Info: this.logChannel.info(message); break;
+ case LogLevel.Warning: this.logChannel.warn(message); break;
+ case LogLevel.Error: this.logChannel.error(message); break;
+ default: this.logChannel.appendLine(message); break;
+ }
+ resolve();
+ });
+ }
+}
+
+/** Parses logs received via the legacy OutputChannel to LogOutputChannel with proper severity.
+ *
+ * HACK: This is for legacy compatability and can be removed when https://github.com/microsoft/vscode-languageserver-node/issues/1116 is merged and replaced with a normal LogOutputChannel. We don't use a middleware here because any direct logging calls like client.warn() and server-initiated messages would not be captured by middleware.
+ */
+export class LanguageClientOutputChannelAdapter implements LogOutputChannel {
+ private _channel: LogOutputChannel | undefined;
+ private get channel(): LogOutputChannel {
+ if (!this._channel) {
+ this._channel = window.createOutputChannel(this.channelName, {log: true});
}
+ return this._channel;
}
- public updateLogLevel(logLevelName: string): void {
- this.logLevel = Logger.logLevelNameToValue(logLevelName);
+ /**
+ * Creates an instance of the logging class.
+ *
+ * @param channelName - The name of the output channel.
+ * @param parser - A function that parses a log message and returns a tuple containing the parsed message and its log level, or undefined if the log should be filtered.
+ */
+ constructor(
+ private channelName: string,
+ private parser: (message: string) => [string, LogLevel] | undefined = LanguageClientOutputChannelAdapter.omnisharpLspParser.bind(this)
+ ) {
}
- private showLogPanel(): void {
- this.logChannel.show();
+ public appendLine(message: string): void {
+ this.append(message);
}
- private async openLogFolder(): Promise {
- if (this.logDirectoryCreated) {
- // Open the folder in VS Code since there isn't an easy way to
- // open the folder in the platform's file browser
- await vscode.commands.executeCommand("vscode.openFolder", this.logDirectoryPath, true);
- } else {
- void this.writeAndShowError("Cannot open PowerShell log directory as it does not exist!");
- }
+ public append(message: string): void {
+ const parseResult = this.parser(message);
+ if (parseResult !== undefined) {this.sendLogMessage(...parseResult);}
}
- private static timestampMessage(message: string, level: LogLevel): string {
- const now = new Date();
- return `${now.toLocaleDateString()} ${now.toLocaleTimeString()} [${LogLevel[level].toUpperCase()}] - ${message}${os.EOL}`;
+ /** Converts from Omnisharp logs since middleware for LogMessage does not currently exist **/
+ public static omnisharpLspParser(message: string): [string, LogLevel] {
+ const logLevelMatch = /^\[(?Trace|Debug|Info|Warn|Error) +- \d+:\d+:\d+ [AP]M\] (?.+)/.exec(message);
+ const logLevel: LogLevel = logLevelMatch?.groups?.level
+ ? LogLevel[logLevelMatch.groups.level as keyof typeof LogLevel]
+ : LogLevel.Info;
+ const logMessage = logLevelMatch?.groups?.message ?? message;
+
+ return [logMessage, logLevel];
}
- // TODO: Should we await this function above?
- private async writeLine(message: string, level: LogLevel = LogLevel.Normal): Promise {
- const timestampedMessage = Logger.timestampMessage(message, level);
- this.logChannel.appendLine(timestampedMessage);
- if (this.logLevel !== LogLevel.None) {
- // A simple lock because this function isn't re-entrant.
- while (this.writingLog) {
- await utils.sleep(300);
- }
- try {
- this.writingLog = true;
- if (!this.logDirectoryCreated) {
- this.writeVerbose(`Creating log directory at: '${this.logDirectoryPath}'`);
- await vscode.workspace.fs.createDirectory(this.logDirectoryPath);
- this.logDirectoryCreated = true;
- }
- let log = new Uint8Array();
- if (await utils.checkIfFileExists(this.logFilePath)) {
- log = await vscode.workspace.fs.readFile(this.logFilePath);
- }
- await vscode.workspace.fs.writeFile(
- this.logFilePath,
- Buffer.concat([log, Buffer.from(timestampedMessage)]));
- } catch (err) {
- console.log(`Error writing to vscode-powershell log file: ${err}`);
- } finally {
- this.writingLog = false;
- }
+ protected sendLogMessage(message: string, level: LogLevel): void {
+ switch (level) {
+ case LogLevel.Trace:
+ this.channel.trace(message);
+ break;
+ case LogLevel.Debug:
+ this.channel.debug(message);
+ break;
+ case LogLevel.Info:
+ this.channel.info(message);
+ break;
+ case LogLevel.Warning:
+ this.channel.warn(message);
+ break;
+ case LogLevel.Error:
+ this.channel.error(message);
+ break;
+ default:
+ this.channel.error("!UNKNOWN LOG LEVEL!: " + message);
+ break;
}
}
+
+ // #region Passthru Implementation
+ public get name(): string {
+ // prevents the window from being created unless we get a log request
+ return this.channelName;
+ }
+ public get logLevel(): LogLevel {
+ return this.channel.logLevel;
+ }
+ replace(value: string): void {
+ this.channel.replace(value);
+ }
+ show(_column?: undefined, preserveFocus?: boolean): void {
+ this.channel.show(preserveFocus);
+ }
+ public get onDidChangeLogLevel(): Event {
+ return this.channel.onDidChangeLogLevel;
+ }
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ public trace(message: string, ...args: any[]): void {
+ this.channel.trace(message, ...args);
+ }
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ public debug(message: string, ...args: any[]): void {
+ this.channel.debug(message, ...args);
+ }
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ public info(message: string, ...args: any[]): void {
+ this.channel.info(message, ...args);
+ }
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ public warn(message: string, ...args: any[]): void {
+ this.channel.warn(message, ...args);
+ }
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ public error(message: string, ...args: any[]): void {
+ this.channel.error(message, ...args);
+ }
+ public clear(): void {
+ this.channel.clear();
+ }
+ public hide(): void {
+ this.channel.hide();
+ }
+ public dispose(): void {
+ this.channel.dispose();
+ }
+ // #endregion
+}
+
+/** Special parsing for PowerShell Editor Services LSP messages since the LogLevel cannot be read due to vscode
+ * LanguageClient Limitations (https://github.com/microsoft/vscode-languageserver-node/issues/1116)
+ */
+export function PsesParser(message: string): [string, LogLevel] {
+ const logLevelMatch = /^<(?Trace|Debug|Info|Warning|Error)>(?.+)/.exec(message);
+ const logLevel: LogLevel = logLevelMatch?.groups?.level
+ ? LogLevel[logLevelMatch.groups.level as keyof typeof LogLevel]
+ : LogLevel.Info;
+ const logMessage = logLevelMatch?.groups?.message ?? message;
+
+ return ["[PSES] " + logMessage, logLevel];
+}
+
+/** Lsp Trace Parser that does some additional parsing and formatting to make it look nicer */
+export function LspTraceParser(message: string): [string, LogLevel] {
+ let [parsedMessage, level] = LanguageClientOutputChannelAdapter.omnisharpLspParser(message);
+ if (parsedMessage.startsWith("Sending ")) {
+ parsedMessage = parsedMessage.replace("Sending", "➡️");
+ level = LogLevel.Debug;
+ }
+ if (parsedMessage.startsWith("Received ")) {
+ parsedMessage = parsedMessage.replace("Received", "⬅️");
+ level = LogLevel.Debug;
+ }
+ if (parsedMessage.startsWith("Params:")
+ || parsedMessage.startsWith("Result:")
+ ) {
+ level = LogLevel.Trace;
+ }
+
+ // These are PSES messages that get logged to the output channel anyways so we drop these to trace for easy noise filtering
+ if (parsedMessage.startsWith("⬅️ notification 'window/logMessage'")) {
+ level = LogLevel.Trace;
+ }
+
+ return [parsedMessage.trimEnd(), level];
}
diff --git a/src/process.ts b/src/process.ts
index d363c8e2ee..052da2b8bf 100644
--- a/src/process.ts
+++ b/src/process.ts
@@ -30,6 +30,7 @@ export class PowerShellProcess {
private isTemp: boolean,
private shellIntegrationEnabled: boolean,
private logger: ILogger,
+ private logDirectoryPath: vscode.Uri,
private startPsesArgs: string,
private sessionFilePath: vscode.Uri,
private sessionSettings: Settings) {
@@ -51,7 +52,7 @@ export class PowerShellProcess {
: "";
this.startPsesArgs +=
- `-LogPath '${utils.escapeSingleQuotes(this.logger.logDirectoryPath.fsPath)}' ` +
+ `-LogPath '${utils.escapeSingleQuotes(this.logDirectoryPath.fsPath)}' ` +
`-SessionDetailsPath '${utils.escapeSingleQuotes(this.sessionFilePath.fsPath)}' ` +
`-FeatureFlags @(${featureFlags}) `;
@@ -89,13 +90,13 @@ export class PowerShellProcess {
startEditorServices);
} else {
// Otherwise use -EncodedCommand for better quote support.
- this.logger.writeVerbose("Using Base64 -EncodedCommand but logging as -Command equivalent.");
+ this.logger.writeDebug("Using Base64 -EncodedCommand but logging as -Command equivalent.");
powerShellArgs.push(
"-EncodedCommand",
Buffer.from(startEditorServices, "utf16le").toString("base64"));
}
- this.logger.writeVerbose(`Starting process: ${this.exePath} ${powerShellArgs.slice(0, -2).join(" ")} -Command ${startEditorServices}`);
+ this.logger.writeDebug(`Starting process: ${this.exePath} ${powerShellArgs.slice(0, -2).join(" ")} -Command ${startEditorServices}`);
// Make sure no old session file exists
await this.deleteSessionFile(this.sessionFilePath);
@@ -173,7 +174,7 @@ export class PowerShellProcess {
}
public dispose(): void {
- this.logger.writeVerbose(`Disposing PowerShell process with PID: ${this.pid}`);
+ this.logger.writeDebug(`Disposing PowerShell process with PID: ${this.pid}`);
void this.deleteSessionFile(this.sessionFilePath);
@@ -226,7 +227,7 @@ export class PowerShellProcess {
const warnAt = numOfTries - PowerShellProcess.warnUserThreshold;
// Check every second.
- this.logger.writeVerbose(`Waiting for session file: ${this.sessionFilePath}`);
+ this.logger.writeDebug(`Waiting for session file: ${this.sessionFilePath}`);
for (let i = numOfTries; i > 0; i--) {
if (cancellationToken.isCancellationRequested) {
this.logger.writeWarning("Canceled while waiting for session file.");
@@ -239,7 +240,7 @@ export class PowerShellProcess {
}
if (await utils.checkIfFileExists(this.sessionFilePath)) {
- this.logger.writeVerbose("Session file found.");
+ this.logger.writeDebug("Session file found.");
return await this.readSessionFile(this.sessionFilePath);
}
diff --git a/src/session.ts b/src/session.ts
index a5f4314845..0b1037a116 100644
--- a/src/session.ts
+++ b/src/session.ts
@@ -6,7 +6,7 @@ import path = require("path");
import vscode = require("vscode");
import TelemetryReporter, { TelemetryEventProperties, TelemetryEventMeasurements } from "@vscode/extension-telemetry";
import { Message } from "vscode-jsonrpc";
-import { ILogger } from "./logging";
+import { ILogger, LanguageClientOutputChannelAdapter, LspTraceParser, PsesParser } from "./logging";
import { PowerShellProcess } from "./process";
import { Settings, changeSetting, getSettings, getEffectiveConfigurationTarget, validateCwdSetting } from "./settings";
import utils = require("./utils");
@@ -14,7 +14,8 @@ import utils = require("./utils");
import {
CloseAction, CloseHandlerResult, DocumentSelector, ErrorAction, ErrorHandlerResult,
LanguageClientOptions, Middleware, NotificationType,
- RequestType0, ResolveCodeLensSignature, RevealOutputChannelOn
+ RequestType0, ResolveCodeLensSignature,
+ RevealOutputChannelOn,
} from "vscode-languageclient";
import { LanguageClient, StreamInfo } from "vscode-languageclient/node";
@@ -93,6 +94,7 @@ export class SessionManager implements Middleware {
private startCancellationTokenSource: vscode.CancellationTokenSource | undefined;
private suppressRestartPrompt = false;
private versionDetails: IPowerShellVersionDetails | undefined;
+ private traceLogLevelHandler?: vscode.Disposable;
constructor(
private extensionContext: vscode.ExtensionContext,
@@ -104,7 +106,6 @@ export class SessionManager implements Middleware {
hostVersion: string,
publisher: string,
private telemetryReporter: TelemetryReporter) {
-
// Create the language status item
this.languageStatusItem = this.createStatusBarItem();
// We have to override the scheme because it defaults to
@@ -161,7 +162,7 @@ export class SessionManager implements Middleware {
return;
case SessionStatus.Running:
// We're started, just return.
- this.logger.writeVerbose("Already started.");
+ this.logger.writeDebug("Already started.");
return;
case SessionStatus.Busy:
// We're started but busy so notify and return.
@@ -170,12 +171,12 @@ export class SessionManager implements Middleware {
return;
case SessionStatus.Stopping:
// Wait until done stopping, then start.
- this.logger.writeVerbose("Still stopping.");
+ this.logger.writeDebug("Still stopping.");
await this.waitWhileStopping();
break;
case SessionStatus.Failed:
// Try to start again.
- this.logger.writeVerbose("Previously failed, starting again.");
+ this.logger.writeDebug("Previously failed, starting again.");
break;
}
@@ -277,6 +278,8 @@ export class SessionManager implements Middleware {
this.startCancellationTokenSource?.dispose();
this.startCancellationTokenSource = undefined;
this.sessionDetails = undefined;
+ this.traceLogLevelHandler?.dispose();
+ this.traceLogLevelHandler = undefined;
this.setSessionStatus("Not Started", SessionStatus.NotStarted);
}
@@ -291,7 +294,7 @@ export class SessionManager implements Middleware {
if (exeNameOverride) {
// Reset the version and PowerShell details since we're launching a
// new executable.
- this.logger.writeVerbose(`Starting with executable overriden to: ${exeNameOverride}`);
+ this.logger.writeDebug(`Starting with executable overriden to: ${exeNameOverride}`);
this.sessionSettings.powerShellDefaultVersion = exeNameOverride;
this.versionDetails = undefined;
this.PowerShellExeDetails = undefined;
@@ -335,7 +338,6 @@ export class SessionManager implements Middleware {
// handler when the process is disposed).
this.debugSessionProcess?.dispose();
this.debugEventHandler?.dispose();
-
if (this.PowerShellExeDetails === undefined) {
return Promise.reject(new Error("Required PowerShellExeDetails undefined!"));
}
@@ -353,6 +355,7 @@ export class SessionManager implements Middleware {
true,
false,
this.logger,
+ this.extensionContext.logUri,
this.getEditorServicesArgs(bundledModulesPath, this.PowerShellExeDetails) + "-DebugServiceOnly ",
this.getNewSessionFilePath(),
this.sessionSettings);
@@ -451,34 +454,58 @@ export class SessionManager implements Middleware {
}
}
- private async onConfigurationUpdated(): Promise {
+ /** There are some changes we cannot "hot" set, so these require a restart of the session */
+ private async restartOnCriticalConfigChange(changeEvent: vscode.ConfigurationChangeEvent): Promise {
+ if (this.suppressRestartPrompt) {return;}
+ if (this.sessionStatus !== SessionStatus.Running) {return;}
+
+ // Restart not needed if shell integration is enabled but the shell is backgrounded.
const settings = getSettings();
- const shellIntegrationEnabled = vscode.workspace.getConfiguration("terminal.integrated.shellIntegration").get("enabled");
- this.logger.updateLogLevel(settings.developer.editorServicesLogLevel);
+ if (changeEvent.affectsConfiguration("terminal.integrated.shellIntegration.enabled")) {
+ const shellIntegrationEnabled = vscode.workspace.getConfiguration("terminal.integrated.shellIntegration").get("enabled") ?? false;
+ if (shellIntegrationEnabled && !settings.integratedConsole.startInBackground) {
+ return this.restartWithPrompt();
+ }
+ }
+
+ // Early return if the change doesn't affect the PowerShell extension settings from this point forward
+ if (!changeEvent.affectsConfiguration("powershell")) {return;}
+
// Detect any setting changes that would affect the session.
- if (!this.suppressRestartPrompt
- && this.sessionStatus === SessionStatus.Running
- && ((shellIntegrationEnabled !== this.shellIntegrationEnabled
- && !settings.integratedConsole.startInBackground)
- || settings.cwd !== this.sessionSettings.cwd
+ const coldRestartSettingNames = [
+ "developer.traceLsp",
+ "developer.traceDap",
+ "developer.editorServicesLogLevel",
+ ];
+ for (const settingName of coldRestartSettingNames) {
+ if (changeEvent.affectsConfiguration("powershell" + "." + settingName)) {
+ return this.restartWithPrompt();
+ }
+ }
+
+ // TODO: Migrate these to affectsConfiguration style above
+ if (settings.cwd !== this.sessionSettings.cwd
|| settings.powerShellDefaultVersion !== this.sessionSettings.powerShellDefaultVersion
- || settings.developer.editorServicesLogLevel !== this.sessionSettings.developer.editorServicesLogLevel
|| settings.developer.bundledModulesPath !== this.sessionSettings.developer.bundledModulesPath
|| settings.developer.editorServicesWaitForDebugger !== this.sessionSettings.developer.editorServicesWaitForDebugger
|| settings.developer.setExecutionPolicy !== this.sessionSettings.developer.setExecutionPolicy
|| settings.integratedConsole.useLegacyReadLine !== this.sessionSettings.integratedConsole.useLegacyReadLine
|| settings.integratedConsole.startInBackground !== this.sessionSettings.integratedConsole.startInBackground
- || settings.integratedConsole.startLocation !== this.sessionSettings.integratedConsole.startLocation)) {
+ || settings.integratedConsole.startLocation !== this.sessionSettings.integratedConsole.startLocation
+ ) {
+ return this.restartWithPrompt();
+ }
+ }
- this.logger.writeVerbose("Settings changed, prompting to restart...");
- const response = await vscode.window.showInformationMessage(
- "The PowerShell runtime configuration has changed, would you like to start a new session?",
- "Yes", "No");
+ private async restartWithPrompt(): Promise {
+ this.logger.writeDebug("Settings changed, prompting to restart...");
+ const response = await vscode.window.showInformationMessage(
+ "The PowerShell runtime configuration has changed, would you like to start a new session?",
+ "Yes", "No");
- if (response === "Yes") {
- await this.restartSession();
- }
+ if (response === "Yes") {
+ await this.restartSession();
}
}
@@ -486,14 +513,14 @@ export class SessionManager implements Middleware {
this.registeredCommands = [
vscode.commands.registerCommand("PowerShell.RestartSession", async () => { await this.restartSession(); }),
vscode.commands.registerCommand(this.ShowSessionMenuCommandName, async () => { await this.showSessionMenu(); }),
- vscode.workspace.onDidChangeConfiguration(async () => { await this.onConfigurationUpdated(); }),
+ vscode.workspace.onDidChangeConfiguration((e) => this.restartOnCriticalConfigChange(e)),
vscode.commands.registerCommand(
"PowerShell.ShowSessionConsole", (isExecute?: boolean) => { this.showSessionTerminal(isExecute); })
];
}
private async findPowerShell(): Promise {
- this.logger.writeVerbose("Finding PowerShell...");
+ this.logger.writeDebug("Finding PowerShell...");
const powershellExeFinder = new PowerShellExeFinder(
this.platformDetails,
this.sessionSettings.powerShellAdditionalExePaths,
@@ -539,6 +566,7 @@ export class SessionManager implements Middleware {
false,
this.shellIntegrationEnabled,
this.logger,
+ this.extensionContext.logUri,
this.getEditorServicesArgs(bundledModulesPath, powerShellExeDetails),
this.getNewSessionFilePath(),
this.sessionSettings);
@@ -591,7 +619,7 @@ export class SessionManager implements Middleware {
}
private sessionStarted(sessionDetails: IEditorServicesSessionDetails): boolean {
- this.logger.writeVerbose(`Session details: ${JSON.stringify(sessionDetails, undefined, 2)}`);
+ this.logger.writeDebug(`Session details: ${JSON.stringify(sessionDetails, undefined, 2)}`);
if (sessionDetails.status === "started") { // Successful server start with a session file
return true;
}
@@ -610,7 +638,7 @@ export class SessionManager implements Middleware {
}
private async startLanguageClient(sessionDetails: IEditorServicesSessionDetails): Promise {
- this.logger.writeVerbose("Connecting to language service...");
+ this.logger.writeDebug("Connecting to language service...");
const connectFunc = (): Promise => {
return new Promise(
(resolve, _reject) => {
@@ -618,11 +646,12 @@ export class SessionManager implements Middleware {
socket.on(
"connect",
() => {
- this.logger.writeVerbose("Language service connected.");
+ this.logger.writeDebug("Language service connected.");
resolve({ writer: socket, reader: socket });
});
});
};
+
const clientOptions: LanguageClientOptions = {
documentSelector: this.documentSelector,
synchronize: {
@@ -646,9 +675,11 @@ export class SessionManager implements Middleware {
// hangs up (ECONNRESET errors).
error: (_error: Error, _message: Message, _count: number): ErrorHandlerResult => {
// TODO: Is there any error worth terminating on?
+ this.logger.writeError(`${_error.name}: ${_error.message} ${_error.cause}`);
return { action: ErrorAction.Continue };
},
closed: (): CloseHandlerResult => {
+ this.logger.write("Language service connection closed.");
// We have our own restart experience
return {
action: CloseAction.DoNotRestart,
@@ -656,9 +687,11 @@ export class SessionManager implements Middleware {
};
},
},
- revealOutputChannelOn: RevealOutputChannelOn.Never,
middleware: this,
- traceOutputChannel: vscode.window.createOutputChannel("PowerShell Trace - LSP", {log: true}),
+ traceOutputChannel: new LanguageClientOutputChannelAdapter("PowerShell: Trace LSP", LspTraceParser),
+ // This is named the same as the Client log to merge the logs, but will be handled and disposed separately.
+ outputChannel: new LanguageClientOutputChannelAdapter("PowerShell", PsesParser),
+ revealOutputChannelOn: RevealOutputChannelOn.Never
};
const languageClient = new LanguageClient("powershell", "PowerShell Editor Services Client", connectFunc, clientOptions);
@@ -763,8 +796,8 @@ Type 'help' to get help.
&& this.extensionContext.extensionMode === vscode.ExtensionMode.Development) {
editorServicesArgs += "-WaitForDebugger ";
}
-
- editorServicesArgs += `-LogLevel '${this.sessionSettings.developer.editorServicesLogLevel}' `;
+ const logLevel = vscode.workspace.getConfiguration("powershell.developer").get("editorServicesLogLevel");
+ editorServicesArgs += `-LogLevel '${logLevel}' `;
return editorServicesArgs;
}
@@ -836,7 +869,7 @@ Type 'help' to get help.
}
private setSessionStatus(detail: string, status: SessionStatus): void {
- this.logger.writeVerbose(`Session status changing from '${this.sessionStatus}' to '${status}'.`);
+ this.logger.writeDebug(`Session status changing from '${this.sessionStatus}' to '${status}'.`);
this.sessionStatus = status;
this.languageStatusItem.text = "$(terminal-powershell)";
this.languageStatusItem.detail = "PowerShell";
diff --git a/src/settings.ts b/src/settings.ts
index 9c2ef38452..f29079846a 100644
--- a/src/settings.ts
+++ b/src/settings.ts
@@ -56,15 +56,6 @@ export enum PipelineIndentationStyle {
None = "None",
}
-export enum LogLevel {
- Diagnostic = "Diagnostic",
- Verbose = "Verbose",
- Normal = "Normal",
- Warning = "Warning",
- Error = "Error",
- None = "None",
-}
-
export enum CommentType {
Disabled = "Disabled",
BlockComment = "BlockComment",
@@ -120,7 +111,6 @@ class DeveloperSettings extends PartialSettings {
// From `/out/main.js` we go to the directory before and
// then into the other repo.
bundledModulesPath = "../../PowerShellEditorServices/module";
- editorServicesLogLevel = LogLevel.Normal;
editorServicesWaitForDebugger = false;
setExecutionPolicy = true;
waitForSessionFileTimeoutSeconds = 240;
@@ -209,7 +199,7 @@ export async function changeSetting(
configurationTarget: vscode.ConfigurationTarget | boolean | undefined,
logger: ILogger | undefined): Promise {
- logger?.writeVerbose(`Changing '${settingName}' at scope '${configurationTarget}' to '${newValue}'.`);
+ logger?.writeDebug(`Changing '${settingName}' at scope '${configurationTarget}' to '${newValue}'.`);
try {
const configuration = vscode.workspace.getConfiguration(utils.PowerShellLanguageId);
@@ -242,7 +232,7 @@ export async function getChosenWorkspace(logger: ILogger | undefined): Promise console.log(newValue));
+ */
+
+// Because we actually do use the constraint in the callback
+// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters
+export function onSettingChange(
+ section: string,
+ setting: string,
+ action: (newValue: T | undefined) => void,
+ options?: onSettingChangeOptions,
+): vscode.Disposable {
+ const settingPath = `${section}.${setting}`;
+ const disposable = vscode.workspace.onDidChangeConfiguration(e => {
+ if (!e.affectsConfiguration(settingPath, options?.scope)) { return; }
+
+ doOnSettingsChange(section, setting, action, options?.scope);
+ if (options?.run === "once") {
+ disposable.dispose(); // Javascript black magic, referring to an outer reference before it exists
+ }
+ });
+ if (options?.run === "now") {
+ doOnSettingsChange(section, setting, action, options.scope);
+ }
+ return disposable;
+}
+
+/** Implementation is separate to avoid duplicate code for run now */
+
+// Because we actually do use the constraint in the callback
+// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters
+function doOnSettingsChange(
+ section: string,
+ setting: string,
+ action: (newValue: T | undefined) => void,
+ scope?: vscode.ConfigurationScope,
+): void {
+ const value = vscode.workspace.getConfiguration(section, scope).get(setting);
+ action(value);
+}
+
+/**
+ * Invokes the specified action when a PowerShell setting changes. Convenience function for `onSettingChange`
+ * @param setting a string representation of the setting you wish to evaluate, e.g. `trace.server`
+ * @param action the action to take when the setting changes
+ * @param scope the scope in which the vscode setting should be evaluated.n
+ * @returns a Disposable object that can be used to stop listening for changes
+ * @example
+ * onPowerShellSettingChange("settingName", (newValue) => console.log(newValue));
+ */
+
+// Because we actually do use the constraint in the callback
+// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters
+export function onPowerShellSettingChange(
+ setting: string,
+ action: (newValue: T | undefined) => void,
+ options?: onSettingChangeOptions
+
+): vscode.Disposable {
+ const section = "powershell";
+ return onSettingChange(section, setting, action, options);
+}
diff --git a/test/core/paths.test.ts b/test/core/paths.test.ts
index 15f60f5bd1..703b22a53f 100644
--- a/test/core/paths.test.ts
+++ b/test/core/paths.test.ts
@@ -9,9 +9,11 @@ import { checkIfDirectoryExists, checkIfFileExists, ShellIntegrationScript } fro
describe("Path assumptions", function () {
let globalStorageUri: vscode.Uri;
+ let logUri: vscode.Uri;
before(async () => {
const extension: IPowerShellExtensionClient = await utils.ensureEditorServicesIsConnected();
globalStorageUri = extension.getStorageUri();
+ logUri = extension.getLogUri();
});
it("Creates the session folder at the correct path", async function () {
@@ -19,7 +21,7 @@ describe("Path assumptions", function () {
});
it("Creates the log folder at the correct path", async function () {
- assert(await checkIfDirectoryExists(vscode.Uri.joinPath(globalStorageUri, "logs")));
+ assert(await checkIfDirectoryExists(logUri));
});
it("Finds the Terminal Shell Integration Script", async function () {
diff --git a/test/utils.ts b/test/utils.ts
index 7d601aadf2..e62de2d87e 100644
--- a/test/utils.ts
+++ b/test/utils.ts
@@ -25,10 +25,10 @@ export class TestLogger implements ILogger {
writeAndShowInformation(_message: string, ..._additionalMessages: string[]): Promise {
return Promise.resolve();
}
- writeDiagnostic(_message: string, ..._additionalMessages: string[]): void {
+ writeTrace(_message: string, ..._additionalMessages: string[]): void {
return;
}
- writeVerbose(_message: string, ..._additionalMessages: string[]): void {
+ writeDebug(_message: string, ..._additionalMessages: string[]): void {
return;
}
writeWarning(_message: string, ..._additionalMessages: string[]): void {