Skip to content

Commit 3b3955a

Browse files
committed
Add output adapters, LSP restart settings
1 parent 69e3209 commit 3b3955a

File tree

6 files changed

+325
-119
lines changed

6 files changed

+325
-119
lines changed

package.json

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -953,6 +953,27 @@
953953
"default": [],
954954
"markdownDescription": "An array of strings that enable experimental features in the PowerShell extension. **No flags are currently available!**"
955955
},
956+
"powershell.developer.traceLsp": {
957+
"type": "boolean",
958+
"default": false,
959+
"markdownDescription": "Traces the LSP 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!**"
960+
},
961+
"powershell.developer.traceDap": {
962+
"type": "boolean",
963+
"default": false,
964+
"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!**"
965+
},
966+
"powershell.trace.server": {
967+
"type": "string",
968+
"enum": [
969+
"off",
970+
"messages",
971+
"verbose"
972+
],
973+
"default": "off",
974+
"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!**",
975+
"markdownDeprecationMessage": "Please use the `powershell.developer.traceLsp` setting instead and control verbosity via the Output Window settings."
976+
},
956977
"powershell.developer.waitForSessionFileTimeoutSeconds": {
957978
"type": "number",
958979
"default": 240,
@@ -1002,21 +1023,6 @@
10021023
"type": "boolean",
10031024
"default": false,
10041025
"markdownDescription": "Show buttons in the editor's title bar for moving the terminals pane (with the PowerShell Extension Terminal) around."
1005-
},
1006-
"powershell.trace.server": {
1007-
"type": "string",
1008-
"enum": [
1009-
"off",
1010-
"messages",
1011-
"verbose"
1012-
],
1013-
"default": "off",
1014-
"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!**"
1015-
},
1016-
"powershell.trace.dap": {
1017-
"type": "boolean",
1018-
"default": false,
1019-
"markdownDescription": "Traces the communication between VS Code and the PowerShell Editor Services [DAP Server](https://microsoft.github.io/debug-adapter-protocol/). **Only meant for extension developers and issue troubleshooting!**"
10201026
}
10211027
}
10221028
},

src/features/DebugSession.ts

Lines changed: 10 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ import { PowerShellProcess } from "../process";
3737
import { IEditorServicesSessionDetails, SessionManager } from "../session";
3838
import { getSettings } from "../settings";
3939
import path from "path";
40-
import { checkIfFileExists, onPowerShellSettingChange } from "../utils";
40+
import { checkIfFileExists } from "../utils";
4141

4242
export const StartDebuggerNotificationType =
4343
new NotificationType<void>("powerShell/startDebugger");
@@ -606,38 +606,26 @@ export class DebugSessionFeature extends LanguageClientConsumer
606606

