Skip to content

Commit 9cd6b91

Browse files
Enable ability to trace DAP messages at client side (PowerShell#5064)
* Enable ability to trace DAP messages at client side * Update setting description --------- Co-authored-by: Andy Jordan <2226434+andyleejordan@users.noreply.github.com>
1 parent e61b612 commit 9cd6b91

File tree

4 files changed

+84
-7
lines changed

4 files changed

+84
-7
lines changed

package.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1011,7 +1011,12 @@
10111011
"verbose"
10121012
],
10131013
"default": "off",
1014-
"markdownDescription": "Traces the communication between VS Code and the PowerShell Editor Services language server. **This setting is only meant for extension developers!**"
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/). **This setting is only meant for extension developers and issue troubleshooting!**"
10151020
}
10161021
}
10171022
},

src/features/DebugSession.ts

Lines changed: 76 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,10 @@ import {
2222
InputBoxOptions,
2323
QuickPickItem,
2424
QuickPickOptions,
25-
DebugConfigurationProviderTriggerKind
25+
DebugConfigurationProviderTriggerKind,
26+
DebugAdapterTrackerFactory,
27+
DebugAdapterTracker,
28+
LogOutputChannel
2629
} from "vscode";
2730
import type { DebugProtocol } from "@vscode/debugprotocol";
2831
import { NotificationType, RequestType } from "vscode-languageclient";
@@ -126,6 +129,7 @@ export class DebugSessionFeature extends LanguageClientConsumer
126129
private tempSessionDetails: IEditorServicesSessionDetails | undefined;
127130
private commands: Disposable[] = [];
128131
private handlers: Disposable[] = [];
132+
private adapterName = "PowerShell";
129133

130134
constructor(context: ExtensionContext, private sessionManager: SessionManager, private logger: ILogger) {
131135
super();
@@ -165,12 +169,17 @@ export class DebugSessionFeature extends LanguageClientConsumer
165169
DebugConfigurationProviderTriggerKind.Dynamic
166170
];
167171

172+
168173
for (const triggerKind of triggers) {
169174
context.subscriptions.push(
170-
debug.registerDebugConfigurationProvider("PowerShell", this, triggerKind));
175+
debug.registerDebugConfigurationProvider(this.adapterName, this, triggerKind)
176+
);
171177
}
172178

173-
context.subscriptions.push(debug.registerDebugAdapterDescriptorFactory("PowerShell", this));
179+
context.subscriptions.push(
180+
debug.registerDebugAdapterTrackerFactory(this.adapterName, new PowerShellDebugAdapterTrackerFactory(this.adapterName)),
181+
debug.registerDebugAdapterDescriptorFactory(this.adapterName, this)
182+
);
174183
}
175184

176185
public override onLanguageClientSet(languageClient: LanguageClient): void {
@@ -595,6 +604,70 @@ export class DebugSessionFeature extends LanguageClientConsumer
595604
}
596605
}
597606

