Update
This commit is contained in:
@@ -4,23 +4,24 @@ import {
|
||||
GraphNode,
|
||||
GraphVisualizationData,
|
||||
} from "@hediet/debug-visualizer-data-extraction";
|
||||
import { hotClass, registerUpdateReconciler } from "@hediet/node-reload";
|
||||
import { DebugSessionProxy } from "../proxies/DebugSessionProxy";
|
||||
import { DebuggerViewProxy } from "../proxies/DebuggerViewProxy";
|
||||
import { FormattedMessage } from "../webviewContract";
|
||||
import {
|
||||
DebugSessionVisualizationSupport,
|
||||
VisualizationBackend,
|
||||
GetVisualizationDataArgs,
|
||||
VisualizationBackend,
|
||||
VisualizationBackendBase,
|
||||
} from "./VisualizationBackend";
|
||||
import { parseEvaluationResultFromGenericDebugAdapter } from "./parseEvaluationResultFromGenericDebugAdapter";
|
||||
import { FormattedMessage } from "../webviewContract";
|
||||
import { hotClass, registerUpdateReconciler } from "@hediet/node-reload";
|
||||
import { DebuggerViewProxy } from "../proxies/DebuggerViewProxy";
|
||||
|
||||
registerUpdateReconciler(module);
|
||||
|
||||
@hotClass(module)
|
||||
export class GenericVisualizationSupport
|
||||
implements DebugSessionVisualizationSupport {
|
||||
implements DebugSessionVisualizationSupport
|
||||
{
|
||||
constructor(private readonly debuggerView: DebuggerViewProxy) {}
|
||||
|
||||
createBackend(
|
||||
@@ -87,12 +88,12 @@ export class GenericVisualizationBackend extends VisualizationBackendBase {
|
||||
return parseEvaluationResultFromGenericDebugAdapter(
|
||||
reply.result,
|
||||
{
|
||||
debugAdapterType: this.debugSession.session
|
||||
.configuration.type,
|
||||
debugAdapterType:
|
||||
this.debugSession.session.configuration.type,
|
||||
}
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
} catch (error: any) {
|
||||
return {
|
||||
kind: "error",
|
||||
message: {
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
import {
|
||||
ApiHasNotBeenInitializedCode,
|
||||
CallFrameRequest,
|
||||
CallFramesRequest,
|
||||
DataExtractionResult,
|
||||
DataExtractorId,
|
||||
DataResult,
|
||||
getExpressionForDataExtractorApi,
|
||||
getExpressionToInitializeDataExtractorApi,
|
||||
ApiHasNotBeenInitializedCode,
|
||||
} from "@hediet/debug-visualizer-data-extraction";
|
||||
import { hotClass, registerUpdateReconciler } from "@hediet/node-reload";
|
||||
import { existsSync, readFileSync, watch } from "fs";
|
||||
import { observable, reaction } from "mobx";
|
||||
import { window, workspace } from "vscode";
|
||||
import { Disposable } from "@hediet/std/disposable";
|
||||
import { window } from "vscode";
|
||||
import { Config } from "../Config";
|
||||
import { DebugSessionProxy, StackFrame, StackTraceInfo } from "../proxies/DebugSessionProxy";
|
||||
import { DebuggerViewProxy } from "../proxies/DebuggerViewProxy";
|
||||
import { DebugSessionProxy } from "../proxies/DebugSessionProxy";
|
||||
import { FileWatcher } from "../webview/WebviewConnection";
|
||||
import { FormattedMessage } from "../webviewContract";
|
||||
import {
|
||||
DebugSessionVisualizationSupport,
|
||||
@@ -19,22 +22,14 @@ import {
|
||||
VisualizationBackend,
|
||||
VisualizationBackendBase,
|
||||
} from "./VisualizationBackend";
|
||||
import { Disposable } from "@hediet/std/disposable";
|
||||
import { DebouncedRunner } from "../utils/DebouncedRunner";
|
||||
import { FileWatcher } from "../webview/WebviewConnection";
|
||||
|
||||
registerUpdateReconciler(module);
|
||||
|
||||
@hotClass(module)
|
||||
export class JsEvaluationEngine implements DebugSessionVisualizationSupport {
|
||||
constructor(
|
||||
private readonly debuggerView: DebuggerViewProxy,
|
||||
private readonly config: Config
|
||||
) {}
|
||||
constructor(private readonly debuggerView: DebuggerViewProxy, private readonly config: Config) {}
|
||||
|
||||
createBackend(
|
||||
session: DebugSessionProxy
|
||||
): VisualizationBackend | undefined {
|
||||
createBackend(session: DebugSessionProxy): VisualizationBackend | undefined {
|
||||
const supportedDebugAdapters = [
|
||||
"node",
|
||||
"node2",
|
||||
@@ -48,25 +43,21 @@ export class JsEvaluationEngine implements DebugSessionVisualizationSupport {
|
||||
];
|
||||
|
||||
if (supportedDebugAdapters.indexOf(session.session.type) !== -1) {
|
||||
return new JsVisualizationBackend(
|
||||
session,
|
||||
this.debuggerView,
|
||||
this.config
|
||||
);
|
||||
return new JsVisualizationBackend(session, this.debuggerView, this.config);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
class CallFrameState {
|
||||
public request: CallFramesRequest | undefined = undefined;
|
||||
}
|
||||
|
||||
class JsVisualizationBackend extends VisualizationBackendBase {
|
||||
public readonly expressionLanguageId = "javascript";
|
||||
private initializePromise: Promise<void> | undefined = undefined;
|
||||
|
||||
constructor(
|
||||
debugSession: DebugSessionProxy,
|
||||
debuggerView: DebuggerViewProxy,
|
||||
private readonly config: Config
|
||||
) {
|
||||
constructor(debugSession: DebugSessionProxy, debuggerView: DebuggerViewProxy, private readonly config: Config) {
|
||||
super(debugSession, debuggerView);
|
||||
}
|
||||
|
||||
@@ -79,20 +70,13 @@ class JsVisualizationBackend extends VisualizationBackendBase {
|
||||
|
||||
public async getVisualizationData(
|
||||
args: GetVisualizationDataArgs
|
||||
): Promise<
|
||||
| { kind: "data"; result: DataExtractionResult }
|
||||
| { kind: "error"; message: FormattedMessage }
|
||||
> {
|
||||
const result = await this._getVisualizationData(args);
|
||||
): Promise<{ kind: "data"; result: DataExtractionResult } | { kind: "error"; message: FormattedMessage }> {
|
||||
let result = await this.getVisualizationDataIfInitialized(args);
|
||||
|
||||
if (result.kind === "not-initialized") {
|
||||
await this.initializeApiSafe();
|
||||
|
||||
const result2 = await this._getVisualizationData(args);
|
||||
|
||||
if (result2.kind !== "not-initialized") {
|
||||
return result2;
|
||||
} else {
|
||||
await this.initializeApiOrWait();
|
||||
result = await this.getVisualizationDataIfInitialized(args);
|
||||
if (result.kind === "not-initialized") {
|
||||
return {
|
||||
kind: "error",
|
||||
message: "Could not initialize API",
|
||||
@@ -103,103 +87,74 @@ class JsVisualizationBackend extends VisualizationBackendBase {
|
||||
return result;
|
||||
}
|
||||
|
||||
private async _getVisualizationData({
|
||||
expression,
|
||||
preferredExtractorId,
|
||||
}: GetVisualizationDataArgs): Promise<
|
||||
private async getVisualizationDataIfInitialized(
|
||||
args: GetVisualizationDataArgs
|
||||
): Promise<
|
||||
| { kind: "data"; result: DataExtractionResult }
|
||||
| { kind: "error"; message: FormattedMessage }
|
||||
| { kind: "not-initialized" }
|
||||
> {
|
||||
try {
|
||||
const frameId = this.debuggerView.getActiveStackFrameId(
|
||||
this.debugSession
|
||||
);
|
||||
const { expression, preferredExtractorId } = args;
|
||||
let callFrameState: CallFrameState;
|
||||
if (args.sessionStore.data instanceof CallFrameState) {
|
||||
callFrameState = args.sessionStore.data;
|
||||
} else {
|
||||
callFrameState = new CallFrameState();
|
||||
args.sessionStore.data = callFrameState;
|
||||
}
|
||||
|
||||
const variableNames = new Array<string>();
|
||||
if (frameId) {
|
||||
const scopes = await this.debugSession.getScopes({ frameId });
|
||||
const scopeVariables = await Promise.all(
|
||||
scopes
|
||||
.filter((s) => !s.expensive && s.name !== "Global")
|
||||
.map((s) =>
|
||||
this.debugSession.getVariables({
|
||||
variablesReference: s.variablesReference,
|
||||
})
|
||||
)
|
||||
);
|
||||
for (const variables of scopeVariables) {
|
||||
variableNames.push(
|
||||
...variables
|
||||
.filter((v) => v.value !== "undefined")
|
||||
.map((v) => v.name)
|
||||
try {
|
||||
const frameId = this.debuggerView.getActiveStackFrameId(this.debugSession);
|
||||
|
||||
const stackTrace = await this.debuggerView.activeDebugSession!.getStackTrace({
|
||||
threadId: 0,
|
||||
levels: 20,
|
||||
});
|
||||
|
||||
const variableNames = await getVariableNames(this.debugSession, frameId);
|
||||
|
||||
let tryCount = 0;
|
||||
while (true) {
|
||||
tryCount++;
|
||||
|
||||
let callFrameDataValue = "null";
|
||||
if (callFrameState.request) {
|
||||
callFrameDataValue = await getCallFramesSnapshotValue(
|
||||
this.debugSession,
|
||||
stackTrace,
|
||||
callFrameState.request
|
||||
);
|
||||
}
|
||||
const result = await this.evaluateVisualizationDataRequest(
|
||||
expression,
|
||||
preferredExtractorId,
|
||||
variableNames,
|
||||
callFrameDataValue,
|
||||
frameId
|
||||
);
|
||||
|
||||
if (variableNames.length > 50) {
|
||||
variableNames.length = 50;
|
||||
if (result.kind === "NoExtractors") {
|
||||
throw new Error("No extractors");
|
||||
} else if (result.kind === "Error") {
|
||||
throw new Error(result.message + "\n" + (result as any).stack);
|
||||
} else if (result.kind === "Data") {
|
||||
return {
|
||||
kind: "data",
|
||||
result: result.extractionResult,
|
||||
};
|
||||
} else if (result.kind === "OutdatedCallFrameSnapshot") {
|
||||
if (tryCount >= 2) {
|
||||
throw new Error("OutdatedCallFrameSnapshot");
|
||||
}
|
||||
callFrameState.request = result.callFramesRequest;
|
||||
} else {
|
||||
throw new Error("Invalid Data");
|
||||
}
|
||||
}
|
||||
|
||||
const preferredExtractorExpr = preferredExtractorId
|
||||
? `"${preferredExtractorId}"`
|
||||
: "undefined";
|
||||
|
||||
const body = `${getExpressionForDataExtractorApi()}.getData(
|
||||
() => (${expression}),
|
||||
expr => eval(expr),
|
||||
${preferredExtractorExpr},
|
||||
{${variableNames.map((n) => `${n}: () => ${n}`).join(",")}},
|
||||
)`;
|
||||
|
||||
const wrappedExpr = `
|
||||
(() => {
|
||||
try {
|
||||
return ${body};
|
||||
} catch (e) {
|
||||
return JSON.stringify({
|
||||
kind: "Error",
|
||||
message: e.message,
|
||||
stack: e.stack
|
||||
});
|
||||
}
|
||||
})()
|
||||
`;
|
||||
|
||||
const reply = await this.debugSession.evaluate({
|
||||
expression: wrappedExpr,
|
||||
frameId,
|
||||
context: this.getContext(),
|
||||
});
|
||||
const resultStr = reply.result;
|
||||
const jsonData =
|
||||
this.getContext() === "copy"
|
||||
? resultStr
|
||||
: resultStr.substr(1, resultStr.length - 2);
|
||||
const result = JSON.parse(jsonData) as DataResult;
|
||||
|
||||
if (result.kind === "NoExtractors") {
|
||||
throw new Error("No extractors");
|
||||
} else if (result.kind === "Error") {
|
||||
throw new Error(result.message + "\n" + (result as any).stack);
|
||||
} else if (result.kind === "Data") {
|
||||
return {
|
||||
kind: "data",
|
||||
result: result.extractionResult,
|
||||
};
|
||||
} else {
|
||||
throw new Error("Invalid Data");
|
||||
} catch (error: any) {
|
||||
if (typeof error.message === "string" && error.message.indexOf(ApiHasNotBeenInitializedCode) !== -1) {
|
||||
return { kind: "not-initialized" };
|
||||
}
|
||||
} catch (error) {
|
||||
if (
|
||||
typeof error.message === "string" &&
|
||||
error.message.indexOf(ApiHasNotBeenInitializedCode) !== -1
|
||||
) {
|
||||
return {
|
||||
kind: "not-initialized",
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
kind: "error",
|
||||
message: error.message,
|
||||
@@ -207,11 +162,42 @@ class JsVisualizationBackend extends VisualizationBackendBase {
|
||||
}
|
||||
}
|
||||
|
||||
private initializeApiSafe(): Promise<void> {
|
||||
private async evaluateVisualizationDataRequest(
|
||||
expression: string,
|
||||
preferredExtractorId: DataExtractorId | undefined,
|
||||
variableNames: string[],
|
||||
callFrameDataValue: string,
|
||||
frameId: number | undefined
|
||||
): Promise<DataResult> {
|
||||
const preferredExtractorExpr = preferredExtractorId ? `"${preferredExtractorId}"` : "undefined";
|
||||
const body = `${getExpressionForDataExtractorApi()}.getData(
|
||||
() => (${expression}),
|
||||
expr => eval(expr),
|
||||
${preferredExtractorExpr},
|
||||
{${variableNames.map((n) => `${n}: () => ${n}`).join(",")}},
|
||||
${callFrameDataValue}
|
||||
)`;
|
||||
|
||||
const wrappedExpr = `(() => {
|
||||
try { return ${body}; }
|
||||
catch (e) { return JSON.stringify({ kind: "Error", message: e.message, stack: e.stack }); }
|
||||
})()
|
||||
`;
|
||||
|
||||
const reply = await this.debugSession.evaluate({
|
||||
expression: wrappedExpr,
|
||||
frameId,
|
||||
context: this.getContext(),
|
||||
});
|
||||
const resultStr = reply.result;
|
||||
const jsonData = this.getContext() === "copy" ? resultStr : resultStr.substr(1, resultStr.length - 2);
|
||||
const result = JSON.parse(jsonData) as DataResult;
|
||||
return result;
|
||||
}
|
||||
|
||||
private initializeApiOrWait(): Promise<void> {
|
||||
if (!this.initializePromise) {
|
||||
this.initializePromise = this.initializeApi().finally(
|
||||
() => (this.initializePromise = undefined)
|
||||
);
|
||||
this.initializePromise = this.initializeApi().finally(() => (this.initializePromise = undefined));
|
||||
}
|
||||
return this.initializePromise;
|
||||
}
|
||||
@@ -219,10 +205,10 @@ class JsVisualizationBackend extends VisualizationBackendBase {
|
||||
private async initializeApi(): Promise<void> {
|
||||
// prefer existing is true, so that manually registered (possibly newer) extractors are not overwritten.
|
||||
const expression = `${getExpressionToInitializeDataExtractorApi()}.registerDefaultExtractors(true);`;
|
||||
|
||||
await this.debugSession.evaluate({
|
||||
const frameId = this.debuggerView.getActiveStackFrameId(this.debugSession);
|
||||
const result = await this.debugSession.evaluate({
|
||||
expression,
|
||||
frameId: undefined,
|
||||
frameId,
|
||||
context: this.getContext(),
|
||||
});
|
||||
|
||||
@@ -235,18 +221,140 @@ class JsVisualizationBackend extends VisualizationBackendBase {
|
||||
this.dispose.untrack(this.customScripts);
|
||||
this.customScripts?.dispose();
|
||||
this.customScripts = this.dispose.track(
|
||||
new CustomScripts(this.debugSession, this.config, () =>
|
||||
this.onChangeEmitter.emit()
|
||||
)
|
||||
new CustomScripts(this.debugSession, this.debuggerView, this.config, () => this.onChangeEmitter.emit())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let globalCallFrameId = 0;
|
||||
|
||||
async function getCallFramesSnapshotValue(
|
||||
debugSession: DebugSessionProxy,
|
||||
stackTrace: StackTraceInfo,
|
||||
callFramesRequest: CallFramesRequest
|
||||
): Promise<string> {
|
||||
const globalFnDef = `
|
||||
function getGlobal() {
|
||||
if (typeof globalThis === "object") {
|
||||
return globalThis;
|
||||
} else if (typeof global === "object") {
|
||||
return global;
|
||||
} else if (typeof window === "object") {
|
||||
return window;
|
||||
}
|
||||
throw new Error("No global available");
|
||||
}`;
|
||||
|
||||
const callFrameId = globalCallFrameId++;
|
||||
let idx = 0;
|
||||
let skippedCount = 0;
|
||||
const skippedCounts: Record<number, number> = {};
|
||||
|
||||
let frames = stackTrace.stackFrames;
|
||||
const firstAsyncFrameIdx = frames.findIndex((f) => f.id === 0);
|
||||
if (firstAsyncFrameIdx !== -1) {
|
||||
frames = frames.slice(0, firstAsyncFrameIdx);
|
||||
}
|
||||
|
||||
const result = await Promise.all(
|
||||
frames.map(async (frame) => {
|
||||
function matches(frame: StackFrame, callFrameRequest: CallFrameRequest): boolean {
|
||||
if (frame.name !== callFrameRequest.methodName) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!callFramesRequest.requestedCallFrames.some((r) => matches(frame, r))) {
|
||||
if (skippedCount === 0) {
|
||||
idx++;
|
||||
}
|
||||
skippedCount++;
|
||||
skippedCounts[idx - 1] = skippedCount;
|
||||
return undefined;
|
||||
}
|
||||
skippedCount = 0;
|
||||
const curFrameIdx = idx;
|
||||
idx++;
|
||||
|
||||
const scopes = await debugSession.getScopes({ frameId: frame.id });
|
||||
const scopeVariables = await Promise.all(
|
||||
scopes
|
||||
.filter((s) => !s.expensive && s.name.indexOf(":") !== -1)
|
||||
.map((s) => debugSession.getVariables({ variablesReference: s.variablesReference }))
|
||||
);
|
||||
const variables = new Set(scopeVariables.flatMap((v) => v.map((v) => v.name)));
|
||||
|
||||
await debugSession.evaluate({
|
||||
expression: `
|
||||
(() => {
|
||||
${globalFnDef}
|
||||
const g = getGlobal();
|
||||
(g.$$hedietDbgVslzr${callFrameId} = g.$$hedietDbgVslzr${callFrameId} || [])[${curFrameIdx}] = {
|
||||
vars: { ${[...variables].map((v) => `${v}: ${v}`).join(", ")} },
|
||||
...${JSON.stringify({
|
||||
methodName: frame.name,
|
||||
source: { path: frame.source.path, name: frame.source.name },
|
||||
})}
|
||||
};
|
||||
})();
|
||||
`,
|
||||
frameId: frame.id,
|
||||
context: "repl",
|
||||
});
|
||||
|
||||
return true;
|
||||
})
|
||||
);
|
||||
|
||||
if (!result.some((r) => r)) {
|
||||
return `{ requestId: ${JSON.stringify(callFramesRequest.requestId)}, frames: [] }`;
|
||||
}
|
||||
|
||||
return `
|
||||
(() => {
|
||||
${globalFnDef}
|
||||
const g = getGlobal();
|
||||
const r = g.$$hedietDbgVslzr${callFrameId};
|
||||
delete g.$$hedietDbgVslzr${callFrameId};
|
||||
${Object.entries(skippedCounts)
|
||||
.map(([idx, val]) => `r[${idx}] = { skippedFrames: ${val} };\n`)
|
||||
.join("")}
|
||||
return { requestId: ${JSON.stringify(callFramesRequest.requestId)}, frames: r };
|
||||
})()
|
||||
`;
|
||||
}
|
||||
|
||||
async function getVariableNames(debugSession: DebugSessionProxy, frameId: number | undefined) {
|
||||
const variableNames = new Array<string>();
|
||||
if (frameId) {
|
||||
const scopes = await debugSession.getScopes({ frameId });
|
||||
const scopeVariables = await Promise.all(
|
||||
scopes
|
||||
.filter((s) => !s.expensive && s.name !== "Global")
|
||||
.map((s) =>
|
||||
debugSession.getVariables({
|
||||
variablesReference: s.variablesReference,
|
||||
})
|
||||
)
|
||||
);
|
||||
for (const variables of scopeVariables) {
|
||||
variableNames.push(...variables.filter((v) => v.value !== "undefined").map((v) => v.name));
|
||||
}
|
||||
|
||||
if (variableNames.length > 50) {
|
||||
variableNames.length = 50;
|
||||
}
|
||||
}
|
||||
return variableNames;
|
||||
}
|
||||
|
||||
class CustomScripts {
|
||||
public readonly dispose = Disposable.fn();
|
||||
|
||||
constructor(
|
||||
debugSession: DebugSessionProxy,
|
||||
debuggerView: DebuggerViewProxy,
|
||||
config: Config,
|
||||
changeHandler: () => void
|
||||
) {
|
||||
@@ -256,9 +364,7 @@ class CustomScripts {
|
||||
async (files) => {
|
||||
for (const file of files) {
|
||||
if (!file.fileExists) {
|
||||
window.showErrorMessage(
|
||||
`The file ${file.path} does not exist.`
|
||||
);
|
||||
window.showErrorMessage(`The file ${file.path} does not exist.`);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -278,20 +384,16 @@ class CustomScripts {
|
||||
}
|
||||
)
|
||||
(
|
||||
${
|
||||
file.content === undefined
|
||||
? "undefined"
|
||||
: `function (module) { ${file.content} }`
|
||||
}
|
||||
${file.content === undefined ? "undefined" : `function (module) { ${file.content} }`}
|
||||
)`;
|
||||
|
||||
try {
|
||||
await debugSession.evaluate({
|
||||
expression,
|
||||
frameId: undefined,
|
||||
frameId: debuggerView.getActiveStackFrameId(debugSession),
|
||||
context: "repl",
|
||||
});
|
||||
} catch (e) {
|
||||
} catch (e: any) {
|
||||
window.showErrorMessage(
|
||||
'Error while running custom visualization extractor script "' +
|
||||
file.path +
|
||||
|
||||
@@ -1,39 +1,33 @@
|
||||
import {
|
||||
DataExtractionResult,
|
||||
DataExtractorId,
|
||||
GraphNode,
|
||||
GraphVisualizationData,
|
||||
} from "@hediet/debug-visualizer-data-extraction";
|
||||
import { hotClass, registerUpdateReconciler } from "@hediet/node-reload";
|
||||
import { Config } from "../Config";
|
||||
import { DebugSessionProxy } from "../proxies/DebugSessionProxy";
|
||||
import { DebuggerViewProxy } from "../proxies/DebuggerViewProxy";
|
||||
import { FormattedMessage } from "../webviewContract";
|
||||
import {
|
||||
DebugSessionVisualizationSupport,
|
||||
VisualizationBackend,
|
||||
GetVisualizationDataArgs,
|
||||
VisualizationBackend,
|
||||
VisualizationBackendBase,
|
||||
} from "./VisualizationBackend";
|
||||
import { Config } from "../Config";
|
||||
import { parseEvaluationResultFromGenericDebugAdapter } from "./parseEvaluationResultFromGenericDebugAdapter";
|
||||
import { FormattedMessage } from "../webviewContract";
|
||||
import { hotClass, registerUpdateReconciler } from "@hediet/node-reload";
|
||||
import { DebuggerViewProxy } from "../proxies/DebuggerViewProxy";
|
||||
|
||||
registerUpdateReconciler(module);
|
||||
|
||||
@hotClass(module)
|
||||
export class PyEvaluationEngine
|
||||
implements DebugSessionVisualizationSupport {
|
||||
export class PyEvaluationEngine implements DebugSessionVisualizationSupport {
|
||||
constructor(
|
||||
private readonly debuggerView: DebuggerViewProxy,
|
||||
private readonly config: Config
|
||||
) { }
|
||||
) {}
|
||||
|
||||
createBackend(
|
||||
session: DebugSessionProxy
|
||||
): VisualizationBackend | undefined {
|
||||
|
||||
const supportedDebugAdapters = [
|
||||
"python",
|
||||
];
|
||||
const supportedDebugAdapters = ["python"];
|
||||
|
||||
if (supportedDebugAdapters.indexOf(session.session.type) !== -1) {
|
||||
return new PyVisualizationBackend(
|
||||
@@ -81,7 +75,8 @@ export class PyVisualizationBackend extends VisualizationBackendBase {
|
||||
try {
|
||||
// inject vscodedebugvisualizer for python
|
||||
await this.debugSession.evaluate({
|
||||
expression: 'from vscodedebugvisualizer import visualize\ntry:\n import debugvisualizer\nexcept ImportError:\n pass',
|
||||
expression:
|
||||
"from vscodedebugvisualizer import visualize\ntry:\n import debugvisualizer\nexcept ImportError:\n pass",
|
||||
frameId,
|
||||
context: this.getContext(),
|
||||
});
|
||||
@@ -97,21 +92,23 @@ export class PyVisualizationBackend extends VisualizationBackendBase {
|
||||
result = result.replace(/\\'/g, "'");
|
||||
result = result.replace(/\\\\/g, "\\");
|
||||
|
||||
return parseEvaluationResultFromGenericDebugAdapter(
|
||||
result,
|
||||
{
|
||||
debugAdapterType: this.debugSession.session
|
||||
.configuration.type,
|
||||
}
|
||||
);
|
||||
return parseEvaluationResultFromGenericDebugAdapter(result, {
|
||||
debugAdapterType: this.debugSession.session.configuration.type,
|
||||
});
|
||||
} catch (error) {
|
||||
let errorTyped = error as Error;
|
||||
if (errorTyped.message.includes("ModuleNotFoundError: No module named 'vscodedebugvisualizer'")) {
|
||||
if (
|
||||
errorTyped.message.includes(
|
||||
"ModuleNotFoundError: No module named 'vscodedebugvisualizer'"
|
||||
)
|
||||
) {
|
||||
return {
|
||||
kind: "error",
|
||||
message: {
|
||||
kind: "list",
|
||||
items: ["Please make sure vscodedebugvisualizer is installed: `pip install vscodedebugvisualizer`"],
|
||||
items: [
|
||||
"Please make sure vscodedebugvisualizer is installed: `pip install vscodedebugvisualizer`",
|
||||
],
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -145,5 +142,4 @@ export class PyVisualizationBackend extends VisualizationBackendBase {
|
||||
pythonInject += "visualize(" + args.expression + ")";
|
||||
return pythonInject;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,10 +1,18 @@
|
||||
import { DataExtractionResult, DataResult } from "@hediet/debug-visualizer-data-extraction";
|
||||
import {
|
||||
DataExtractionResult,
|
||||
DataResult,
|
||||
} from "@hediet/debug-visualizer-data-extraction";
|
||||
import { hotClass, registerUpdateReconciler } from "@hediet/node-reload";
|
||||
import { Config } from "../Config";
|
||||
import { DebuggerViewProxy } from "../proxies/DebuggerViewProxy";
|
||||
import { DebugSessionProxy } from "../proxies/DebugSessionProxy";
|
||||
import { DebuggerViewProxy } from "../proxies/DebuggerViewProxy";
|
||||
import { FormattedMessage } from "../webviewContract";
|
||||
import { DebugSessionVisualizationSupport, GetVisualizationDataArgs, VisualizationBackend, VisualizationBackendBase } from "./VisualizationBackend";
|
||||
import {
|
||||
DebugSessionVisualizationSupport,
|
||||
GetVisualizationDataArgs,
|
||||
VisualizationBackend,
|
||||
VisualizationBackendBase,
|
||||
} from "./VisualizationBackend";
|
||||
|
||||
registerUpdateReconciler(module);
|
||||
|
||||
@@ -13,12 +21,12 @@ export class RbEvaluationEngine implements DebugSessionVisualizationSupport {
|
||||
constructor(
|
||||
private readonly debuggerView: DebuggerViewProxy,
|
||||
private readonly config: Config
|
||||
) { }
|
||||
) {}
|
||||
|
||||
createBackend(
|
||||
session: DebugSessionProxy
|
||||
): VisualizationBackend | undefined {
|
||||
const supportedDebugAdapters = ['rdbg'];
|
||||
const supportedDebugAdapters = ["rdbg"];
|
||||
|
||||
if (supportedDebugAdapters.indexOf(session.session.type) !== -1) {
|
||||
return new RbVisualizationBackend(
|
||||
@@ -32,13 +40,13 @@ export class RbEvaluationEngine implements DebugSessionVisualizationSupport {
|
||||
}
|
||||
|
||||
class RbVisualizationBackend extends VisualizationBackendBase {
|
||||
public readonly expressionLanguageId = 'ruby';
|
||||
public readonly expressionLanguageId = "ruby";
|
||||
constructor(
|
||||
debugSession: DebugSessionProxy,
|
||||
debuggerView: DebuggerViewProxy,
|
||||
private readonly config: Config
|
||||
) {
|
||||
super(debugSession, debuggerView)
|
||||
super(debugSession, debuggerView);
|
||||
}
|
||||
|
||||
private readonly defaultContext = "repl";
|
||||
@@ -46,8 +54,8 @@ class RbVisualizationBackend extends VisualizationBackendBase {
|
||||
public async getVisualizationData(
|
||||
args: GetVisualizationDataArgs
|
||||
): Promise<
|
||||
| { kind: "data"; result: DataExtractionResult; }
|
||||
| { kind: "error"; message: FormattedMessage; }
|
||||
| { kind: "data"; result: DataExtractionResult }
|
||||
| { kind: "error"; message: FormattedMessage }
|
||||
> {
|
||||
const result = await this._getVisualizationData(args);
|
||||
return result;
|
||||
@@ -55,7 +63,7 @@ class RbVisualizationBackend extends VisualizationBackendBase {
|
||||
|
||||
private async _getVisualizationData({
|
||||
expression,
|
||||
preferredExtractorId
|
||||
preferredExtractorId,
|
||||
}: GetVisualizationDataArgs): Promise<
|
||||
| { kind: "data"; result: DataExtractionResult }
|
||||
| { kind: "error"; message: FormattedMessage }
|
||||
@@ -69,9 +77,9 @@ class RbVisualizationBackend extends VisualizationBackendBase {
|
||||
const initialReply = await this.debugSession.evaluate({
|
||||
expression: "require 'debugvisualizer'",
|
||||
frameId,
|
||||
context: this.defaultContext
|
||||
context: this.defaultContext,
|
||||
});
|
||||
if (initialReply.result.includes('LoadError')) {
|
||||
if (initialReply.result.includes("LoadError")) {
|
||||
return {
|
||||
kind: "error",
|
||||
message: {
|
||||
@@ -82,49 +90,56 @@ class RbVisualizationBackend extends VisualizationBackendBase {
|
||||
kind: "inlineList",
|
||||
items: [
|
||||
"Install the gem by executing:",
|
||||
{ kind: "code", content: "$ bundle add debugvisualizer" },
|
||||
{
|
||||
kind: "code",
|
||||
content: "$ bundle add debugvisualizer",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
kind: "inlineList",
|
||||
items: [
|
||||
"If bundler is not being used, install the gem by executing:",
|
||||
{ kind: "code", content: "$ gem install debugvisualizer" },
|
||||
{
|
||||
kind: "code",
|
||||
content:
|
||||
"$ gem install debugvisualizer",
|
||||
},
|
||||
],
|
||||
}
|
||||
},
|
||||
],
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
const preferredId = preferredExtractorId || '';
|
||||
const preferredId = preferredExtractorId || "";
|
||||
const wrappedExpr = `
|
||||
DebugVisualizer.to_debug_visualizer_protocol_json("${preferredId}", ${expression})
|
||||
`;
|
||||
const reply = await this.debugSession.evaluate({
|
||||
expression: wrappedExpr,
|
||||
frameId,
|
||||
context: this.defaultContext
|
||||
})
|
||||
context: this.defaultContext,
|
||||
});
|
||||
|
||||
let dataResult: DataResult;
|
||||
try {
|
||||
// Debuggee converts result to a JSON string twice.
|
||||
dataResult = JSON.parse(JSON.parse(reply.result)) as DataResult;
|
||||
} catch (error) {
|
||||
let message = error.message
|
||||
} catch (error: any) {
|
||||
let message = error.message;
|
||||
// The `reply.result` is as follows when error occurs in the evaluation of an expression and parsing will fail.
|
||||
// e.g. "#<ZeroDivisionError: divided by 0>"
|
||||
// In this case, it is more beneficial to display this error message.
|
||||
if (reply.result.includes('Error')) message = reply.result;
|
||||
throw new Error(message)
|
||||
if (reply.result.includes("Error")) message = reply.result;
|
||||
throw new Error(message);
|
||||
}
|
||||
|
||||
switch (dataResult.kind) {
|
||||
case 'NoExtractors':
|
||||
case "NoExtractors":
|
||||
throw new Error("No extractors");
|
||||
case 'Error':
|
||||
case "Error":
|
||||
throw new Error(dataResult.message);
|
||||
case 'Data':
|
||||
case "Data":
|
||||
return {
|
||||
kind: "data",
|
||||
result: dataResult.extractionResult,
|
||||
@@ -132,10 +147,10 @@ class RbVisualizationBackend extends VisualizationBackendBase {
|
||||
default:
|
||||
throw new Error("Invalid Data");
|
||||
}
|
||||
} catch (error) {
|
||||
} catch (error: any) {
|
||||
return {
|
||||
kind: "error",
|
||||
message: error.message
|
||||
message: error.message,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,11 +3,11 @@ import {
|
||||
DataExtractorId,
|
||||
} from "@hediet/debug-visualizer-data-extraction";
|
||||
import { Disposable } from "@hediet/std/disposable";
|
||||
import { DebugSessionProxy } from "../proxies/DebugSessionProxy";
|
||||
import { CompletionItem, FormattedMessage } from "../webviewContract";
|
||||
import { reaction } from "mobx";
|
||||
import { DebuggerViewProxy } from "../proxies/DebuggerViewProxy";
|
||||
import { EventEmitter, EventSource } from "@hediet/std/events";
|
||||
import { reaction } from "mobx";
|
||||
import { DebugSessionProxy } from "../proxies/DebugSessionProxy";
|
||||
import { DebuggerViewProxy } from "../proxies/DebuggerViewProxy";
|
||||
import { CompletionItem, FormattedMessage } from "../webviewContract";
|
||||
|
||||
export interface DebugSessionVisualizationSupport {
|
||||
createBackend(session: DebugSessionProxy): VisualizationBackend | undefined;
|
||||
@@ -32,8 +32,11 @@ export interface VisualizationBackend extends Disposable {
|
||||
}
|
||||
|
||||
export interface GetVisualizationDataArgs {
|
||||
expression: string;
|
||||
preferredExtractorId: DataExtractorId | undefined;
|
||||
readonly expression: string;
|
||||
readonly preferredExtractorId: DataExtractorId | undefined;
|
||||
|
||||
// Can be used to attach data to the session.
|
||||
readonly sessionStore: { data: unknown };
|
||||
}
|
||||
|
||||
export abstract class VisualizationBackendBase implements VisualizationBackend {
|
||||
@@ -49,7 +52,7 @@ export abstract class VisualizationBackendBase implements VisualizationBackend {
|
||||
this.dispose.track({
|
||||
dispose: reaction(
|
||||
() => debuggerView.getActiveStackFrameId(debugSession),
|
||||
activeStackFrameId => {
|
||||
(activeStackFrameId) => {
|
||||
if (activeStackFrameId !== undefined) {
|
||||
this.onChangeEmitter.emit();
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ export function parseEvaluationResultFromGenericDebugAdapter(
|
||||
},
|
||||
};
|
||||
}
|
||||
} catch (e) {
|
||||
} catch (e: any) {
|
||||
return {
|
||||
kind: "error",
|
||||
message: e.message,
|
||||
@@ -93,7 +93,7 @@ function isEnclosedWith(str: string, char: string): boolean {
|
||||
function parseJson(str: string, context: ParseEvaluationResultContext) {
|
||||
try {
|
||||
return JSON.parse(str);
|
||||
} catch (error) {
|
||||
} catch (error: any) {
|
||||
throw new FormattedError({
|
||||
kind: "list",
|
||||
items: [
|
||||
|
||||
Reference in New Issue
Block a user