Skip to content

Commit 88f01dc

Browse files
angelozerrdatho7561
authored andcommitted
feat: Show Qute DAP traces
Signed-off-by: azerr <azerr@redhat.com>
1 parent cca1442 commit 88f01dc

File tree

3 files changed

+200
-0
lines changed

3 files changed

+200
-0
lines changed

package.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,17 @@
391391
"default": "warning",
392392
"markdownDescription": "Validation severity for undefined section tag in Qute template files.",
393393
"scope": "resource"
394+
},
395+
"qute.trace.debug": {
396+
"type": "string",
397+
"enum": [
398+
"off",
399+
"messages",
400+
"verbose"
401+
],
402+
"default": "off",
403+
"markdownDescription": "Traces the communication between VS Code and the Qute debugger server in the Output view (Qute Debug). Default is `off`.",
404+
"scope": "window"
394405
}
395406
}
396407
},

src/extension.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import { getFilePathsFromWorkspace } from './utils/workspaceUtils';
3131
import { WelcomeWebview } from './webviews/WelcomeWebview';
3232
import { createTerminateDebugListener } from './wizards/debugging/terminateProcess';
3333
import { QuteDebugAdapterFactory } from './qute/debugAdapter/quteDebugAdapterFactory';
34+
import { QuteDebugAdapterTrackerFactory } from './qute/debugAdapter/quteDebugAdapterTrackerFactory';
3435

3536
// alias for vscode-java's ExtensionAPI
3637
export type JavaExtensionAPI = any;
@@ -61,9 +62,13 @@ export async function activate(context: ExtensionContext) {
6162

6263
// Register the Qute Debugger
6364
const quteDebugFactory = new QuteDebugAdapterFactory();
65+
const quteDebugTrackerFactory = new QuteDebugAdapterTrackerFactory();
6466
context.subscriptions.push(
6567
debug.registerDebugAdapterDescriptorFactory('qute', quteDebugFactory)
6668
);
69+
context.subscriptions.push(
70+
debug.registerDebugAdapterTrackerFactory('qute', quteDebugTrackerFactory)
71+
);
6772
}
6873