607+
class PowerShellDebugAdapterTrackerFactory implements DebugAdapterTrackerFactory, Disposable {
608+
disposables: Disposable[] = [];
609+
dapLogEnabled: boolean = workspace.getConfiguration("powershell").get<boolean>("trace.dap") ?? false;
610+
constructor(private adapterName = "PowerShell") {
611+
this.disposables.push(workspace.onDidChangeConfiguration(change => {
612+
if (
613+
change.affectsConfiguration("powershell.trace.dap")
614+
) {
615+
this.dapLogEnabled = workspace.getConfiguration("powershell").get<boolean>("trace.dap") ?? false;
616+
if (this.dapLogEnabled) {
617+
// Trigger the output pane to appear. This gives the user time to position it before starting a debug.
618+
this.log?.show(true);
619+
}
620+
}
621+
}));
622+
}
623+
624+
/* We want to use a shared output log for separate debug sessions as usually only one is running at a time and we
625+
* dont need an output window for every debug session. We also want to leave it active so user can copy and paste
626+
* even on run end. When user changes the setting and disables it getter will return undefined, which will result
627+
* in a noop for the logging activities, effectively pausing logging but not disposing the output channel. If the
628+
* user re-enables, then logging resumes.
629+
*/
630+
_log: LogOutputChannel | undefined;
631+
get log(): LogOutputChannel | undefined {
632+
if (this.dapLogEnabled && this._log === undefined) {
633+
this._log = window.createOutputChannel(`${this.adapterName} Trace - DAP`, { log: true });
634+
this.disposables.push(this._log);
635+
}
636+
return this.dapLogEnabled ? this._log : undefined;
637+
}
638+
639+
createDebugAdapterTracker(session: DebugSession): DebugAdapterTracker {
640+
const sessionInfo = `${this.adapterName} Debug Session: ${session.name} [${session.id}]`;
641+
return {
642+
onWillStartSession: () => this.log?.info(`Starting ${sessionInfo}. Set log level to trace to see DAP messages beyond errors`),
643+
onWillStopSession: () => this.log?.info(`Stopping ${sessionInfo}`),
644+
onExit: code => this.log?.info(`${sessionInfo} exited with code ${code}`),
645+
onWillReceiveMessage: (m): void => {
646+
this.log?.debug(`▶️${m.seq} ${m.type}: ${m.command}`);
647+
if (m.arguments && (Array.isArray(m.arguments) ? m.arguments.length > 0 : Object.keys(m.arguments).length > 0)) {
648+
this.log?.trace(`${m.seq}: ` + JSON.stringify(m.arguments, undefined, 2));
649+
}
650+
},
651+
onDidSendMessage: (m):void => {
652+
const responseSummary = m.request_seq !== undefined
653+
? `${m.success ? "✅" : "❌"}${m.request_seq} ${m.type}(${m.seq}): ${m.command}`
654+
: `◀️${m.seq} ${m.type}: ${m.event ?? m.command}`;
655+
this.log?.debug(
656+
responseSummary
657+
);
658+
if (m.body && (Array.isArray(m.body) ? m.body.length > 0 : Object.keys(m.body).length > 0)) {
659+
this.log?.trace(`${m.seq}: ` + JSON.stringify(m.body, undefined, 2));
660+
}
661+
},
662+
onError: e => this.log?.error(e),
663+
};
664+
}
665+
666+
dispose(): void {
667+
this.disposables.forEach(d => d.dispose());
668+
}
669+
}
670+
598671
export class SpecifyScriptArgsFeature implements Disposable {
599672
private command: Disposable;
600673
private context: ExtensionContext;

src/session.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -623,8 +623,6 @@ export class SessionManager implements Middleware {
623623
});
624624
});
625625
};
626-
627-
628626
const clientOptions: LanguageClientOptions = {
629627
documentSelector: this.documentSelector,
630628
synchronize: {
@@ -660,6 +658,7 @@ export class SessionManager implements Middleware {
660658
},
661659
revealOutputChannelOn: RevealOutputChannelOn.Never,
662660
middleware: this,
661+
traceOutputChannel: vscode.window.createOutputChannel("PowerShell Trace - LSP", {log: true}),
663662
};
664663

665664
const languageClient = new LanguageClient("powershell", "PowerShell Editor Services Client", connectFunc, clientOptions);

test/features/DebugSession.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ describe("DebugSessionFeature", () => {
6969
createDebugSessionFeatureStub({context: context});
7070
assert.ok(registerFactoryStub.calledOnce, "Debug adapter factory method called");
7171
assert.ok(registerProviderStub.calledTwice, "Debug config provider registered for both Initial and Dynamic");
72-
assert.equal(context.subscriptions.length, 3, "DebugSessionFeature disposables populated");
72+
assert.equal(context.subscriptions.length, 4, "DebugSessionFeature disposables populated");
7373
// TODO: Validate the registration content, such as the language name
7474
});
7575
});

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy