This commit is contained in:
Henning Dieterichs
2023-09-07 15:00:50 +02:00
parent d45dfdaa71
commit a9656e1e12
37 changed files with 5502 additions and 3664 deletions

2
.gitignore vendored
View File

@@ -86,3 +86,5 @@ out/
__pycache__/
*.py[cod]
*$py.class
*.vsix

View File

@@ -2,5 +2,6 @@
"trailingComma": "es5",
"tabWidth": 4,
"semi": true,
"useTabs": true
"useTabs": true,
"printWidth": 120
}

14
.vscode/settings.json vendored
View File

@@ -7,11 +7,15 @@
"plantuml.exportOutDir": "docs/exported",
"typescript.updateImportsOnFileMove.enabled": "prompt",
"mochaExplorer.files": "./data-extraction/test/**/*.test.ts",
"mochaExplorer.require": [
"ts-node/register",
"source-map-support/register"
],
"mochaExplorer.require": ["ts-node/register", "source-map-support/register"],
"tasksStatusbar.taskLabelFilter": "dev",
"editor.formatOnSave": true,
"typescript.tsdk": "node_modules\\typescript\\lib"
"typescript.tsdk": "node_modules\\typescript\\lib",
"editor.defaultFormatter": "esbenp.prettier-vscode",
"prettier.prettierPath": "node_modules/prettier",
"prettier.printWidth": 120,
"[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
}

View File

@@ -28,6 +28,8 @@
},
"dependencies": {},
"devDependencies": {
"copy-webpack-plugin": "^11.0.0",
"@types/copy-webpack-plugin": "^10.1.0",
"@types/mocha": "^7.0.2",
"@types/node": "^13.7.4",
"@types/plotly.js": "1.44.28",
@@ -36,10 +38,11 @@
"mocha-lcov-reporter": "^1.3.0",
"nyc": "^15.0.0",
"source-map-support": "^0.5.16",
"ts-loader": "^6.2.1",
"tslint": "^6.0.0",
"typescript": "^3.8.2",
"webpack": "^4.41.6",
"webpack-cli": "^3.3.11"
"tslint": "^6.1.3",
"typescript": "^5.1.6",
"webpack": "^5.88.1",
"webpack-cli": "^5.1.4",
"ts-loader": "^9.4.4",
"ts-node": "^10.9.1"
}
}

View File

@@ -6,6 +6,5 @@ export function getGlobal(): any {
} else if (typeof window === "object") {
return window;
}
throw new Error("No global available");
}

View File