607607
class PowerShellDebugAdapterTrackerFactory implements DebugAdapterTrackerFactory, Disposable {
608608
disposables: Disposable[] = [];
609-
dapLogEnabled: boolean = workspace.getConfiguration("powershell").get<boolean>("trace.dap") ?? false;
610-
constructor(private adapterName = "PowerShell") {
611-
this.disposables.push(
612-
onPowerShellSettingChange("trace.dap", (visible:boolean | undefined) => {this.setLogVisibility(visible);})
613-
);
614-
}
609+
constructor(private adapterName = "PowerShell") {}
610+
615611

616-
/* We want to use a shared output log for separate debug sessions as usually only one is running at a time and we
617-
* dont need an output window for every debug session. We also want to leave it active so user can copy and paste
618-
* even on run end. When user changes the setting and disables it getter will return undefined, which will result
612+
_log: LogOutputChannel | undefined;
613+
/** Lazily creates a {@link LogOutputChannel} for debug tracing, and presents it only when DAP logging is enabled.
614+
*
615+
* We want to use a shared output log for separate debug sessions as usually only one is running at a time and we
616+
* dont need an output window for every debug session. We also want to leave it active so user can copy and paste
617+
* even on run end. When user changes the setting and disables it getter will return undefined, which will result
619618
* in a noop for the logging activities, effectively pausing logging but not disposing the output channel. If the
620619
* user re-enables, then logging resumes.
621620
*/
622-
_log: LogOutputChannel | undefined;
623621
get log(): LogOutputChannel | undefined {
624-
if (this.dapLogEnabled && this._log === undefined) {
622+
if (workspace.getConfiguration("powershell.developer").get<boolean>("traceDap") && this._log === undefined) {
625623
this._log = window.createOutputChannel(`${this.adapterName}: Trace DAP`, { log: true });
626624
this.disposables.push(this._log);
627625
}
628626
return this._log;
629627
}
630628

631-
private setLogVisibility(visible?: boolean): void {
632-
if (this.log !== undefined) {
633-
if (visible) {
634-
this.log.show(true);
635-
} else {
636-
this.log.hide();
637-
}
638-
}
639-
}
640-
641629
createDebugAdapterTracker(session: DebugSession): DebugAdapterTracker {
642630
const sessionInfo = `${this.adapterName} Debug Session: ${session.name} [${session.id}]`;
643631
return {
@@ -665,8 +653,6 @@ class PowerShellDebugAdapterTrackerFactory implements DebugAdapterTrackerFactory
665653
};
666654
}
667655

668-
669-
670656
dispose(): void {
671657
this.disposables.forEach(d => d.dispose());
672658
}

src/logging.ts

Lines changed: 157 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Copyright (c) Microsoft Corporation.
22
// Licensed under the MIT License.
33

4-
import { LogOutputChannel, Uri, Disposable, LogLevel, window, commands } from "vscode";
4+
import { LogOutputChannel, Uri, Disposable, LogLevel, window, commands, Event } from "vscode";
55

66
/** Interface for logging operations. New features should use this interface for the "type" of logger.
77
* This will allow for easy mocking of the logger during unit tests.
@@ -160,3 +160,159 @@ export class Logger implements ILogger {
160160
});
161161
}
162162
}
163+
164+
/** Parses logs received via the legacy OutputChannel to LogOutputChannel with proper severity.
165+
*
166+
* 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() would not be captured by middleware.
167+
*/
168+
export class LanguageClientOutputChannelAdapter implements LogOutputChannel {
169+
private channel: LogOutputChannel;
170+
constructor(channel: LogOutputChannel) {
171+
this.channel = channel;
172+
}
173+
174+
public appendLine(message: string): void {
175+
this.append(message);
176+
}
177+
178+
public append(message: string): void {
179+
const [parsedMessage, level] = this.parse(message);
180+
this.sendLogMessage(parsedMessage, level);
181+
}
182+
183+
protected parse(message: string): [string, LogLevel] {
184+
const logLevelMatch = /^\[(?<level>Trace|Debug|Info|Warn|Error) +- \d+:\d+:\d+ [AP]M\] (?<message>.+)/.exec(message);
185+
if (logLevelMatch) {
186+
const { level, message } = logLevelMatch.groups!;
187+
let logLevel: LogLevel;
188+
switch (level) {
189+
case "Trace":
190+
logLevel = LogLevel.Trace;
191+
break;
192+
case "Debug":
193+
logLevel = LogLevel.Debug;
194+
break;
195+
case "Info":
196+
logLevel = LogLevel.Info;
197+
break;
198+
case "Warn":
199+
logLevel = LogLevel.Warning;
200+
break;
201+
case "Error":
202+
logLevel = LogLevel.Error;
203+
break;
204+
default:
205+
logLevel = LogLevel.Info;
206+
break;
207+
}
208+
return [message, logLevel];
209+
} else {
210+
return [message, LogLevel.Info];
211+
}
212+
}
213+
214+
protected sendLogMessage(message: string, level: LogLevel): void {
215+
switch (level) {
216+
case LogLevel.Trace:
217+
this.channel.trace(message);
218+
break;
219+
case LogLevel.Debug:
220+
this.channel.debug(message);
221+
break;
222+
case LogLevel.Info:
223+
this.channel.info(message);
224+
break;
225+
case LogLevel.Warning:
226+
this.channel.warn(message);
227+
break;
228+
case LogLevel.Error:
229+
this.channel.error(message);
230+
break;
231+
default:
232+
this.channel.trace(message);
233+
break;
234+
}
235+
}
236+
237+
// #region Passthru Implementation
238+
public get name(): string {
239+
return this.channel.name;
240+
}
241+
public get logLevel(): LogLevel {
242+
return this.channel.logLevel;
243+
}
244+
replace(value: string): void {
245+
this.channel.replace(value);
246+
}
247+
show(_column?: undefined, preserveFocus?: boolean): void {
248+
this.channel.show(preserveFocus);
249+
}
250+
public get onDidChangeLogLevel(): Event<LogLevel> {
251+
return this.channel.onDidChangeLogLevel;
252+
}
253+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
254+
public trace(message: string, ...args: any[]): void {
255+
this.channel.trace(message, ...args);
256+
}
257+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
258+
public debug(message: string, ...args: any[]): void {
259+
this.channel.debug(message, ...args);
260+
}
261+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
262+
public info(message: string, ...args: any[]): void {
263+
this.channel.info(message, ...args);
264+
}
265+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
266+
public warn(message: string, ...args: any[]): void {
267+
this.channel.warn(message, ...args);
268+
}
269+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
270+
public error(message: string, ...args: any[]): void {
271+
this.channel.error(message, ...args);
272+
}
273+
public clear(): void {
274+
this.channel.clear();
275+
}
276+
public hide(): void {
277+
this.channel.hide();
278+
}
279+
public dispose(): void {
280+
this.channel.dispose();
281+
}
282+
// #endregion
283+
}
284+
285+
/** Overrides the severity of some LSP traces to be more logical */
286+
export class LanguageClientTraceFormatter extends LanguageClientOutputChannelAdapter {
287+
288+
public override appendLine(message: string): void {
289+
this.append(message);
290+
}
291+
292+
public override append(message: string): void {
293+
// eslint-disable-next-line prefer-const
294+
let [parsedMessage, level] = this.parse(message);
295+
296+
// Override some LSP traces to be more logical
297+
if (parsedMessage.startsWith("Sending ")) {
298+
parsedMessage = parsedMessage.replace("Sending", "▶️");
299+
level = LogLevel.Debug;
300+
}
301+
if (parsedMessage.startsWith("Received ")) {
302+
parsedMessage = parsedMessage.replace("Received", "◀️");
303+
level = LogLevel.Debug;
304+
}
305+
if (parsedMessage.startsWith("Params:")
306+
|| parsedMessage.startsWith("Result:")
307+
) {
308+
level = LogLevel.Trace;
309+
}
310+
311+
// These are PSES messages we don't really need to see so we drop these to trace
312+
if (parsedMessage.startsWith("◀️ notification 'window/logMessage'")) {
313+
level = LogLevel.Trace;
314+
}
315+
316+
this.sendLogMessage(parsedMessage.trimEnd(), level);
317+
}
318+
}

0 commit comments

Comments
 (0)