6974
async function doActivate(context: ExtensionContext): Promise<void> {
Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
import * as vscode from 'vscode';
2+
3+
interface ResponsePromise {
4+
method: string;
5+
timerStart: number;
6+
}
7+
8+
enum Trace {
9+
Off, Messages, Verbose
10+
}
11+
12+
namespace Trace {
13+
export function fromString(value: string): Trace {
14+
switch (value.toLowerCase()) {
15+
case 'off': return Trace.Off;
16+
case 'messages': return Trace.Messages;
17+
case 'verbose': return Trace.Verbose;
18+
default: return Trace.Off;
19+
}
20+
}
21+
}
22+
23+
let trace = Trace.Off;
24+
let outputChannel: vscode.LogOutputChannel | null = null;
25+
const responsePromises: Map<string | number, ResponsePromise> = new Map();
26+
27+
export class QuteDebugAdapterTrackerFactory implements vscode.DebugAdapterTrackerFactory {
28+
29+
constructor() {
30+
// Watch configuration changes to update trace dynamically
31+
vscode.workspace.onDidChangeConfiguration(event => {
32+
if (event.affectsConfiguration("qute.trace.debug")) {
33+
updateTraceLevel();
34+
}
35+
});
36+
}
37+
38+
createDebugAdapterTracker(session: vscode.DebugSession): vscode.DebugAdapterTracker {
39+
if (!outputChannel) {
40+
outputChannel = vscode.window.createOutputChannel("Qute Debug", { log: true });
41+
updateTraceLevel(true);
42+
}
43+
44+
outputChannel.info(`Debug session started for ${session.name}`);
45+
46+
return {
47+
onWillReceiveMessage: (message) => {
48+
if (message.type === 'request') {
49+
responsePromises.set(message.seq, { method: message.command, timerStart: Date.now() });
50+
traceSendingRequest(message);
51+
}
52+
},
53+
54+
onDidSendMessage: (message) => {
55+
if (message.type === 'response') {
56+
handleResponse(message);
57+
} else if (message.type === 'event') {
58+
traceReceivedNotification(message);
59+
}
60+
},
61+
62+
onWillStopSession: () => {
63+
outputChannel!.info(`Debug session stopped`);
64+
},
65+
66+
onError: (error) => {
67+
outputChannel!.error(`Error: ${error.message}`);
68+
},
69+
70+
onExit: (code, signal) => {
71+
outputChannel!.info(`Exited with code=${code} signal=${signal}`);
72+
}
73+
};
74+
}
75+
}
76+
77+
/**
78+
* Update trace level from current settings.
79+
*/
80+
function updateTraceLevel(init = false) {
81+
const config = vscode.workspace.getConfiguration("qute");
82+
const traceSetting = config.get<string>("trace.debug", "off")!;
83+
trace = Trace.fromString(traceSetting);
84+
85+
if (outputChannel) {
86+
if (init) {
87+
outputChannel.info(`Trace enabled at level: ${Trace[trace]} (${traceSetting})`);
88+
} else {
89+
outputChannel.info(`Trace level updated to: ${Trace[trace]} (${traceSetting})`);
90+
}
91+
}
92+
}
93+
94+
function traceSendingRequest(message: any) {
95+
if (trace === Trace.Off) return;
96+
97+
let data: string | undefined = undefined;
98+
if (trace === Trace.Verbose && message.arguments) {
99+
data = `Params: ${stringifyTrace(message.arguments)}`;
100+
}
101+
showTrace(`Sending request '${message.command} - (${message.seq})'.`, data);
102+
}
103+
104+
function traceReceivedNotification(message: any): void {
105+
if (trace === Trace.Off) return;
106+
107+
let data: string | undefined = undefined;
108+
if (trace === Trace.Verbose) {
109+
data = message.body
110+
? `Params: ${stringifyTrace(message.body)}`
111+
: 'No parameters provided.';
112+
}
113+
showTrace(`Received notification '${message.event}'.`, data);
114+
}
115+
116+
function handleResponse(message: any) {
117+
if (message.request_seq === null) {
118+
if (message.error) {
119+
showError(`Received response message without id: Error is: \n${JSON.stringify(message.error, undefined, 4)}`);
120+
} else {
121+
showError(`Received response message without id. No further error information provided.`);
122+
}
123+
return;
124+
}
125+
126+
const responsePromise = responsePromises.get(message.request_seq);
127+
traceReceivedResponse(message, responsePromise);
128+
129+
if (responsePromise) {
130+
responsePromises.delete(message.request_seq);
131+
}
132+
}
133+
134+
function traceReceivedResponse(message: any, responsePromise: ResponsePromise | undefined): void {
135+
if (trace === Trace.Off) return;
136+
137+
let data: string | undefined = undefined;
138+
if (trace === Trace.Verbose) {
139+
if (message.error && message.error.data) {
140+
data = `Error data: ${stringifyTrace(message.error.data)}`;
141+
} else if (message.body) {
142+
data = `Result: ${stringifyTrace(message.body)}`;
143+
} else if (message.error === undefined) {
144+
data = 'No result returned.';
145+
}
146+
}
147+
148+
if (responsePromise) {
149+
const error = message.error
150+
? ` Request failed: ${message.error.message} (${message.error.code}).`
151+
: '';
152+
showTrace(
153+
`Received response '${responsePromise.method} - (${message.request_seq})' in ${Date.now() - responsePromise.timerStart}ms.${error}`,
154+
data
155+
);
156+
} else {
157+
showTrace(`Received response ${message.request_seq} without active response promise.`, data);
158+
}
159+
}
160+
161+
function stringifyTrace(params: any): string | undefined {
162+
if (params === undefined || params === null) return undefined;
163+
164+
switch (trace) {
165+
case Trace.Verbose:
166+
return JSON.stringify(params, null, 4);
167+
case Trace.Messages:
168+
return JSON.stringify(params);
169+
default:
170+
return undefined;
171+
}
172+
}
173+
174+
function showTrace(message: string, data?: any | undefined): void {
175+
outputChannel!.trace(getLogMessage(message, data));
176+
}
177+
178+
function showError(message: string): void {
179+
outputChannel!.error(message);
180+
}
181+
182+
function getLogMessage(message: string, data?: any | undefined): string {
183+
return data ? `${message}\n${data}` : message;
184+
}

0 commit comments

Comments
 (0)