@@ -1,7 +1,4 @@
import {
VisualizationData,
DataExtractionResult,
} from "../../DataExtractionResult";
import { DataExtractionResult, VisualizationData } from "../../DataExtractionResult";
import { LoadDataExtractorsFn } from "./LoadDataExtractorsFn";
export interface DataExtractorApi {
@@ -24,7 +21,8 @@ export interface DataExtractorApi {
valueFn: () => unknown,
evalFn: <T>(expression: string) => T,
preferredDataExtractorId: string | undefined,
variablesInScope: Record<string, unknown>
variablesInScope: Record<string, unknown>,
callFramesSnapshot: CallFramesSnapshot | null
): JSONString<DataResult>;
/**
@@ -42,12 +40,41 @@ export type DataResult =
extractionResult: DataExtractionResult;
}
| { kind: "NoExtractors" }
| { kind: "Error"; message: string };
| { kind: "Error"; message: string }
| {
kind: "OutdatedCallFrameSnapshot";
callFramesRequest: CallFramesRequest;
};
export interface JSONString<T> extends String {
__brand: { json: T };
}
export interface CallFramesRequest {
requestId: string;
requestedCallFrames: CallFrameRequest[];
}
export interface CallFrameRequest {
methodName: string;
pathRegExp: string | undefined;
}
export interface CallFramesSnapshot {
requestId: string;
frames: (CallFrameInfo | SkippedCallFrames)[];
}
export interface SkippedCallFrames {
skippedFrames: number;
}
export interface CallFrameInfo {
methodName: string;
source: { name: string; path: string };
vars: Record<string, any>;
}
export interface DataExtractor {
/**
* Must be unique among all data extractors.
@@ -58,11 +85,7 @@ export interface DataExtractor {
* Filters the data to be extracted.
*/
dataCtor?: string;
getExtractions(
data: unknown,
extractionCollector: ExtractionCollector,
context: DataExtractorContext
): void;
getExtractions(data: unknown, extractionCollector: ExtractionCollector, context: DataExtractorContext): void;
}
export interface ExtractionCollector {
@@ -78,11 +101,15 @@ export interface DataExtractorContext {
*/
evalFn: <TEval>(expression: string) => TEval;
expression: string | undefined;
readonly expression: string | undefined;
variablesInScope: Record<string, () => unknown>;
readonly variablesInScope: Record<string, () => unknown>;
extract(value: unknown): VisualizationData | undefined;
readonly callFrameInfos: readonly (CallFrameInfo | SkippedCallFrames)[];
addCallFrameRequest(request: CallFrameRequest): void;
}
export interface DataExtraction {

View File

@@ -1,19 +1,22 @@
import {
DataExtractorApi,
DataResult,
JSONString,
DataExtractor,
DataExtraction,
ExtractionCollector,
DataExtractorContext,
} from "./DataExtractorApi";
import {
DataExtractorInfo,
VisualizationData,
} from "../../DataExtractionResult";
import { registerDefaultExtractors } from "./default-extractors";
import { LoadDataExtractorsFn } from "./LoadDataExtractorsFn";
import * as helpers from "../helpers";
import {
CallFrameInfo,
CallFrameRequest,
CallFramesSnapshot,
DataExtraction,
DataExtractor,
DataExtractorApi,
DataExtractorContext,
DataResult,
JSONString,
SkippedCallFrames,
} from "./DataExtractorApi";
import { LoadDataExtractorsFn } from "./LoadDataExtractorsFn";
import { registerDefaultExtractors } from "./default-extractors";
/**
* @internal
@@ -42,51 +45,18 @@ export class DataExtractorApiImpl implements DataExtractorApi {
valueFn: () => unknown,
evalFn: <T>(expression: string) => T,
preferredDataExtractorId: string | undefined,
variablesInScope: Record<string, () => unknown>
variablesInScope: Record<string, () => unknown>,
callFramesSnapshot: CallFramesSnapshot | null
): JSONString<DataResult> {
class ContextImpl implements DataExtractorContext {
constructor(
public readonly variablesInScope: Record<string, () => unknown>,
public readonly expression: string | undefined,
public readonly evalFn: <T>(expression: string) => T,
private readonly _api: DataExtractorApiImpl,
private readonly _parent: ContextImpl | undefined
) {}
get _level(): number {
return this._parent ? this._parent._level + 1 : 0;
}
extract(value: any): VisualizationData | undefined {
if (this._level > 10) {
throw new Error(
"extract() called too many times recursively"
);
}
const extractions = this._api.getExtractions(
value,
new ContextImpl(
this.variablesInScope,
undefined,
this.evalFn,
this._api,
this
)
);
if (extractions.length === 0) {
return undefined;
}
return extractions[0].extractData();
}
}
const callFrameRequests: CallFrameRequest[] = [];
const rootContext = new ContextImpl(
variablesInScope,
removeEnd(removeStart(valueFn.toString(), "() => ("), ")").trim(),
evalFn,
this,
undefined
undefined,
callFramesSnapshot?.frames ?? [],
callFrameRequests
);
DataExtractorApiImpl.lastContext = rootContext;
@@ -94,6 +64,20 @@ export class DataExtractorApiImpl implements DataExtractorApi {
const extractions = this.getExtractions(value, rootContext);
DataExtractorApiImpl.lastContext = undefined;
const requestId =
callFrameRequests.length === 0
? ""
: "" + cyrb53(JSON.stringify(callFrameRequests));
if ((callFramesSnapshot?.requestId ?? "") !== requestId) {
return this.toJson({
kind: "OutdatedCallFrameSnapshot",
callFramesRequest: {
requestId,
requestedCallFrames: callFrameRequests,
},
});
}
let usedExtraction = extractions[0];
if (!usedExtraction) {
return this.toJson({ kind: "NoExtractors" } as DataResult);
@@ -108,14 +92,6 @@ export class DataExtractorApiImpl implements DataExtractorApi {
}
}
function mapExtractor(e: DataExtraction): DataExtractorInfo {
return {
id: e.id! as any,
name: e.name!,
priority: e.priority,
};
}
const data = usedExtraction.extractData();
return this.toJson({
kind: "Data",
@@ -188,6 +164,60 @@ export class DataExtractorApiImpl implements DataExtractorApi {
}
}
function mapExtractor(e: DataExtraction): DataExtractorInfo {
return {
id: e.id! as any,
name: e.name!,
priority: e.priority,
};
}
class ContextImpl implements DataExtractorContext {
constructor(
public readonly variablesInScope: Record<string, () => unknown>,
public readonly expression: string | undefined,
public readonly evalFn: <T>(expression: string) => T,
private readonly _api: DataExtractorApiImpl,
private readonly _parent: ContextImpl | undefined,
public readonly callFrameInfos: readonly (
| CallFrameInfo
| SkippedCallFrames
)[],
private readonly _callFrameRequests: CallFrameRequest[]
) {}
addCallFrameRequest(request: CallFrameRequest): void {
this._callFrameRequests.push(request);
}
get _level(): number {
return this._parent ? this._parent._level + 1 : 0;
}
extract(value: any): VisualizationData | undefined {
if (this._level > 10) {
throw new Error("extract() called too many times recursively");
}
const extractions = this._api.getExtractions(
value,
new ContextImpl(
this.variablesInScope,
undefined,
this.evalFn,
this._api,
this,
this.callFrameInfos,
this._callFrameRequests
)
);
if (extractions.length === 0) {
return undefined;
}
return extractions[0].extractData();
}
}
function removeStart(str: string, start: string): string {
if (str.startsWith(start)) {
return str.substr(start.length);
@@ -201,3 +231,20 @@ function removeEnd(str: string, end: string): string {
}
return str;
}
// From https://stackoverflow.com/questions/7616461/generate-a-hash-from-string-in-javascript
function cyrb53(str: string, seed = 0) {
let h1 = 0xdeadbeef ^ seed,
h2 = 0x41c6ce57 ^ seed;
for (let i = 0, ch; i < str.length; i++) {
ch = str.charCodeAt(i);
h1 = Math.imul(h1 ^ ch, 2654435761);
h2 = Math.imul(h2 ^ ch, 1597334677);
}
h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507);
h1 ^= Math.imul(h2 ^ (h2 >>> 13), 3266489909);
h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507);
h2 ^= Math.imul(h1 ^ (h1 >>> 13), 3266489909);
return 4294967296 * (2097151 & h2) + (h1 >>> 0);
}

View File

@@ -1,9 +1,9 @@
import * as fs from "fs";
import { getGlobal } from "../../getGlobal";
import * as globalHelpers from "../global-helpers";
import * as helpers from "../helpers";
import { DataExtractorApi } from "./DataExtractorApi";
import { DataExtractorApiImpl } from "./DataExtractorApiImpl";
import * as helpers from "../helpers";
import * as globalHelpers from "../global-helpers";
import { getGlobal } from "../../getGlobal";
/**
* Returns standalone JS code representing an expression that initializes the data extraction API.
@@ -50,8 +50,18 @@ export function getDataExtractorApi(): DataExtractorApi {
* @internal
*/
function selfContainedGetInitializedDataExtractorApi(): DataExtractorApi {
const globalObj =
typeof window === "object" ? (window as any) : (global as any);
function getGlobal(): any {
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 globalObj = getGlobal();
const key: typeof apiKey = "@hediet/data-extractor-api/v3";
let api: DataExtractorApi | undefined = globalObj[key];
if (!api) {

View File

@@ -30,7 +30,12 @@ export function createGraphFromPointers<T>(
return createGraph<T | Pointer>(
items,
(item) => {
if ("marker" in item && item["marker"] === marker) {
if (
typeof item === "object" &&
item &&
"marker" in item &&
item["marker"] === marker
) {
return {
id: "label____" + item.name,
color: "orange",

View File

@@ -1,6 +1,6 @@
{
"compilerOptions": {
"target": "esnext",
"target": "ES2018",
"module": "commonjs",
"strict": true,
"outDir": "./dist",

View File

@@ -1,2 +0,0 @@
require("ts-node").register();
module.exports = require("./webpack.config.ts");

View File

@@ -1,6 +1,6 @@
import * as webpack from "webpack";
import path = require("path");
import { CleanWebpackPlugin } from "clean-webpack-plugin";
import * as path from "path";
import * as webpack from "webpack";
const r = (file: string) => path.resolve(__dirname, file);

2920
data-extraction/yarn.lock Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -23,130 +23,37 @@
});
},
});
};
register({
id: "binaryViewer",
getExtractions(data, collector, context) {
if (typeof data !== "number") {
return;
}
/*
register({
id: "binaryViewer",
getExtractions(data, collector, context) {
if (typeof data !== "number") {
return;
}
context.addCallFrameRequest({
methodName: "Module._load",
pathRegExp: ".*",
});
collector.addExtraction({
priority: 100,
id: "binary",
name: "Bits of Integer",
extractData() {
return helpers.asData({
kind: { text: true },
text: data.toString(2),
});
},
});
},
});
context.addCallFrameRequest({
methodName: "LinkedList.insertAt",
pathRegExp: ".*",
});*/
*/
(register, helpers) => {
register({
id: "positionOrRangeInTextModel",
getExtractions(data, collector, context) {
collector.addExtraction({
priority: 100,
id: "foo",
name: "Foo",
priority: 10000,
id: "binary",
name: "Bits of Integer",
extractData() {
return context.extract(JSON.stringify(context.callFrameInfos, undefined, 4));
return helpers.asData({
kind: { text: true },
text: Object.keys(context.variablesInScope).join(", "),
text: data.toString(2),
});
},
});
},
});
register({
id: "positionOrRangeInTextModel",
getExtractions(data, collector, context) {
/** @type {{ start: { line: number; column: number; }; end: { line: number; column: number; }; }} */
let range;
if (isRange(data)) {
range = {
start: {
line: data.startLineNumber - 1,
column: data.startColumn - 1,
},
end: {
line: data.endLineNumber - 1,
column: data.endColumn - 1,
},
};
} else if (isPosition(data)) {
range = {
start: {
line: data.lineNumber - 1,
column: data.column - 1,
},
end: { line: data.lineNumber - 1, column: data.column - 1 },
};
} else {
return;
}
if (!isRange(data) && !isPosition(data)) {
return;
}
/** @type {any} */
const textModel = helpers.find(
x =>
typeof x === "object" &&
!!x &&
"constructor" in x &&
x.constructor.name === "TextModel"
);
collector.addExtraction({
id: "positionOrRangeInTextModel",
name: "Position/Range In TextModel",
extractData() {
return helpers.asData({
kind: { text: true },
text: textModel.getValue(),
decorations: [{ range }],
});
},
priority: 1000,
});
},
});
};
/**
* @return {value is { startColumn: number; startLineNumber: number; endColumn: number; endLineNumber: number; }}
*/
function isRange(value) {
return (
typeof value === "object" &&
!!value &&
"startColumn" in value &&
"startLineNumber" in value
);
}
/**
* @return {value is { column: number; lineNumber: number }}
*/
function isPosition(value) {
return (
typeof value === "object" &&
!!value &&
"column" in value &&
"lineNumber" in value
);
}
};

Binary file not shown.

View File

@@ -4,7 +4,7 @@
"displayName": "Debug Visualizer",
"description": "A visual watch window that lets you visualize your data structures while debugging.",
"icon": "docs/logo.drawio.png",
"version": "2.4.0",
"version": "2.5.0",
"license": "GPL-3.0",
"engines": {
"vscode": "^1.46.0"
@@ -36,8 +36,6 @@
"Other"
],
"activationEvents": [
"onCommand:vscode-debug-visualizer.new-visualizer",
"onCommand:vscode-debug-visualizer.visualizer-set-expression",
"onDebug",
"onWebviewPanel:debugVisualizer"
],
@@ -113,8 +111,8 @@
}
},
"scripts": {
"pub": "vsce publish --packagePath extension.vsix",
"package": "vsce package --no-dependencies -o extension.vsix --yarn --baseImagesUrl https://github.com/hediet/vscode-debug-visualizer/raw/master/extension --baseContentUrl https://github.com/hediet/vscode-debug-visualizer/raw/master/extension",
"pub": "vsce publish --packagePath ./dist/extension.vsix",
"package": "vsce package --no-dependencies -o ./dist/extension.vsix --yarn --baseImagesUrl https://github.com/hediet/vscode-debug-visualizer/raw/master/extension --baseContentUrl https://github.com/hediet/vscode-debug-visualizer/raw/master/extension",
"vscode:prepublish": "",
"build": "webpack --mode production",
"dev": "shx rm -rf dist && tsc -watch -p ./"
@@ -133,24 +131,18 @@
"mobx-utils": "^5.6.1"
},
"devDependencies": {
"copy-webpack-plugin": "^5.1.1",
"@types/copy-webpack-plugin": "^5.0.0",
"copy-webpack-plugin": "^11.0.0",
"@types/copy-webpack-plugin": "^10.1.0",
"@types/express": "^4.17.2",
"@types/serve-static": "^1.13.3",
"@types/node": "^13.7.4",
"@types/vscode": "1.46.0",
"tslint": "^6.0.0",
"typescript": "^3.8.2",
"webpack": "^4.41.6",
"webpack-cli": "^3.3.11",
"ts-loader": "^6.2.1",
"@types/vscode": "^1.79.1",
"tslint": "^6.1.3",
"typescript": "^5.1.6",
"webpack": "^5.88.1",
"webpack-cli": "^5.1.4",
"ts-loader": "^9.4.4",
"shx": "^0.3.2",
"vsce": "2.9.3"
},
"__metadata": {
"id": "bce78737-8848-49f7-a27d-97004928fe41",
"publisherDisplayName": "Henning Dieterichs",
"publisherId": "4d8048f9-07cf-483a-b27b-1fdc29cea931",
"isPreReleaseVersion": false
}
}

View File

@@ -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: {

View File

@@ -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 +

View File

@@ -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;
}
}

View File

@@ -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,
};
}
}

View File

@@ -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();
}

View File

@@ -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: [

View File

@@ -1,5 +1,5 @@
import { DataExtractorId } from "@hediet/debug-visualizer-data-extraction";
import { DataExtractionState, CompletionItem } from "../webviewContract";
import { CompletionItem, DataExtractionState } from "../webviewContract";
export interface VisualizationWatchModel {
createWatch(
@@ -19,6 +19,7 @@ export interface VisualizationWatchModel {
export interface VisualizationWatchOptions {
preferredDataExtractor?: DataExtractorId | undefined;
sessionState?: unknown;
}
export interface VisualizationWatch {
@@ -31,6 +32,8 @@ export interface VisualizationWatch {
/** This field is observable */
readonly preferredDataExtractor: DataExtractorId | undefined;
readonly sessionState: unknown;
setPreferredDataExtractor(id: DataExtractorId | undefined): void;
refresh(): void;
dispose(): void;

View File

@@ -7,11 +7,7 @@ import { action, observable } from "mobx";
import { CancellationToken, CancellationTokenSource } from "vscode";
import { VisualizationBackend } from "../VisualizationBackend/VisualizationBackend";
import { CompletionItem, DataExtractionState } from "../webviewContract";
import {
VisualizationWatch,
VisualizationWatchModel,
VisualizationWatchOptions,
} from "./VisualizationWatchModel";
import { VisualizationWatch, VisualizationWatchModel, VisualizationWatchOptions } from "./VisualizationWatchModel";
@hotClass(module)
export class VisualizationWatchModelImpl implements VisualizationWatchModel {
@@ -22,23 +18,15 @@ export class VisualizationWatchModelImpl implements VisualizationWatchModel {
this.dispose.track(
visualizationBackend.onChange.sub(() => {
if (visualizationBackend.expressionLanguageId !== undefined) {
this.lastLanguageId =
visualizationBackend.expressionLanguageId;
this.lastLanguageId = visualizationBackend.expressionLanguageId;
}
})
);
this.lastLanguageId = visualizationBackend.expressionLanguageId;
}
public createWatch(
expression: string,
options: VisualizationWatchOptions
): VisualizationWatch {
const w = new ObservableVisualizationWatch(
expression,
options,
this.visualizationBackend
);
public createWatch(expression: string, options: VisualizationWatchOptions): VisualizationWatch {
const w = new ObservableVisualizationWatch(expression, options, this.visualizationBackend);
w.onDispose.sub(() => {
this.watchers.delete(w);
});
@@ -53,10 +41,7 @@ export class VisualizationWatchModelImpl implements VisualizationWatchModel {
return this.lastLanguageId;
}
public getCompletions(
text: string,
column: number
): Promise<CompletionItem[]> {
public getCompletions(text: string, column: number): Promise<CompletionItem[]> {
return this.visualizationBackend.getCompletions(text, column);
}
}
@@ -115,31 +100,22 @@ class ObservableVisualizationWatch implements VisualizationWatch {
this._refresh(tokenSource.token);
}
public sessionState: unknown = undefined;
private async _refresh(token: CancellationToken): Promise<void> {
/*const session = this.debuggerView.activeDebugSession;
if (!session) {
this._state = { kind: "noDebugSession" };
return;
}*/
//const frameId = this.debuggerView.activeFrameId;
this._state = { kind: "loading" };
/*
const visualizationBackend = this.getVisualizationBackend();
if (!visualizationBackend) {
this._state = {
kind: "error",
message: `The debug adapter "${session.session.type}" is not supported.`,
};
return;
}*/
const that = this;
const result = await this.visualizationBackend.getVisualizationData({
expression: this.expression,
//frameId,
preferredExtractorId: this.preferredDataExtractor,
sessionStore: {
get data(): unknown {
return that.sessionState;
},
set data(value: unknown) {
that.sessionState = value;
},
},
});
if (result.kind === "error") {

View File

@@ -1,17 +1,17 @@
import { observable } from "mobx";
import { DebugSession } from "vscode";
import { CompletionItem } from "../webviewContract";
import { observable } from "mobx";
export class DebugSessionProxy {
@observable private _activeStackFrameId: number | undefined;
constructor(public readonly session: DebugSession) {}
protected async getStackTrace(args: {
public async getStackTrace(args: {
threadId: number;
startFrame?: number;
levels?: number;
}): Promise<{ totalFrames?: number; stackFrames: StackFrame[] }> {
}): Promise<StackTraceInfo> {
try {
const reply = (await this.session.customRequest("stackTrace", {
threadId: args.threadId,
@@ -61,9 +61,7 @@ export class DebugSessionProxy {
}
}
public async getVariables(args: {
variablesReference: number;
}): Promise<Variable[]> {
public async getVariables(args: { variablesReference: number }): Promise<Variable[]> {
try {
const reply = await this.session.customRequest("variables", {
variablesReference: args.variablesReference,
@@ -100,6 +98,11 @@ export class DebugSessionProxy {
}
}
export interface StackTraceInfo {
totalFrames?: number;
stackFrames: StackFrame[];
}
interface Scope {
name: string;
expensive: boolean;
@@ -112,7 +115,8 @@ interface Variable {
variablesReference: number;
}
interface StackFrame {
export interface StackFrame {
id: number;
name: string;
source: { name: string; path: string };
}

View File

@@ -1,7 +1,6 @@
import { Disposable } from "@hediet/std/disposable";
import { debug, DebugSession } from "vscode";
import { EventEmitter } from "@hediet/std/events";
import { runInAction } from "mobx";
import { debug, DebugSession } from "vscode";
import { DebugSessionProxy } from "./DebugSessionProxy";
/**
@@ -14,7 +13,8 @@ export class DebuggerProxy {
private readonly _onDidStartDebugSession = new EventEmitter<{
session: DebugSessionProxy;
}>();
public readonly onDidStartDebugSession = this._onDidStartDebugSession.asEvent();
public readonly onDidStartDebugSession =
this._onDidStartDebugSession.asEvent();
public getDebugSessionProxy(session: DebugSession): DebugSessionProxy {
let result = this.sessions.get(session);
@@ -27,18 +27,18 @@ export class DebuggerProxy {
constructor() {
this.dispose.track([
debug.onDidStartDebugSession(session => {
debug.onDidStartDebugSession((session) => {
const e = this.sessions.get(session)!;
this._onDidStartDebugSession.emit({ session: e });
}),
debug.onDidTerminateDebugSession(session => {
debug.onDidTerminateDebugSession((session) => {
this.sessions.delete(session);
}),
debug.registerDebugAdapterTrackerFactory("*", {
createDebugAdapterTracker: session => {
createDebugAdapterTracker: (session) => {
const extendedSession = this.getDebugSessionProxy(session);
return {
onDidSendMessage: async msg => {
onDidSendMessage: async (msg) => {
type Message =
| StoppedEvent
| ThreadsResponse
@@ -79,13 +79,12 @@ export class DebuggerProxy {
if (m.type === "event") {
if (m.event === "stopped") {
const threadId = m.body.threadId;
const r = await extendedSession[
"getStackTrace"
]({
threadId,
startFrame: 0,
levels: 1,
});
const r =
await extendedSession.getStackTrace({
threadId,
startFrame: 0,
levels: 1,
});
extendedSession["_activeStackFrameId"] =
r.stackFrames.length > 0
? r.stackFrames[0].id
@@ -98,9 +97,8 @@ export class DebuggerProxy {
m.command === "stepIn" ||
m.command === "stepOut"
) {
extendedSession[
"_activeStackFrameId"
] = undefined;
extendedSession["_activeStackFrameId"] =
undefined;
}
}
},

View File

@@ -1,21 +1,17 @@
import { Disposable } from "@hediet/std/disposable";
import { webviewContract } from "../webviewContract";
import { ConsoleRpcLogger, RequestHandlingError } from "@hediet/typed-json-rpc";
import {
VisualizationWatch,
VisualizationWatchModel,
} from "../VisualizationWatchModel";
import { WebSocketStream } from "@hediet/typed-json-rpc-websocket";
import { observable, autorun } from "mobx";
import { WebviewServer } from "./WebviewServer";
import { existsSync, readFileSync, watch } from "fs";
import { autorun, observable } from "mobx";
import * as open from "open";
import chromeLauncher = require("chrome-launcher");
import { Config } from "../Config";
import { IncrementalMap } from "../utils/IncrementalMap";
import { watch, existsSync, readFileSync } from "fs";
import { DebouncedRunner } from "../utils/DebouncedRunner";
import { getExpressionForDataExtractorApi } from "@hediet/debug-visualizer-data-extraction";
import { window } from "vscode";
import { Config } from "../Config";
import { VisualizationWatch, VisualizationWatchModel } from "../VisualizationWatchModel";
import { DebouncedRunner } from "../utils/DebouncedRunner";
import { IncrementalMap } from "../utils/IncrementalMap";
import { webviewContract } from "../webviewContract";
import { WebviewServer } from "./WebviewServer";
import chromeLauncher = require("chrome-launcher");
export class WebviewConnection {
public readonly dispose = Disposable.fn();
@@ -23,7 +19,7 @@ export class WebviewConnection {
@observable
private watcher: VisualizationWatch | undefined = undefined;
private readonly client: typeof webviewContract["TClientInterface"];
private readonly client: (typeof webviewContract)["TClientInterface"];
constructor(
evaluationWatchService: VisualizationWatchModel,
@@ -40,78 +36,74 @@ export class WebviewConnection {
}
}
const { client, channel } = webviewContract.registerServerToStream(
stream,
new ConsoleRpcLogger(),
{
authenticate: async ({ secret }, { newErr }) => {
if (secret !== serverSecret) {
return newErr({ errorMessage: "Invalid Secret" });
} else {
authenticated = true;
}
},
refresh: async () => {
throwIfNotAuthenticated();
const { client, channel } = webviewContract.registerServerToStream(stream, new ConsoleRpcLogger(), {
authenticate: async ({ secret }, { newErr }) => {
if (secret !== serverSecret) {
return newErr({ errorMessage: "Invalid Secret" });
} else {
authenticated = true;
}
},
refresh: async () => {
throwIfNotAuthenticated();
if (this.watcher) {
this.watcher.refresh();
}
},
setExpression: async ({ newExpression }) => {
throwIfNotAuthenticated();
if (this.watcher) {
this.watcher.refresh();
}
},
setExpression: async ({ newExpression }) => {
throwIfNotAuthenticated();
let oldPreferredDataExtractor: VisualizationWatch["preferredDataExtractor"];
if (this.watcher) {
oldPreferredDataExtractor =
this.watcher.preferredDataExtractor;
this.dispose.untrack(this.watcher).dispose();
}
this.watcher = this.dispose.track(
evaluationWatchService.createWatch(newExpression, {
preferredDataExtractor: oldPreferredDataExtractor,
})
);
},
openInBrowser: async ({}) => {
throwIfNotAuthenticated();
if (this.watcher?.expression === newExpression) {
return;
}
const url = server.getWebviewPageUrl({
mode: "standalone",
expression: this.watcher
? this.watcher.expression
: undefined,
});
let oldPreferredDataExtractor: VisualizationWatch["preferredDataExtractor"];
let oldSessionState: unknown;
if (this.watcher) {
oldPreferredDataExtractor = this.watcher.preferredDataExtractor;
oldSessionState = this.watcher.sessionState;
this.dispose.untrack(this.watcher).dispose();
}
this.watcher = this.dispose.track(
evaluationWatchService.createWatch(newExpression, {
preferredDataExtractor: oldPreferredDataExtractor,
sessionState: oldSessionState,
})
);
},
openInBrowser: async ({}) => {
throwIfNotAuthenticated();
let opened = false;
if (config.useChromeKioskMode) {
opened = await launchChrome(url);
}
if (!opened) {
open(url);
}
},
setPreferredDataExtractor: async ({ dataExtractorId }) => {
throwIfNotAuthenticated();
const url = server.getWebviewPageUrl({
mode: "standalone",
expression: this.watcher ? this.watcher.expression : undefined,
});
if (this.watcher) {
this.watcher.setPreferredDataExtractor(dataExtractorId);
}
},
getCompletions: async ({ text, column }) => {
throwIfNotAuthenticated();
let opened = false;
if (config.useChromeKioskMode) {
opened = await launchChrome(url);
}
if (!opened) {
open(url);
}
},
setPreferredDataExtractor: async ({ dataExtractorId }) => {
throwIfNotAuthenticated();
const completions =
await evaluationWatchService.getCompletions(
text,
column
);
return {
completions,
};
},
}
);
if (this.watcher) {
this.watcher.setPreferredDataExtractor(dataExtractorId);
}
},
getCompletions: async ({ text, column }) => {
throwIfNotAuthenticated();
const completions = await evaluationWatchService.getCompletions(text, column);
return {
completions,
};
},
});
this.client = client;
@@ -121,9 +113,7 @@ export class WebviewConnection {
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;
}
@@ -132,7 +122,7 @@ export class WebviewConnection {
id: file.path,
jsSource: file.content || null,
});
} catch (e) {
} catch (e: any) {
window.showErrorMessage(
'Error while running custom visualization extractor script "' +
file.path +
@@ -214,9 +204,7 @@ export class FileWatcher {
new SingleFileWatcher(path, (w) => {
scheduleRefresh.add(w);
debouncer.run(() => {
const changedWatchers = [...scheduleRefresh].filter(
(w) => w.refresh()
);
const changedWatchers = [...scheduleRefresh].filter((w) => w.refresh());
handleFileContents(
changedWatchers.map((w) => ({
path: w.path,
@@ -242,18 +230,13 @@ export class FileWatcher {
}
class SingleFileWatcher {
private readonly watcher = watch(this.path, { encoding: "utf-8" }, () =>
this.scheduleRefresh(this)
);
private readonly watcher = watch(this.path, { encoding: "utf-8" }, () => this.scheduleRefresh(this));
private isDisposed = false;
public content: string | undefined;
public exists: boolean = true;
constructor(
public readonly path: string,
private readonly scheduleRefresh: (self: SingleFileWatcher) => void
) {
this.scheduleRefresh(this);
constructor(public readonly path: string, private readonly scheduleRefresh: (self: SingleFileWatcher) => void) {
this.refresh();
}
public dispose() {

View File

@@ -1,17 +1,14 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "es6",
"target": "ES2017",
"outDir": "dist",
"lib": ["es6"],
"lib": ["ES2017"],
"sourceMap": true,
"rootDir": "src",
"strict": true,
"skipLibCheck": true,
"experimentalDecorators": true
},
"include": [
"src/**/*",
"../js-data-extractors/src/Extractors/TypeScriptDataExtractors.ts"
]
"include": ["src/**/*", "../js-data-extractors/src/Extractors/TypeScriptDataExtractors.ts"]
}

View File

@@ -1,8 +1,8 @@
import * as webpack from "webpack";
import path = require("path");
import { CleanWebpackPlugin } from "clean-webpack-plugin";
import CopyPlugin = require("copy-webpack-plugin");
import * as CopyPlugin from "copy-webpack-plugin";
import { readFileSync } from "fs";
import * as path from "path";
import * as webpack from "webpack";
const r = (file: string) => path.resolve(__dirname, file);
@@ -21,7 +21,7 @@ module.exports = {
"@hediet/debug-visualizer-data-extraction":
"@hediet/debug-visualizer-data-extraction",
"debug-visualizer-webview": "debug-visualizer-webview",
fsevents: "require('fsevents')"
fsevents: "require('fsevents')",
},
resolve: {
extensions: [".ts", ".js"],
@@ -55,11 +55,15 @@ function includeDependency(location: string) {
});
const pkgName = JSON.parse(content).name;
return new CopyPlugin([
{
from: location,
to: r(`./dist/node_modules/${pkgName}`),
ignore: ["**/node_modules/**/*"],
},
]);
return new CopyPlugin({
patterns: [
{
from: location,
to: r(`./dist/node_modules/${pkgName}`),
globOptions: {
ignore: ["**/node_modules/**/*"],
},
},
],
});
}

View File

@@ -12,6 +12,6 @@
"build-extension": "yarn workspace debug-visualizer build"
},
"devDependencies": {
"prettier": "^1.19.1"
"prettier": "^2.8.8"
}
}

View File

@@ -4,7 +4,7 @@
"license": "GPL-3.0",
"scripts": {
"dev": "webpack-dev-server --hot",
"build": "node --max_old_space_size=4096 ../node_modules/webpack/bin/webpack.js --mode production"
"build": "node --max_old_space_size=4096 ../node_modules/webpack/bin/webpack.js --mode development"
},
"main": "./index.js",
"dependencies": {
@@ -28,25 +28,24 @@
"devDependencies": {
"@types/html-webpack-plugin": "^3.2.2",
"@types/react-measure": "^2.0.6",
"@types/webpack": "^4.41.6",
"@types/classnames": "^2.2.9",
"@types/react": "^16.9.22",
"@types/react-dom": "^16.9.5",
"clean-webpack-plugin": "^3.0.0",
"css-loader": "^3.4.2",
"file-loader": "^5.1.0",
"fork-ts-checker-webpack-plugin": "^4.0.4",
"html-webpack-plugin": "^3.2.0",
"clean-webpack-plugin": "^4.0.0",
"css-loader": "^6.8.1",
"file-loader": "^6.2.0",
"fork-ts-checker-webpack-plugin": "^8.0.0",
"html-webpack-plugin": "^5.5.3",
"monaco-editor-webpack-plugin": "^4.0.0",
"sass": "^1.25.0",
"raw-loader": "^4.0.0",
"sass-loader": "^8.0.2",
"style-loader": "^1.1.3",
"ts-loader": "^6.2.1",
"ts-node": "^8.6.2",
"typescript": "^3.8.2",
"webpack": "^4.41.6",
"webpack-cli": "^3.3.11",
"webpack-dev-server": "^3.10.3"
"ts-loader": "^9.4.4",
"ts-node": "^10.9.1",
"typescript": "^5.1.6",
"webpack": "^5.88.1",
"webpack-cli": "^5.1.4",
"webpack-dev-server": "^4.15.1"
}
}

View File

@@ -1,12 +1,12 @@
import * as React from "react";
import { Button, Spinner } from "@blueprintjs/core";
import { observable, action } from "mobx";
import { action, observable } from "mobx";
import { observer } from "mobx-react";
import * as React from "react";
import { Model } from "../model/Model";
import { ExpressionInput } from "./ExpressionInput";
import { Visualizer } from "./Visualizer";
import classnames = require("classnames");
import { VisualizerHeaderDetails } from "./VisualizerHeaderDetails";
import classnames = require("classnames");
@observer
export class GUI extends React.Component<{ model: Model }> {
@@ -25,10 +25,7 @@ export class GUI extends React.Component<{ model: Model }> {
<div className="part-Header">
<VisualizerHeader model={m} />
</div>
<div
className="part-Visualizer"
style={{ flex: 1, minHeight: 0 }}
>
<div className="part-Visualizer" style={{ flex: 1, minHeight: 0 }}>
<Visualizer model={m} />
</div>
</div>
@@ -43,15 +40,9 @@ export class VisualizerHeader extends React.Component<{ model: Model }> {
render() {
const m = this.props.model;
return (
<div
className="component-VisualizerHeader"
style={{ display: "flex", flexDirection: "row" }}
>
<div className="component-VisualizerHeader" style={{ display: "flex", flexDirection: "row" }}>
<div
className={classnames(
"part-ExpandButton",
this.expanded && "expanded"
)}
className={classnames("part-ExpandButton", this.expanded && "expanded")}
onClick={this.toggleExpanded}
/>
<div
@@ -96,10 +87,7 @@ export class VisualizerHeaderMain extends React.Component<{ model: Model }> {
alignItems: "center",
}}
>
<div
className="part-ExpressionInput "
style={{ flex: 1, minWidth: 0 }}
>
<div className="part-ExpressionInput " style={{ flex: 1, minWidth: 0 }}>
<ExpressionInput model={m} />
</div>
<div style={{ width: 4 }} />
@@ -109,21 +97,9 @@ export class VisualizerHeaderMain extends React.Component<{ model: Model }> {
<Spinner size={Spinner.SIZE_SMALL} />
</div>
) : (
<Button
minimal
small
className="part-Icon"
icon="refresh"
onClick={() => m.refresh()}
/>
<Button minimal small className="part-Icon" icon="refresh" onClick={() => m.refresh()} />
))}
<Button
minimal
small
className="part-Icon"
icon="log-in"
onClick={() => m.openBrowser()}
/>
<Button minimal small className="part-Icon" icon="log-in" onClick={() => m.openBrowser()} />
{/*
TODO
<Button

View File

@@ -1,8 +1,8 @@
import * as monaco from "monaco-editor";
import * as React from "react";
import * as ReactDOM from "react-dom";
import "./style.scss";
import { App } from "./components/App";
import * as monaco from "monaco-editor";
import "./style.scss";
(globalThis as any).monaco = monaco;

View File

@@ -1,37 +1,27 @@
import {
webviewContract,
DataExtractionState,
WindowWithWebviewData,
WebviewUrlParams,
} from "debug-visualizer/src/webviewContract";
import { WebSocketStream } from "@hediet/typed-json-rpc-websocket";
import { DataExtractorId, VisualizationData } from "@hediet/debug-visualizer-data-extraction";
import { Disposable } from "@hediet/std/disposable";
import { EventTimer } from "@hediet/std/timer";
import { ConsoleRpcLogger } from "@hediet/typed-json-rpc";
import {
observable,
action,
computed,
when,
runInAction,
ObservableMap,
} from "mobx";
import {
DataExtractorId,
VisualizationData,
} from "@hediet/debug-visualizer-data-extraction";
import { WebSocketStream } from "@hediet/typed-json-rpc-websocket";
import "@hediet/visualization-bundle";
import {
globalVisualizationFactory,
VisualizationId,
Visualization,
RegisterVisualizerFn,
createVisualizer,
libImplementation,
Visualization,
VisualizationFactory,
VisualizationId,
createVisualizer,
globalVisualizationFactory,
libImplementation,
} from "@hediet/visualization-core";
import { getApi as getVsCodeApi } from "./VsCodeApi";
import {
DataExtractionState,
WebviewUrlParams,
WindowWithWebviewData,
webviewContract,
} from "debug-visualizer/src/webviewContract";
import { ObservableMap, action, computed, observable, runInAction, when } from "mobx";
import { MonacoBridge } from "./MonacoBridge";
import { Disposable } from "@hediet/std/disposable";
import { startInterval, EventTimer } from "@hediet/std/timer";
import { getApi as getVsCodeApi } from "./VsCodeApi";
declare const window: Window & WindowWithWebviewData;
@@ -39,8 +29,7 @@ declare let __webpack_public_path__: string;
export class Model {
public readonly dispose = Disposable.fn();
public readonly runningMode: "standalone" | "webview" | "webviewIFrame" =
"webview";
public readonly runningMode: "standalone" | "webview" | "webviewIFrame" = "webview";
@observable
public theme: "dark" | "light" = "light";
@@ -58,13 +47,10 @@ export class Model {
@observable languageId: string = "text";
@observable private preferredVisualizationId: VisualizationId | undefined =
undefined;
@observable private preferredVisualizationId: VisualizationId | undefined = undefined;
@observable isPolling = false;
private readonly pollingTimer = this.dispose.track(
new EventTimer(500, "stopped")
);
private readonly pollingTimer = this.dispose.track(new EventTimer(500, "stopped"));
@action
public setPreferredVisualizationId(id: VisualizationId) {
@@ -95,10 +81,7 @@ export class Model {
if (this.state.kind === "data") {
const vis = visualizerRegistry
.get()
.getVisualizations(
this.state.result.data,
this.preferredVisualizationId
);
.getVisualizations(this.state.result.data, this.preferredVisualizationId);
return {
visualization: vis.bestVisualization,
allVisualizations: vis.allVisualizations,
@@ -109,8 +92,7 @@ export class Model {
}
@observable.ref
public server: typeof webviewContract.TServerInterface | undefined =
undefined;
public server: typeof webviewContract.TServerInterface | undefined = undefined;
private readonly vsCodeApi = getVsCodeApi<{ expression: string }>();
@@ -137,9 +119,7 @@ export class Model {
}
} else {
const url = new URL(window.location.href);
const urlParams = Object.fromEntries(
url.searchParams.entries()
) as unknown as WebviewUrlParams;
const urlParams = Object.fromEntries(url.searchParams.entries()) as unknown as WebviewUrlParams;
const portStr = urlParams.serverPort;
if (!portStr) {
throw new Error("No port given.");
@@ -220,29 +200,26 @@ export class Model {
host: "localhost",
port: this.port,
});
const { server } = webviewContract.getServerFromStream(
stream,
new ConsoleRpcLogger(),
{
updateState: async ({ newState }) => {
runInAction(() => {
this._loading = newState.kind === "loading";
if (!this._loading) {
this.state = newState;
}
});
},
setExpression: async ({ expression }) => {
this.setExpression(expression);
},
updateLanguageId: async ({ languageId }) => {
this.languageId = languageId || "text";
},
setTheme: async ({ theme }) => {
this.theme = theme;
},
setCustomVisualizerScript: async ({ id, jsSource }) => {
eval(`
const { server } = webviewContract.getServerFromStream(stream, new ConsoleRpcLogger(), {
updateState: async ({ newState }) => {
runInAction(() => {
this._loading = newState.kind === "loading";
if (!this._loading) {
this.state = newState;
}
});
},
setExpression: async ({ expression }) => {
this.setExpression(expression);
},
updateLanguageId: async ({ languageId }) => {
this.languageId = languageId || "text";
},
setTheme: async ({ theme }) => {
this.theme = theme;
},
setCustomVisualizerScript: async ({ id, jsSource }) => {
eval(`
((load) => {
let fn = undefined;
if (load) {
@@ -255,9 +232,8 @@ export class Model {
${jsSource ? `function (module) { ${jsSource} }` : "undefined"}
)
`);
},
}
);
},
});
try {
await server.authenticate({ secret: this.serverSecret });
} catch (e) {
@@ -291,18 +267,12 @@ const visualizerRegistry = computed(() => {
result.addHiddenVisualizer(visualization);
}
fns.forEach((fn) => {
fn(
(options) => result.addVisualizer(createVisualizer(options)),
libImplementation
);
fn((options) => result.addVisualizer(createVisualizer(options)), libImplementation);
});
return result;
});
function setVisualizationModule(
id: string,
moduleFn: RegisterVisualizerFn | undefined
) {
function setVisualizationModule(id: string, moduleFn: RegisterVisualizerFn | undefined) {
if (moduleFn) {
fns.set(id, moduleFn);
} else {

View File

@@ -1,16 +1,18 @@
{
"compilerOptions": {
"target": "esnext",
"module": "commonjs",
"strict": true,
"outDir": "dist",
"skipLibCheck": true,
"rootDir": "./src",
"resolveJsonModule": true,
"newLine": "LF",
"sourceMap": true,
"jsx": "react",
"experimentalDecorators": true
},
"include": ["src/**/*"]
"compilerOptions": {
"target": "ES2017",
"module": "commonjs",
"strict": true,
"outDir": "dist",
"lib": ["DOM", "ES2023", "DOM.Iterable"],
"skipLibCheck": true,
"rootDir": "./src",
"resolveJsonModule": true,
"newLine": "LF",
"sourceMap": true,
"jsx": "react",
"experimentalDecorators": true,
"allowSyntheticDefaultImports": true
},
"include": ["src/**/*"]
}

View File

@@ -1,9 +1,9 @@
import * as webpack from "webpack";
import path = require("path");
import HtmlWebpackPlugin = require("html-webpack-plugin");
import MonacoWebpackPlugin = require("monaco-editor-webpack-plugin");
import ForkTsCheckerWebpackPlugin = require("fork-ts-checker-webpack-plugin");
import { CleanWebpackPlugin } from "clean-webpack-plugin";
import * as HtmlWebpackPlugin from "html-webpack-plugin";
import * as path from "path";
import * as webpack from "webpack";
import ForkTsCheckerWebpackPlugin = require("fork-ts-checker-webpack-plugin");
import MonacoWebpackPlugin = require("monaco-editor-webpack-plugin");
const r = (file: string) => path.resolve(__dirname, file);
@@ -13,7 +13,7 @@ module.exports = {
path: r("dist"),
filename: "[name].js",
chunkFilename: "[name]-[hash].js",
devtoolModuleFilenameTemplate: info => {
devtoolModuleFilenameTemplate: (info: any) => {
let result = info.absoluteResourcePath.replace(/\\/g, "/");
if (!result.startsWith("file:")) {
// Some paths already start with the file scheme.
@@ -30,10 +30,10 @@ module.exports = {
rules: [
{
test: /\.less$/,
loaders: ["style-loader", "css-loader", "less-loader"],
use: ["style-loader", "css-loader", "less-loader"],
},
{ test: /\.css$/, loader: "style-loader!css-loader" },
{ test: /\.scss$/, loader: "style-loader!css-loader!sass-loader" },
{ test: /\.css$/, use: ["style-loader", "css-loader"] },
{ test: /\.scss$/, use: ["style-loader", "css-loader", "sass-loader"] },
{
test: /\.(jpe?g|png|gif|eot|ttf|svg|woff|woff2|md)$/i,
loader: "file-loader",
@@ -45,9 +45,9 @@ module.exports = {
},
],
},
node: {
/*node: {
fs: "empty",
},
},*/
plugins: (() => {
const plugins: any[] = [
new HtmlWebpackPlugin({
@@ -55,10 +55,13 @@ module.exports = {
}),
new ForkTsCheckerWebpackPlugin(),
new CleanWebpackPlugin(),
new MonacoWebpackPlugin({
new webpack.DefinePlugin({
"process.env": JSON.stringify(process.env),
}),
/*new MonacoWebpackPlugin({
// Add more languages here once webworker issues are solved.
languages: ["typescript"],
}),
}),*/
];
return plugins;

4677
yarn.lock

File diff suppressed because it is too large Load Diff