Merge pull request #159 from ono-max/patch-1

ruby support
This commit is contained in:
Henning Dieterichs
2022-11-15 16:58:02 +01:00
committed by GitHub
4 changed files with 147 additions and 1 deletions

View File

@@ -25,6 +25,7 @@ See [demos](../demos/) for demos. These languages and debuggers are verified to
- C++ using `cppdbg` debug adapter: [✅ Basic Support](../demos/cpp)
- Swift using `lldb` debug adapter: [✅ Basic Support](../demos/swift)
- Rust using `lldb` debug adapter: [✅ Basic Support](../demos/rust)
- Ruby using `rdbg` debug adapter: [✅ Basic Support]
All other languages and debuggers might work too.
For languages with _Basic Support_, only JSON strings can be visualized - you must implement some logic that builds this JSON for your data structure!

View File

@@ -0,0 +1,142 @@
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 { FormattedMessage } from "../webviewContract";
import { DebugSessionVisualizationSupport, GetVisualizationDataArgs, VisualizationBackend, VisualizationBackendBase } from "./VisualizationBackend";
registerUpdateReconciler(module);
@hotClass(module)
export class RbEvaluationEngine implements DebugSessionVisualizationSupport {
constructor(
private readonly debuggerView: DebuggerViewProxy,
private readonly config: Config
) { }
createBackend(
session: DebugSessionProxy
): VisualizationBackend | undefined {
const supportedDebugAdapters = ['rdbg'];
if (supportedDebugAdapters.indexOf(session.session.type) !== -1) {
return new RbVisualizationBackend(
session,
this.debuggerView,
this.config
);
}
return undefined;
}
}
class RbVisualizationBackend extends VisualizationBackendBase {
public readonly expressionLanguageId = 'ruby';
constructor(
debugSession: DebugSessionProxy,
debuggerView: DebuggerViewProxy,
private readonly config: Config
) {
super(debugSession, debuggerView)
}
private readonly defaultContext = "repl";
public async getVisualizationData(
args: GetVisualizationDataArgs
): Promise<
| { kind: "data"; result: DataExtractionResult; }
| { kind: "error"; message: FormattedMessage; }
> {
const result = await this._getVisualizationData(args);
return result;
}
private async _getVisualizationData({
expression,
preferredExtractorId
}: GetVisualizationDataArgs): Promise<
| { kind: "data"; result: DataExtractionResult }
| { kind: "error"; message: FormattedMessage }
> {
try {
if (expression.length === 0) throw new Error("No extractors");
const frameId = this.debuggerView.getActiveStackFrameId(
this.debugSession
);
const initialReply = await this.debugSession.evaluate({
expression: "require 'debugvisualizer'",
frameId,
context: this.defaultContext
});
if (initialReply.result.includes('LoadError')) {
return {
kind: "error",
message: {
kind: "list",
items: [
"LoadError: Failed to load debugvisualizer.",
{
kind: "inlineList",
items: [
"Install the gem by executing:",
{ 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" },
],
}
],
}
};
}
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
})
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
// 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)
}
switch (dataResult.kind) {
case 'NoExtractors':
throw new Error("No extractors");
case 'Error':
throw new Error(dataResult.message);
case 'Data':
return {
kind: "data",
result: dataResult.extractionResult,
};
default:
throw new Error("Invalid Data");
}
} catch (error) {
return {
kind: "error",
message: error.message
};
}
}
}

View File

@@ -3,5 +3,6 @@ export * from "./VisualizationBackend";
export { ComposedVisualizationSupport } from "./ComposedVisualizationSupport";
export { JsEvaluationEngine } from "./JsVisualizationSupport";
export { PyEvaluationEngine } from "./PyVisualizationSupport";
export { RbEvaluationEngine } from './RbVisualizationSupport';
export { GenericVisualizationSupport } from "./GenericVisualizationSupport";
export { ConfigurableVisualizationSupport } from "./ConfigurableVisualizationSupport";

View File

@@ -22,6 +22,7 @@ import {
ComposedVisualizationSupport,
JsEvaluationEngine,
PyEvaluationEngine,
RbEvaluationEngine,
GenericVisualizationSupport,
ConfigurableVisualizationSupport,
} from "./VisualizationBackend";
@@ -53,6 +54,7 @@ export class Extension {
),
new JsEvaluationEngine(this.debuggerView, this.config),
new PyEvaluationEngine(this.debuggerView, this.config),
new RbEvaluationEngine(this.debuggerView, this.config),
new GenericVisualizationSupport(this.debuggerView),
]),
this.debuggerView
@@ -102,7 +104,7 @@ export class Extension {
if (
match.index <= selection.start.character &&
selection.start.character <=
match.index + match[0].length
match.index + match[0].length
) {
selectedText = match[1];
}