Version 2.2.0.

* Adds support for custom injected scripts with automatic reload
* Refactors extension
This commit is contained in:
Henning Dieterichs
2021-06-18 20:26:51 +02:00
parent 6ce5d666a3
commit d22e5d4111
46 changed files with 1421 additions and 716 deletions

View File

@@ -7,7 +7,11 @@
"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
"editor.formatOnSave": true,
"typescript.tsdk": "node_modules\\typescript\\lib"
}

View File

@@ -1,7 +1,7 @@
{
"name": "@hediet/debug-visualizer-data-extraction",
"description": "A library that helps implementing data extractors for the Debug Visualizer VS Code extension.",
"version": "0.13.1",
"version": "0.14.0",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"author": {

View File

@@ -0,0 +1,7 @@
import { DataExtractor } from "./DataExtractorApi";
import type * as helpersTypes from "../helpers"
export type LoadDataExtractorsFn = (
register: (extractor: DataExtractor) => void,
helpers: typeof helpersTypes
) => void;

View File

@@ -0,0 +1,34 @@
import {
DataExtractor,
ExtractionCollector,
DataExtractorContext,
LoadDataExtractorsFn,
} from "..";
import * as helpers from "../../helpers";
export class InjectedExtractor implements DataExtractor {
readonly id = "injected-extractor";
getExtractions(
data: unknown,
extractionCollector: ExtractionCollector,
context: DataExtractorContext
): void {
const key = "@hediet/debug-visualizer/injectedScripts";
const injectedExtractorsFns = Object.values(
(global as any)[key]
) as LoadDataExtractorsFn[];
const extractors = new Array<DataExtractor>();
for (const fn of injectedExtractorsFns) {
fn(extractor => {
extractors.push(extractor);
}, helpers);
}
for (const extractor of extractors) {
extractor.getExtractions(data, extractionCollector, context);
}
}
}

View File

@@ -9,7 +9,7 @@ import { getDataExtractorApi } from "../injection";
import { GridExtractor } from "./GridExtractor";
import { TableDataExtractor } from "./TableExtractor";
import { StringDiffExtractor } from "./StringDiffExtractor";
import { find } from "../../helpers";
import { InjectedExtractor } from "./InjectedExtractor";
/**
* The default data extractors should be registered by VS Code automatically.
@@ -28,101 +28,8 @@ export function registerDefaultExtractors(
new GridExtractor(),
new TableDataExtractor(),
new StringDiffExtractor(),
new InjectedExtractor(),
]) {
api.registerExtractor(item);
}
function isRange(
value: unknown
): value is {
startColumn: number;
startLineNumber: number;
endColumn: number;
endLineNumber: number;
} {
return (
typeof value === "object" &&
!!value &&
"startColumn" in value &&
"startLineNumber" in value
);
}
function isPosition(
value: unknown
): value is { column: number; lineNumber: number } {
return (
typeof value === "object" &&
!!value &&
"column" in value &&
"lineNumber" in value
);
}
// hedietDbgVis.find(x => x.constructor.name === 'TextModel').getValue()
api.registerExtractor({
id: "positionInTextModel",
getExtractions(data, collector, context) {
let range: {
start: {
line: number;
column: number;
};
end: {
line: number;
column: number;
};
};
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;
}
const textModel: any = find(
x =>
typeof x === "object" &&
!!x &&
"constructor" in x &&
(x as any).constructor.name === "TextModel"
);
collector.addExtraction({
id: "positionInTextModel",
name: "Position In TextModel",
extractData() {
return {
kind: { text: true },
text: textModel.getValue(),
decorations: [{ range }],
};
},
priority: 1000,
});
},
});
}

View File

@@ -1,3 +1,4 @@
export * from "./DataExtractorApi";
export * from "./injection";
export { DataExtractorApiImpl } from "./DataExtractorApiImpl";
export * from "./LoadDataExtractorsFn";

View File

@@ -20,16 +20,18 @@ export function getExpressionToInitializeDataExtractorApi(): string {
}
/**
* Returns standalone JS code representing an expression returns the data extraction API.
* Returns standalone JS code representing an expression that returns the data extraction API.
* This expression returns an object of type `DataExtractorApi`.
* If the API must be initialized with `getExpressionToInitializeDataExtractorApi()`,
* it throws an exception containing the text of `ApiHasNotBeenInitializedCode`.
* This function is called in the VS Code extension, the expression is evaluated in the debugee.
*/
export function getExpressionForDataExtractorApi(): string {
return `((${selfContainedGetInitializedDataExtractorApi.toString()})())`;
}
export function getExpressionToDetectDataExtractorApiPresence(): string {
return `((${selfContainedIsDataExtractorApiInitialized.toString()})())`;
}
const apiKey = "@hediet/data-extractor-api/v2";
export function getDataExtractorApi(): DataExtractorApi {
@@ -43,10 +45,15 @@ export function getDataExtractorApi(): DataExtractorApi {
}
/**
* This code is used to detect if the API has not been initialized yet.
* @internal
*/
export const ApiHasNotBeenInitializedCode = "EgH0cybXij1jYUozyakO" as const;
function selfContainedIsDataExtractorApiInitialized(): boolean {
const globalObj =
typeof window === "object" ? (window as any) : (global as any);
const key: typeof apiKey = "@hediet/data-extractor-api/v2";
let api: DataExtractorApi | undefined = globalObj[key];
return api !== undefined;
}
/**
* @internal
@@ -57,11 +64,7 @@ function selfContainedGetInitializedDataExtractorApi(): DataExtractorApi {
const key: typeof apiKey = "@hediet/data-extractor-api/v2";
let api: DataExtractorApi | undefined = globalObj[key];
if (!api) {
const code: typeof ApiHasNotBeenInitializedCode =
"EgH0cybXij1jYUozyakO";
throw new Error(
`Data Extractor API has not been initialized. Code: ${code}`
);
throw new Error(`Data Extractor API has not been initialized.`);
}
return api;
}

View File

@@ -1,4 +1,8 @@
{
"editor.minimap.enabled": false,
"git.enabled": false
"git.enabled": false,
"typescript.tsc.autoDetect": "off",
"debugVisualizer.js.customScriptPaths": [
"${workspaceFolder}/custom-visualizer.js",
]
}

View File

@@ -0,0 +1,152 @@
// @ts-check
/**
* @type {import("@hediet/debug-visualizer-data-extraction").LoadDataExtractorsFn}
*/
module.exports = (register, helpers) => {
register({
id: "map",
getExtractions(data, collector, context) {
if (!(data instanceof Map)) {
return;
}
collector.addExtraction({
priority: 1000,
id: "map",
name: "Map",
extractData() {
return helpers.asData({
kind: { table: true },
rows: [...data].map(([k, v]) => ({ key: k, value: v }))
});
},
});
},
});
};
/*
register({
id: "binaryViewer",
getExtractions(data, collector, context) {
if (typeof data !== "number") {
return;
}
collector.addExtraction({
priority: 100,
id: "binary",
name: "Bits of Integer",
extractData() {
return helpers.asData({
kind: { text: true },
text: data.toString(2),
});
},
});
},
});
*/
(register, helpers) => {
register({
id: "positionOrRangeInTextModel",
getExtractions(data, collector, context) {
collector.addExtraction({
priority: 100,
id: "foo",
name: "Foo",
extractData() {
return helpers.asData({
kind: { text: true },
text: Object.keys(context.variablesInScope).join(", "),
});
},
});
},
});
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
);
}

View File

@@ -10,7 +10,9 @@ setTimeout(() => {
}, 0);
class Main {
/** @pure */
run() {
id = 0;
const head = new DoublyLinkedListNode("1");
head.setNext(new DoublyLinkedListNode("2"));
head.next!.setNext(new DoublyLinkedListNode("3"));
@@ -34,9 +36,9 @@ function reverse(list: DoublyLinkedList) {
i => ({
id: i.id,
label: i.name,
color: finished.has(i) ? "lime" : undefined,
color: finished.has(i) ? "lime" : "lightblue",
edges: [
{ to: i.next!, label: "next" },
{ to: i.next!, label: "next", color: "lightblue", },
{ to: i.prev!, label: "prev", color: "lightgray" },
].filter(r => !!r.to),
}));

View File

@@ -13,9 +13,13 @@ hedietDbgVis.markedGrid(
*/
// From https://github.com/AvraamMavridis/Algorithms-Data-Structures-in-Typescript/blob/master/algorithms/quickSort.md
const array = [1, 2, 33, 31, 1, 2, 63, 123, 6, 32, 943, 346, 24];
const sorted = quickSort(array, 0, array.length - 1);
console.log(sorted);
/** @pure */
function main() {
const array = [1, 2, 33, 31, 1, 2, 63, 123, 6, 32, 943, 346, 24];
const sorted = quickSort(array, 0, array.length - 1);
console.log(sorted);
}
main();
function swap(array: Array<number>, i: number, j: number) {
[array[i], array[j]] = [array[j], array[i]];

View File

@@ -271,6 +271,11 @@ fsevents@^2.0.6:
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.0.7.tgz#382c9b443c6cbac4c57187cdda23aa3bf1ccfc2a"
integrity sha512-a7YT0SV3RB+DjYcppwVDLtn13UQnmg0SWZS7ezZD0UjnLwXmy8Zm21GMVGLaFGimIqcvyMQaOJBrop8MyOp1kQ==
function-bind@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==
get-func-name@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.0.tgz#ead774abee72e20409433a066366023dd6887a41"
@@ -296,6 +301,13 @@ has-flag@^3.0.0:
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0=
has@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796"
integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==
dependencies:
function-bind "^1.1.1"
io-ts@^1.8.6:
version "1.10.4"
resolved "https://registry.yarnpkg.com/io-ts/-/io-ts-1.10.4.tgz#cd5401b138de88e4f920adbcb7026e2d1967e6e2"
@@ -310,6 +322,13 @@ is-binary-path@^2.1.0:
dependencies:
binary-extensions "^2.0.0"
is-core-module@^2.2.0:
version "2.4.0"
resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.4.0.tgz#8e9fc8e15027b011418026e98f0e6f4d86305cc1"
integrity sha512-6A2fkfq1rfeQZjxrZJGerpLCTHRNEBiSgnu0+obeJpEPZRUooHgsizvzv0ZjJwOz3iWIHdJtVWJ/tmPr3D21/A==
dependencies:
has "^1.0.3"
is-extglob@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
@@ -378,6 +397,11 @@ normalize-path@^3.0.0:
resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
path-parse@^1.0.6:
version "1.0.7"
resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735"
integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==
path-to-regexp@^1.7.0:
version "1.7.0"
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.7.0.tgz#59fde0f435badacba103a84e9d3bc64e96b9937d"
@@ -407,6 +431,14 @@ readdirp@^3.1.1:
dependencies:
picomatch "^2.0.4"
resolve@>=1.9.0:
version "1.20.0"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975"
integrity sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==
dependencies:
is-core-module "^2.2.0"
path-parse "^1.0.6"
sinon@^7.2.7:
version "7.3.2"
resolved "https://registry.yarnpkg.com/sinon/-/sinon-7.3.2.tgz#82dba3a6d85f6d2181e1eca2c10d8657c2161f28"
@@ -452,15 +484,22 @@ to-regex-range@^5.0.1:
dependencies:
is-number "^7.0.0"
ttypescript@^1.5.12:
version "1.5.12"
resolved "https://registry.yarnpkg.com/ttypescript/-/ttypescript-1.5.12.tgz#27a8356d7d4e719d0075a8feb4df14b52384f044"
integrity sha512-1ojRyJvpnmgN9kIHmUnQPlEV1gq+VVsxVYjk/NfvMlHSmYxjK5hEvOOU2MQASrbekTUiUM7pR/nXeCc8bzvMOQ==
dependencies:
resolve ">=1.9.0"
type-detect@4.0.8, type-detect@^4.0.0, type-detect@^4.0.5:
version "4.0.8"
resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c"
integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==
typescript@^3.8.2:
version "3.9.7"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.7.tgz#98d600a5ebdc38f40cb277522f12dc800e9e25fa"
integrity sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw==
typescript@^3.9.0:
version "3.9.9"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.9.tgz#e69905c54bc0681d0518bd4d587cc6f2d0b1a674"
integrity sha512-kdMjTiekY+z/ubJCATUPlRDl39vXYiMV9iyeMuEuXZh2we6zz80uovNN2WlAxmmdE/Z/YQe+EbOEXB5RHEED3w==
ws@^7.0.0:
version "7.1.1"

BIN
docs/custom-script-map.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

View File

@@ -1,5 +1,9 @@
# Change Log
## 2.2.0
- Adds setting `debugVisualizer.js.customScriptPaths` to inject custom scripts when debugging JavaScript. Such scripts are reloaded automatically when changed.
## 2.1.0
- Adds text diff and text decoration viewer.

View File

@@ -16,18 +16,18 @@ Click [here](https://hediet.github.io/visualization/) to explore all available v
See [demos](../demos/) for demos. These languages and debuggers are verified to work with this extension:
- JavaScript/TypeScript/... using `node`/`node2`/`extensionHost`/`chrome`/`pwa-chrome`/`pwa-node` debug adapter: [⭐ Full Support](../demos/js)
- Dart/Flutter using `dart` debug adapter: [✅ Rudimentary Support](../demos/dart)
- Go using `go` (Delve) debug adapter: [✅ Rudimentary Support](../demos/golang)
- Python using `python` debug adapter: [✅ Rudimentary Support](../demos/python)
- C# using `coreclr` debug adapter: [✅ Rudimentary Support](../demos/csharp) (work in progress for Full Support)
- PHP using `php` debug adapter: [✅ Rudimentary Support](../demos/php)
- Java using `java` debug adapter: [✅ Rudimentary Support](../demos/java)
- C++ using `cppdbg` debug adapter: [✅ Rudimentary Support](../demos/cpp)
- Swift using `lldb` debug adapter: [✅ Rudimentary Support](../demos/swift)
- Rust using `lldb` debug adapter: [✅ Rudimentary Support](../demos/rust)
- Dart/Flutter using `dart` debug adapter: [✅ Basic Support](../demos/dart)
- Go using `go` (Delve) debug adapter: [✅ Basic Support](../demos/golang)
- Python using `python` debug adapter: [✅ Basic Support](../demos/python)
- C# using `coreclr` debug adapter: [✅ Basic Support](../demos/csharp) (work in progress for Full Support)
- PHP using `php` debug adapter: [✅ Basic Support](../demos/php)
- Java using `java` debug adapter: [✅ Basic Support](../demos/java)
- 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)
All other languages and debuggers might work too.
For languages with _Rudimentary Support_, only JSON strings can be visualized - you must implement some logic that builds this JSON for your data structure!
For languages with _Basic Support_, only JSON strings can be visualized - you must implement some logic that builds this JSON for your data structure!
Fully supported languages offer _Data Extractors_ which convert some well known data structures to json.
## Usage
@@ -132,6 +132,49 @@ This extension provides these configuration options:
Specifies whether to pop out Debug Visualization Views with Chrome in Kiosk Mode. Uses the default browser otherwise or if Chrome is not found. Defaults to `true`.
- `debugVisualizer.js.customScriptPaths`
Specifies a list of JavaScript files that are injected into the debugee when debugging JavaScript.
Each script must assign `module.exports` with a function of type `import("@hediet/debug-visualizer-data-extraction").LoadDataExtractorsFn`.
Paths must be absolute and can use the variable `${workspaceFolder}`.
Scripts are automatically reloaded when they are changed.
Example:
```js
// @ts-check
/**
* @type {import("@hediet/debug-visualizer-data-extraction").LoadDataExtractorsFn}
*/
module.exports = (register, helpers) => {
register({
id: "map",
getExtractions(data, collector, context) {
if (!(data instanceof Map)) {
return;
}
collector.addExtraction({
priority: 1000,
id: "map",
name: "Map",
extractData() {
return helpers.asData({
kind: { table: true },
rows: [...data].map(([k, v]) => ({
key: k,
value: v,
})),
});
},
});
},
});
};
```
![](../docs/custom-script-map.png)
# See Also
This extension works very well together with my library [`@hediet/node-reload`](https://github.com/hediet/node-reload) for TypeScript/JavaScript.

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.1.0",
"version": "2.2.0",
"license": "GPL-3.0",
"engines": {
"vscode": "^1.46.0"
@@ -41,6 +41,11 @@
"onDebug",
"onWebviewPanel:debugVisualizer"
],
"capabilities": {
"untrustedWorkspaces": {
"supported": false
}
},
"main": "./dist/extension.js",
"contributes": {
"commands": [
@@ -63,6 +68,14 @@
"configuration": {
"title": "Debug Visualizer",
"properties": {
"debugVisualizer.js.customScriptPaths": {
"type": "array",
"description": "Defines JavaScript sources that are injected into the debuggee.",
"items": {
"type": "string",
"description": "The file path to a custom js script. Must be absolute. You can use ${workspaceFolder}."
}
},
"debugVisualizer.useChromeKioskMode": {
"type": "boolean",
"default": true,

View File

@@ -21,6 +21,29 @@ export class Config {
{ serializer: serializerWithDefault<DebugAdapterConfigs>({}) }
);
private readonly _customScriptPaths = new VsCodeSetting(
"debugVisualizer.js.customScriptPaths",
{ serializer: serializerWithDefault<string[]>([]) }
);
public get customScriptPaths(): string[] {
return this._customScriptPaths.get().map(p => {
const tpl = new SimpleTemplate(p);
return tpl.render({
workspaceFolder: () => {
const workspaceFolder = (workspace.workspaceFolders ||
[])[0];
if (!workspaceFolder) {
throw new Error(
`Cannot get workspace folder - '${p}' cannot be evaluated!`
);
}
return workspaceFolder.uri.fsPath;
},
});
});
}
@observable
private _vsCodeTheme = window.activeColorTheme;
@@ -85,3 +108,13 @@ function evaluateTemplate(
}
return result;
}
export class SimpleTemplate {
constructor(private readonly str: string) {}
render(data: Record<string, () => string>): string {
return this.str.replace(/\$\{([a-zA-Z0-9]+)\}/g, (substr, grp1) => {
return data[grp1]();
});
}
}

View File

@@ -1,16 +0,0 @@
import { EvaluationEngine, Evaluator } from "./EvaluationEngine";
import { EnhancedDebugSession } from "../../debugger/EnhancedDebugSession";
export class ComposedEvaluationEngine implements EvaluationEngine {
constructor(public readonly engines: EvaluationEngine[]) {}
createEvaluator(session: EnhancedDebugSession): Evaluator | undefined {
for (const f of this.engines) {
const evaluator = f.createEvaluator(session);
if (evaluator) {
return evaluator;
}
}
return undefined;
}
}

View File

@@ -1,47 +0,0 @@
import { DataExtractorId } from "@hediet/debug-visualizer-data-extraction";
import { EnhancedDebugSession } from "../../debugger/EnhancedDebugSession";
import { EvaluationEngine, Evaluator } from "./EvaluationEngine";
import { Config, DebugAdapterConfig } from "../../Config";
import { GenericEvaluator } from "./GenericEvaluationEngine";
import { registerUpdateReconciler, hotClass } from "@hediet/node-reload";
registerUpdateReconciler(module);
@hotClass(module)
export class ConfiguredEvaluationEngine implements EvaluationEngine {
constructor(private readonly config: Config) {}
createEvaluator(session: EnhancedDebugSession): Evaluator | undefined {
const config = this.config.getDebugAdapterConfig(session.session.type);
if (!config) {
return undefined;
}
return new ConfiguredEvaluator(session, config);
}
}
class ConfiguredEvaluator extends GenericEvaluator {
constructor(
session: EnhancedDebugSession,
private readonly config: DebugAdapterConfig
) {
super(session);
}
protected getContext() {
return this.config.context;
}
protected getFinalExpression({
expression,
preferredExtractorId,
}: {
expression: string;
preferredExtractorId: DataExtractorId | undefined;
}) {
return this.config.getFinalExpression({
expression,
preferredExtractorId,
});
}
}

View File

@@ -1,30 +0,0 @@
import {
DataExtractionResult,
DataExtractorId,
} from "@hediet/debug-visualizer-data-extraction";
import { EnhancedDebugSession } from "../../debugger/EnhancedDebugSession";
import { FormattedMessage } from "../../webviewContract";
export interface EvaluationEngine {
createEvaluator(session: EnhancedDebugSession): Evaluator | undefined;
}
export interface Evaluator {
evaluate(
args: EvaluationArgs
): Promise<
| { kind: "data"; result: DataExtractionResult }
| { kind: "error"; message: FormattedMessage }
>;
/**
* The language that expressions must be written in.
*/
readonly languageId: string | undefined;
}
export interface EvaluationArgs {
expression: string;
preferredExtractorId: DataExtractorId | undefined;
frameId: number | undefined;
}

View File

@@ -1,169 +0,0 @@
import {
getExpressionForDataExtractorApi,
DataResult,
ApiHasNotBeenInitializedCode,
getExpressionToInitializeDataExtractorApi,
DataExtractionResult,
} from "@hediet/debug-visualizer-data-extraction";
import { EnhancedDebugSession } from "../../debugger/EnhancedDebugSession";
import {
EvaluationEngine,
Evaluator,
EvaluationArgs,
} from "./EvaluationEngine";
import { FormattedMessage } from "../../webviewContract";
import { registerUpdateReconciler, hotClass } from "@hediet/node-reload";
registerUpdateReconciler(module);
@hotClass(module)
export class JsEvaluationEngine implements EvaluationEngine {
createEvaluator(session: EnhancedDebugSession): Evaluator | undefined {
const supportedDebugAdapters = [
"node",
"node2",
"extensionHost",
"chrome",
"pwa-chrome",
"pwa-node",
"pwa-extensionHost",
"node-terminal",
"pwa-msedge",
];
if (supportedDebugAdapters.indexOf(session.session.type) !== -1) {
return new JsEvaluator(session);
}
return undefined;
}
}
class JsEvaluator implements Evaluator {
public readonly languageId = "javascript";
constructor(private readonly session: EnhancedDebugSession) {}
private getContext(): "copy" | "repl" {
if (this.session.session.type.startsWith("pwa-")) {
return "copy";
}
return "repl";
}
public async evaluate({
expression,
preferredExtractorId,
frameId,
}: EvaluationArgs): Promise<
| { kind: "data"; result: DataExtractionResult }
| { kind: "error"; message: FormattedMessage }
> {
const variableNames = new Array<string>();
if (frameId) {
const scopes = await this.session.getScopes({ frameId });
const scopeVariables = await Promise.all(
scopes
.filter(s => !s.expensive)
.map(s =>
this.session.getVariables({
variablesReference: s.variablesReference,
})
)
);
for (const variables of scopeVariables) {
variableNames.push(
...variables
.filter(v => v.value !== "undefined")
.map(v => v.name)
);
}
if (variableNames.length > 100) {
variableNames.length = 100;
}
}
while (true) {
try {
const preferredExtractorExpr = preferredExtractorId
? `"${preferredExtractorId}"`
: "undefined";
const body = `${getExpressionForDataExtractorApi()}.getData(
e => (${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.session.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);
} else if (result.kind === "Data") {
return {
kind: "data",
result: result.extractionResult,
};
} else {
throw new Error("Invalid Data");
}
} catch (error) {
const msg = error.message as string | undefined;
if (msg && msg.includes(ApiHasNotBeenInitializedCode)) {
if (await this.initializeApi(frameId)) {
continue;
}
}
return {
kind: "error",
message: error.message,
};
}
}
}
private async initializeApi(frameId: number | undefined): Promise<boolean> {
try {
// prefer existing is true, so that manually registered (possibly newer) extractors are not overwritten.
const expression = `${getExpressionToInitializeDataExtractorApi()}.registerDefaultExtractors(true);`;
await this.session.evaluate({
expression,
frameId,
context: this.getContext(),
});
return true;
} catch (error) {
return false;
}
}
}

View File

@@ -1,6 +0,0 @@
export * from "./EvaluationEngine";
export { ComposedEvaluationEngine } from "./ComposedEvaluationEngine";
export { JsEvaluationEngine } from "./JsEvaluationEngine";
export { GenericEvaluationEngine } from "./GenericEvaluationEngine";
export { ConfiguredEvaluationEngine } from "./ConfiguredEvaluationEngine";

View File

@@ -1,175 +0,0 @@
import {
EvaluationWatchService,
EvaluationWatcher,
EvaluationWatcherOptions,
} from "./EvaluationWatchService";
import { observable, autorun, action } from "mobx";
import { Disposable } from "@hediet/std/disposable";
import { CancellationTokenSource, CancellationToken } from "vscode";
import { DataExtractorId } from "@hediet/debug-visualizer-data-extraction";
import { DataExtractionState, CompletionItem } from "../webviewContract";
import { hotClass } from "@hediet/node-reload";
import { VsCodeDebuggerView } from "../debugger/VsCodeDebuggerView";
import { EvaluationEngine } from "./EvaluationEngine/EvaluationEngine";
import { wait } from "@hediet/std/timer";
@hotClass(module)
export class EvaluationWatchServiceImpl implements EvaluationWatchService {
public readonly dispose = Disposable.fn();
private readonly watchers = new Set<ObservableEvaluationWatcher>();
constructor(
private readonly vsCodeDebuggerView: VsCodeDebuggerView,
private readonly evaluationEngine: EvaluationEngine
) {
this.dispose.track({
dispose: autorun(() => {
if (
vsCodeDebuggerView.activeDebugSession &&
vsCodeDebuggerView.activeFrameId !== undefined
) {
for (const w of this.watchers) {
w.refresh();
}
}
}),
});
}
public createEvaluationWatcher(
expression: string,
options: EvaluationWatcherOptions
): EvaluationWatcher {
const w = new ObservableEvaluationWatcher(expression, this, options);
this.watchers.add(w);
this.refresh(w);
return w;
}
public removeWatcher(w: ObservableEvaluationWatcher) {
this.watchers.delete(w);
}
private lastLanguageId: string | undefined = undefined;
get languageId(): string | undefined {
const session = this.vsCodeDebuggerView.activeDebugSession;
if (!session) {
return this.lastLanguageId;
}
const evaluator = this.evaluationEngine.createEvaluator(session);
if (!evaluator) {
return this.lastLanguageId;
}
this.lastLanguageId = evaluator.languageId;
return evaluator.languageId;
}
private runningRefreshOperation: Disposable | undefined;
public refresh(w: ObservableEvaluationWatcher): void {
if (this.runningRefreshOperation) {
this.runningRefreshOperation.dispose();
}
const tokenSource = new CancellationTokenSource();
this.runningRefreshOperation = {
dispose: () => {
tokenSource.cancel();
},
};
this._refresh(w, tokenSource.token);
}
private async _refresh(
w: ObservableEvaluationWatcher,
token: CancellationToken
): Promise<void> {
const session = this.vsCodeDebuggerView.activeDebugSession;
if (!session) {
w._state = { kind: "noDebugSession" };
return;
}
const frameId = this.vsCodeDebuggerView.activeFrameId;
w._state = { kind: "loading" };
const evaluator = this.evaluationEngine.createEvaluator(session);
if (!evaluator) {
w._state = {
kind: "error",
message: `The debug adapter "${session.session.type}" is not supported.`,
};
return;
}
const result = await evaluator.evaluate({
expression: w.expression,
frameId,
preferredExtractorId: w.preferredDataExtractor,
});
if (result.kind === "error") {
// give cancellation requested more time in case of errors
await wait(330);
}
if (token.isCancellationRequested) {
return;
}
w._state = result;
}
public async getCompletions(
text: string,
column: number
): Promise<CompletionItem[]> {
const session = this.vsCodeDebuggerView.activeDebugSession;
if (!session) {
return [];
}
return await session.getCompletions({
text,
frameId: this.vsCodeDebuggerView.activeFrameId,
column,
});
}
}
class ObservableEvaluationWatcher implements EvaluationWatcher {
constructor(
public readonly expression: string,
private readonly source: EvaluationWatchServiceImpl,
options: EvaluationWatcherOptions
) {
this._preferredDataExtractor = options.preferredDataExtractor;
}
@observable
private _preferredDataExtractor: DataExtractorId | undefined = undefined;
public get preferredDataExtractor(): DataExtractorId | undefined {
return this._preferredDataExtractor;
}
@action
public setPreferredDataExtractor(id: DataExtractorId | undefined): void {
this._preferredDataExtractor = id;
this.refresh();
}
public refresh(): void {
this.source.refresh(this);
}
@observable
public _state: DataExtractionState = { kind: "loading" };
public get state(): DataExtractionState {
return this._state;
}
public dispose(): void {
this.source.removeWatcher(this);
}
}

View File

@@ -1,2 +0,0 @@
export * from "./EvaluationWatchServiceImpl";
export * from "./EvaluationWatchService";

View File

@@ -0,0 +1,22 @@
import {
DebugSessionVisualizationSupport,
VisualizationBackend,
} from "./VisualizationBackend";
import { DebugSessionProxy } from "../proxies/DebugSessionProxy";
export class ComposedVisualizationSupport
implements DebugSessionVisualizationSupport {
constructor(public readonly engines: DebugSessionVisualizationSupport[]) {}
createBackend(
session: DebugSessionProxy
): VisualizationBackend | undefined {
for (const f of this.engines) {
const evaluator = f.createBackend(session);
if (evaluator) {
return evaluator;
}
}
return undefined;
}
}

View File

@@ -0,0 +1,62 @@
import { DataExtractorId } from "@hediet/debug-visualizer-data-extraction";
import { DebugSessionProxy } from "../proxies/DebugSessionProxy";
import {
DebugSessionVisualizationSupport,
VisualizationBackend,
} from "./VisualizationBackend";
import { Config, DebugAdapterConfig } from "../Config";
import { GenericVisualizationBackend } from "./GenericVisualizationSupport";
import { registerUpdateReconciler, hotClass } from "@hediet/node-reload";
import { DebuggerViewProxy } from "../proxies/DebuggerViewProxy";
registerUpdateReconciler(module);
@hotClass(module)
export class ConfigurableVisualizationSupport
implements DebugSessionVisualizationSupport {
constructor(
private readonly config: Config,
private readonly debuggerView: DebuggerViewProxy
) {}
createBackend(
session: DebugSessionProxy
): VisualizationBackend | undefined {
const config = this.config.getDebugAdapterConfig(session.session.type);
if (!config) {
return undefined;
}
return new ConfiguredVisualizationBackend(
session,
this.debuggerView,
config
);
}
}
class ConfiguredVisualizationBackend extends GenericVisualizationBackend {
constructor(
debugSession: DebugSessionProxy,
debuggerView: DebuggerViewProxy,
private readonly config: DebugAdapterConfig
) {
super(debugSession, debuggerView);
}
protected getContext() {
return this.config.context;
}
protected getFinalExpression({
expression,
preferredExtractorId,
}: {
expression: string;
preferredExtractorId: DataExtractorId | undefined;
}) {
return this.config.getFinalExpression({
expression,
preferredExtractorId,
});
}
}

View File

@@ -0,0 +1,134 @@
import { DataExtractionResult } from "@hediet/debug-visualizer-data-extraction";
import { CompletionItem, FormattedMessage } from "../webviewContract";
import {
DebugSessionVisualizationSupport,
GetVisualizationDataArgs,
VisualizationBackend,
} from "./VisualizationBackend";
import { EventEmitter, EventSource } from "@hediet/std/events";
import { debug } from "vscode";
import { DebugSessionProxy } from "../proxies/DebugSessionProxy";
import { Disposable } from "@hediet/std/disposable";
import { DebuggerViewProxy } from "../proxies/DebuggerViewProxy";
import { autorun, reaction } from "mobx";
export class DispatchingVisualizationBackend implements VisualizationBackend {
public readonly dispose = Disposable.fn();
private readonly visualizationBackends = new Map<
DebugSessionProxy,
VisualizationBackend
>();
protected readonly onChangeEmitter = new EventEmitter();
public readonly onChange = this.onChangeEmitter;
constructor(
private readonly visualizationSupport: DebugSessionVisualizationSupport,
private readonly debuggerView: DebuggerViewProxy
) {
this.dispose.track(
debug.onDidTerminateDebugSession(session => {
const existing = [...this.visualizationBackends].find(
v => v[0].session === session
);
if (existing) {
this.visualizationBackends.delete(existing[0]);
existing[1].dispose();
}
})
);
this.dispose.track({
dispose: () => {
for (const backend of this.visualizationBackends.values()) {
backend.dispose();
}
this.visualizationBackends.clear();
},
});
this.dispose.track({
dispose: reaction(
() => this.debuggerView.activeDebugSession,
debugSession => {
if (debugSession !== undefined) {
this.onChangeEmitter.emit();
}
}
),
});
}
private getVisualizationBackend(
debugSession: DebugSessionProxy
): VisualizationBackend | undefined {
const existing = this.visualizationBackends.get(debugSession);
if (existing) {
return existing;
}
const newBackend = this.visualizationSupport.createBackend(
debugSession
);
if (!newBackend) {
return undefined;
}
newBackend.onChange.sub(() => {
this.onChangeEmitter.emit();
});
this.visualizationBackends.set(debugSession, newBackend);
return newBackend;
}
private get activeVisualizationBackend(): VisualizationBackend | undefined {
const activeDebugSession = this.debuggerView.activeDebugSession;
if (!activeDebugSession) {
return undefined;
}
const backend = this.getVisualizationBackend(activeDebugSession);
return backend;
}
public async getVisualizationData(
args: GetVisualizationDataArgs
): Promise<
| { kind: "data"; result: DataExtractionResult }
| { kind: "error"; message: FormattedMessage }
> {
const activeDebugSession = this.debuggerView.activeDebugSession;
if (!activeDebugSession) {
return {
kind: "error",
message: "No active debug session.",
};
}
const backend = this.getVisualizationBackend(activeDebugSession);
if (!backend) {
return {
kind: "error",
message: `The debug adapter "${activeDebugSession.session.type}" is not supported.`,
};
}
return await backend.getVisualizationData(args);
}
public get expressionLanguageId(): string | undefined {
return this.activeVisualizationBackend?.expressionLanguageId;
}
public async getCompletions(
text: string,
column: number
): Promise<CompletionItem[]> {
return (
(await this.activeVisualizationBackend?.getCompletions(
text,
column
)) || []
);
}
}

View File

@@ -4,45 +4,60 @@ import {
GraphNode,
GraphVisualizationData,
} from "@hediet/debug-visualizer-data-extraction";
import { EnhancedDebugSession } from "../../debugger/EnhancedDebugSession";
import { DebugSessionProxy } from "../proxies/DebugSessionProxy";
import {
EvaluationEngine,
Evaluator,
EvaluationArgs,
} from "./EvaluationEngine";
DebugSessionVisualizationSupport,
VisualizationBackend,
GetVisualizationDataArgs,
VisualizationBackendBase,
} from "./VisualizationBackend";
import { parseEvaluationResultFromGenericDebugAdapter } from "./parseEvaluationResultFromGenericDebugAdapter";
import { FormattedMessage } from "../../webviewContract";
import { FormattedMessage } from "../webviewContract";
import { hotClass, registerUpdateReconciler } from "@hediet/node-reload";
import { DebuggerViewProxy } from "../proxies/DebuggerViewProxy";
registerUpdateReconciler(module);
@hotClass(module)
export class GenericEvaluationEngine implements EvaluationEngine {
createEvaluator(session: EnhancedDebugSession): Evaluator | undefined {
return new GenericEvaluator(session);
export class GenericVisualizationSupport
implements DebugSessionVisualizationSupport {
constructor(private readonly debuggerView: DebuggerViewProxy) {}
createBackend(
session: DebugSessionProxy
): VisualizationBackend | undefined {
return new GenericVisualizationBackend(session, this.debuggerView);
}
}
export class GenericEvaluator implements Evaluator {
public readonly languageId = "text";
export class GenericVisualizationBackend extends VisualizationBackendBase {
public readonly expressionLanguageId = "text";
constructor(private readonly session: EnhancedDebugSession) {}
constructor(
debugSession: DebugSessionProxy,
debuggerView: DebuggerViewProxy
) {
super(debugSession, debuggerView);
}
public async evaluate({
public async getVisualizationData({
expression,
preferredExtractorId,
frameId,
}: EvaluationArgs): Promise<
}: GetVisualizationDataArgs): Promise<
| { kind: "data"; result: DataExtractionResult }
| { kind: "error"; message: FormattedMessage }
> {
const frameId = this.debuggerView.getActiveStackFrameId(
this.debugSession
);
const finalExpression = this.getFinalExpression({
expression,
preferredExtractorId,
});
let reply: { result: string, variablesReference: number };
let reply: { result: string; variablesReference: number };
try {
reply = await this.session.evaluate({
reply = await this.debugSession.evaluate({
expression: finalExpression,
frameId,
context: this.getContext(),
@@ -51,7 +66,10 @@ export class GenericEvaluator implements Evaluator {
// Use structural information about variables
// from the evaluation response if present.
if (reply.variablesReference) {
const graph = await this.constructGraphFromVariablesReference(reply.result, reply.variablesReference);
const graph = await this.constructGraphFromVariablesReference(
reply.result,
reply.variablesReference
);
return {
kind: "data",
@@ -64,11 +82,15 @@ export class GenericEvaluator implements Evaluator {
},
data: graph,
},
}
};
} else {
return parseEvaluationResultFromGenericDebugAdapter(reply.result, {
debugAdapterType: this.session.session.configuration.type,
});
return parseEvaluationResultFromGenericDebugAdapter(
reply.result,
{
debugAdapterType: this.debugSession.session
.configuration.type,
}
);
}
} catch (error) {
return {
@@ -78,7 +100,7 @@ export class GenericEvaluator implements Evaluator {
items: [
"An error occurred while evaluating the expression:",
error.message,
`Used debug adapter: ${this.session.session.configuration.type}`,
`Used debug adapter: ${this.debugSession.session.configuration.type}`,
{
kind: "inlineList",
items: [
@@ -98,7 +120,7 @@ export class GenericEvaluator implements Evaluator {
* variables. Objects are considered to be equivalent if
* they share the same variables reference (this is important
* for representing cyclic relationships).
*
*
* @param rootLabel - The root object's label
* @param rootVariablesReference - The root object's DAP variables reference
* @param maxDepth - The maximum depth to search at
@@ -108,22 +130,29 @@ export class GenericEvaluator implements Evaluator {
rootLabel: string,
rootVariablesReference: number,
maxDepth: number = 30,
maxKnownNodes: number = 50,
maxKnownNodes: number = 50
): Promise<GraphVisualizationData> {
// Perform a breadth-first search on the object to construct the graph
const graph: GraphVisualizationData = {
kind: { graph: true },
nodes: [],
edges: []
edges: [],
};
const knownNodeIds: { [ref: number]: string; } = {};
const bfsQueue: { source: { id: string, name: string } | undefined, label: string, variablesReference: number, depth: number }[] = [{
source: undefined,
label: rootLabel,
variablesReference: rootVariablesReference,
depth: 0,
}];
const knownNodeIds: { [ref: number]: string } = {};
const bfsQueue: {
source: { id: string; name: string } | undefined;
label: string;
variablesReference: number;
depth: number;
}[] = [
{
source: undefined,
label: rootLabel,
variablesReference: rootVariablesReference,
depth: 0,
},
];
let knownCount: number = 0;
@@ -141,7 +170,9 @@ export class GenericEvaluator implements Evaluator {
// The variable is a leaf or an unvisited object: create the node.
const node: GraphNode = {
id: hasChilds ? `${variable.variablesReference}` : `__${variable.label}@${knownCount}__`,
id: hasChilds
? `${variable.variablesReference}`
: `__${variable.label}@${knownCount}__`,
label: variable.label,
color: variable.depth == 0 ? "lightblue" : undefined,
shape: "box",
@@ -153,8 +184,15 @@ export class GenericEvaluator implements Evaluator {
if (hasChilds) {
knownNodeIds[variable.variablesReference] = node.id;
for (const child of await this.session.getVariables({ variablesReference: variable.variablesReference })) {
bfsQueue.push({ source: { id: node.id, name: child.name }, label: child.value, variablesReference: child.variablesReference, depth: variable.depth + 1 });
for (const child of await this.debugSession.getVariables({
variablesReference: variable.variablesReference,
})) {
bfsQueue.push({
source: { id: node.id, name: child.name },
label: child.value,
variablesReference: child.variablesReference,
depth: variable.depth + 1,
});
}
}
@@ -166,7 +204,11 @@ export class GenericEvaluator implements Evaluator {
}
if (variable.source) {
graph.edges.push({ from: variable.source.id, to: nodeId, label: variable.source.name });
graph.edges.push({
from: variable.source.id,
to: nodeId,
label: variable.source.name,
});
}
} while (bfsQueue.length > 0 && knownCount <= maxKnownNodes);

View File

@@ -0,0 +1,395 @@
import {
getExpressionForDataExtractorApi,
DataResult,
getExpressionToInitializeDataExtractorApi,
DataExtractionResult,
getExpressionToDetectDataExtractorApiPresence,
} from "@hediet/debug-visualizer-data-extraction";
import { DebugSessionProxy } from "../proxies/DebugSessionProxy";
import {
DebugSessionVisualizationSupport,
VisualizationBackend,
GetVisualizationDataArgs,
VisualizationBackendBase,
} from "./VisualizationBackend";
import { FormattedMessage } from "../webviewContract";
import { registerUpdateReconciler, hotClass } from "@hediet/node-reload";
import { Config } from "../Config";
import { DebuggerViewProxy } from "../proxies/DebuggerViewProxy";
import { readFileSync, existsSync } from "fs";
import { reaction, observable } from "mobx";
import {
workspace,
FileSystemWatcher,
CancellationTokenSource,
CancellationToken,
window,
} from "vscode";
import { ResettableTimeout } from "@hediet/std/timer";
registerUpdateReconciler(module);
@hotClass(module)
export class JsEvaluationEngine implements DebugSessionVisualizationSupport {
constructor(
private readonly debuggerView: DebuggerViewProxy,
private readonly config: Config
) {}
createBackend(
session: DebugSessionProxy
): VisualizationBackend | undefined {
const supportedDebugAdapters = [
"node",
"node2",
"extensionHost",
"chrome",
"pwa-chrome",
"pwa-node",
"pwa-extensionHost",
"node-terminal",
"pwa-msedge",
];
if (supportedDebugAdapters.indexOf(session.session.type) !== -1) {
return new JsVisualizationBackend(
session,
this.debuggerView,
this.config
);
}
return undefined;
}
}
class JsVisualizationBackend extends VisualizationBackendBase {
public readonly expressionLanguageId = "javascript";
private apiInitialized = false;
private initializedPromise: Promise<void> | undefined = undefined;
constructor(
debugSession: DebugSessionProxy,
debuggerView: DebuggerViewProxy,
private readonly config: Config
) {
super(debugSession, debuggerView);
}
private getContext(): "copy" | "repl" {
if (this.debugSession.session.type.startsWith("pwa-")) {
return "copy";
}
return "repl";
}
public async getVisualizationData({
expression,
preferredExtractorId,
}: GetVisualizationDataArgs): Promise<
| { kind: "data"; result: DataExtractionResult }
| { kind: "error"; message: FormattedMessage }
> {
try {
await this.ensureRuntimeIsInjected();
const frameId = this.debuggerView.getActiveStackFrameId(
this.debugSession
);
const variableNames = new Array<string>();
if (frameId) {
const scopes = await this.debugSession.getScopes({ frameId });
const scopeVariables = await Promise.all(
scopes
.filter(s => !s.expensive)
.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)
);
}
if (variableNames.length > 100) {
variableNames.length = 100;
}
}
const preferredExtractorExpr = preferredExtractorId
? `"${preferredExtractorId}"`
: "undefined";
const body = `${getExpressionForDataExtractorApi()}.getData(
e => (${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);
} else if (result.kind === "Data") {
return {
kind: "data",
result: result.extractionResult,
};
} else {
throw new Error("Invalid Data");
}
} catch (error) {
return {
kind: "error",
message: error.message,
};
}
}
private ensureRuntimeIsInjected(): Promise<void> | undefined {
if (this.apiInitialized) {
return;
}
if (this.initializedPromise) {
return this.initializedPromise;
} else {
this.initializedPromise = this._ensureRuntimeIsInjected();
}
}
private async _ensureRuntimeIsInjected(): Promise<void> {
const body = getExpressionToDetectDataExtractorApiPresence();
const reply = await this.debugSession.evaluate({
expression: body,
frameId: undefined,
context: this.getContext(),
});
const isApiInitialized = reply.result === "true";
if (!isApiInitialized) {
await this.initializeApi();
}
await this.initializeCustomScripts();
}
private async initializeApi(): Promise<boolean> {
try {
// prefer existing is true, so that manually registered (possibly newer) extractors are not overwritten.
const expression = `${getExpressionToInitializeDataExtractorApi()}.registerDefaultExtractors(true);`;
await this.debugSession.evaluate({
expression,
frameId: undefined,
context: this.getContext(),
});
return true;
} catch (error) {
return false;
}
}
@observable
private filesVersionId: number = 0;
private readonly fileChangeDebouncedScheduler = new DebouncedScheduler(100);
private async initializeCustomScripts(): Promise<void> {
await this.debugSession.evaluate({
expression: `global["${injectedScriptsKey}"] = {};`,
frameId: undefined,
context: "repl",
});
const watchers = new Map<string, WatchedFile>();
this.dispose.track({
dispose: () => {
for (const w of watchers.values()) {
w.watcher.dispose();
}
},
});
reaction(
() => ({
customScriptPaths: [...this.config.customScriptPaths],
version: this.filesVersionId,
}),
async ({ customScriptPaths }) => {
const promises = new Array<Promise<[Result, WatchedFile]>>();
const activePaths = new Set<string>();
for (const fsPath of customScriptPaths) {
activePaths.add(fsPath);
let entry = watchers.get(fsPath);
if (!entry) {
entry = new WatchedFile(fsPath, this.debugSession);
watchers.set(fsPath, entry);
entry.watcher.onDidChange(() => {
this.fileChangeDebouncedScheduler.run(() => {
this.filesVersionId++;
});
});
}
promises.push(entry.update().then(e => [e, entry!]));
}
for (const [fsPath, entry] of watchers) {
if (!activePaths.has(fsPath)) {
promises.push(entry.remove().then(e => [e, entry]));
entry.watcher.dispose();
watchers.delete(fsPath);
}
}
const results = await Promise.all(promises);
const errors = results.filter(([r]) => r.error);
for (const [e, watcher] of errors) {
window.showErrorMessage(
`Error while evaluating "${watcher.fsPath}": ${
e.error!.message
}`
);
}
if (promises.length > 0) {
this.onChangeEmitter.emit();
}
},
{
fireImmediately: true,
}
);
}
}
const injectedScriptsKey = `@hediet/debug-visualizer/injectedScripts`;
interface Result {
error?: { message: string };
}
class WatchedFile {
public static nextId = 0;
public readonly watcher = workspace.createFileSystemWatcher(this.fsPath);
public readonly id = WatchedFile.nextId++;
public lastContent: string | undefined = undefined;
constructor(
public readonly fsPath: string,
private readonly debugSession: DebugSessionProxy
) {}
public async update(): Promise<Result> {
if (!existsSync(this.fsPath)) {
return {
error: { message: `File does not exist.` },
};
}
const scriptContent = readFileSync(this.fsPath, {
encoding: "utf-8",
});
if (this.lastContent === scriptContent) {
return {};
}
this.lastContent = scriptContent;
const expression = `
(
load => {
var module = {};
load(module);
global["${injectedScriptsKey}"]["${this.id}"] = module.exports;
}
)
(
function (module) { ${scriptContent} }
)`;
try {
await this.debugSession.evaluate({
expression,
frameId: undefined,
context: "repl",
});
} catch (e) {
return { error: { message: e.message } };
}
return {};
}
public async remove(): Promise<Result> {
const expression = `(() => { delete global["${injectedScriptsKey}"]["${this.id}"]; })()`;
try {
await this.debugSession.evaluate({
expression,
frameId: undefined,
context: "repl",
});
} catch (e) {
return { error: { message: e.message } };
}
return {};
}
}
class DebouncedScheduler {
private timeout: NodeJS.Timeout | undefined;
constructor(private readonly debounceTimeout: number) {}
public run(action: () => void): void {
this.clear();
this.timeout = setTimeout(action, this.debounceTimeout);
}
private clear() {
if (this.timeout) {
clearTimeout(this.timeout);
this.timeout = undefined;
}
}
public dispose(): void {
this.clear();
}
}

View File

@@ -0,0 +1,80 @@
import {
DataExtractionResult,
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";
export interface DebugSessionVisualizationSupport {
createBackend(session: DebugSessionProxy): VisualizationBackend | undefined;
}
export interface VisualizationBackend extends Disposable {
readonly onChange: EventSource;
getVisualizationData(
args: GetVisualizationDataArgs
): Promise<
| { kind: "data"; result: DataExtractionResult }
| { kind: "error"; message: FormattedMessage }
>;
/**
* The language that expressions must be written in.
*/
readonly expressionLanguageId: string | undefined;
getCompletions(text: string, column: number): Promise<CompletionItem[]>;
}
export interface GetVisualizationDataArgs {
expression: string;
preferredExtractorId: DataExtractorId | undefined;
}
export abstract class VisualizationBackendBase implements VisualizationBackend {
public readonly dispose = Disposable.fn();
protected readonly onChangeEmitter = new EventEmitter();
public readonly onChange = this.onChangeEmitter;
constructor(
protected readonly debugSession: DebugSessionProxy,
protected readonly debuggerView: DebuggerViewProxy
) {
this.dispose.track({
dispose: reaction(
() => debuggerView.getActiveStackFrameId(debugSession),
activeStackFrameId => {
if (activeStackFrameId !== undefined) {
this.onChangeEmitter.emit();
}
}
),
});
}
public abstract readonly expressionLanguageId: string | undefined;
public abstract getVisualizationData(
args: GetVisualizationDataArgs
): Promise<
| { kind: "data"; result: DataExtractionResult }
| { kind: "error"; message: FormattedMessage }
>;
public async getCompletions(
text: string,
column: number
): Promise<CompletionItem[]> {
return await this.debugSession.getCompletions({
text,
frameId: this.debuggerView.getActiveStackFrameId(this.debugSession),
column,
});
}
}

View File

@@ -0,0 +1,6 @@
export * from "./VisualizationBackend";
export { ComposedVisualizationSupport } from "./ComposedVisualizationSupport";
export { JsEvaluationEngine } from "./JsVisualizationSupport";
export { GenericVisualizationSupport } from "./GenericVisualizationSupport";
export { ConfigurableVisualizationSupport } from "./ConfigurableVisualizationSupport";

View File

@@ -2,7 +2,7 @@ import {
DataExtractionResult,
isVisualizationData,
} from "@hediet/debug-visualizer-data-extraction";
import { FormattedMessage } from "../../webviewContract";
import { FormattedMessage } from "../webviewContract";
export interface ParseEvaluationResultContext {
debugAdapterType: string;

View File

@@ -1,11 +1,11 @@
import { DataExtractorId } from "@hediet/debug-visualizer-data-extraction";
import { DataExtractionState, CompletionItem } from "../webviewContract";
export interface EvaluationWatchService {
createEvaluationWatcher(
export interface VisualizationWatchModel {
createWatch(
expression: string,
options: EvaluationWatcherOptions
): EvaluationWatcher;
options: VisualizationWatchOptions
): VisualizationWatch;
getCompletions(text: string, column: number): Promise<CompletionItem[]>;
@@ -17,14 +17,17 @@ export interface EvaluationWatchService {
readonly languageId: string | undefined;
}
export interface EvaluationWatcherOptions {
export interface VisualizationWatchOptions {
preferredDataExtractor?: DataExtractorId | undefined;
}
export interface EvaluationWatcher {
export interface VisualizationWatch {
/** This field is constant. */
readonly expression: string;
/** This field is observable */
readonly state: DataExtractionState;
/** This field is observable */
readonly preferredDataExtractor: DataExtractorId | undefined;

View File

@@ -0,0 +1,162 @@
import { DataExtractorId } from "@hediet/debug-visualizer-data-extraction";
import { hotClass } from "@hediet/node-reload";
import { Disposable } from "@hediet/std/disposable";
import { EventEmitter } from "@hediet/std/events";
import { wait } from "@hediet/std/timer";
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";
@hotClass(module)
export class VisualizationWatchModelImpl implements VisualizationWatchModel {
public readonly dispose = Disposable.fn();
private readonly watchers = new Set<ObservableVisualizationWatch>();
constructor(private readonly visualizationBackend: VisualizationBackend) {
this.dispose.track(
visualizationBackend.onChange.sub(() => {
if (visualizationBackend.expressionLanguageId !== undefined) {
this.lastLanguageId =
visualizationBackend.expressionLanguageId;
}
})
);
this.lastLanguageId = visualizationBackend.expressionLanguageId;
}
public createWatch(
expression: string,
options: VisualizationWatchOptions
): VisualizationWatch {
const w = new ObservableVisualizationWatch(
expression,
options,
this.visualizationBackend
);
w.onDispose.sub(() => {
this.watchers.delete(w);
});
this.watchers.add(w);
return w;
}
@observable
private lastLanguageId: string | undefined = undefined;
get languageId(): string | undefined {
return this.lastLanguageId;
}
public getCompletions(
text: string,
column: number
): Promise<CompletionItem[]> {
return this.visualizationBackend.getCompletions(text, column);
}
}
class ObservableVisualizationWatch implements VisualizationWatch {
public readonly dispose = Disposable.fn();
private readonly onDisposeEmitter = new EventEmitter();
public readonly onDispose = this.onDisposeEmitter.asEvent();
constructor(
public readonly expression: string,
options: VisualizationWatchOptions,
private readonly visualizationBackend: VisualizationBackend
) {
this._preferredDataExtractor = options.preferredDataExtractor;
this.dispose.track(
visualizationBackend.onChange.sub(() => {
this.refresh();
})
);
this.dispose.track({
dispose: () => {
this.onDisposeEmitter.emit();
},
});
this.refresh();
}
private _preferredDataExtractor: DataExtractorId | undefined = undefined;
public get preferredDataExtractor(): DataExtractorId | undefined {
return this._preferredDataExtractor;
}
@action
public setPreferredDataExtractor(id: DataExtractorId | undefined): void {
this._preferredDataExtractor = id;
this.refresh();
}
private runningRefreshOperation: Disposable | undefined;
public refresh(): void {
if (this.runningRefreshOperation) {
this.runningRefreshOperation.dispose();
}
const tokenSource = new CancellationTokenSource();
this.runningRefreshOperation = {
dispose: () => {
tokenSource.cancel();
},
};
this._refresh(tokenSource.token);
}
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 result = await this.visualizationBackend.getVisualizationData({
expression: this.expression,
//frameId,
preferredExtractorId: this.preferredDataExtractor,
});
if (result.kind === "error") {
// give cancellation requested more time in case of errors
await wait(330);
}
if (token.isCancellationRequested) {
return;
}
this._state = result;
}
@observable
public _state: DataExtractionState = { kind: "loading" };
public get state(): DataExtractionState {
return this._state;
}
}

View File

@@ -0,0 +1,2 @@
export * from "./VisualizationWatchModelImpl";
export * from "./VisualizationWatchModel";

View File

@@ -1,48 +0,0 @@
import { Disposable } from "@hediet/std/disposable";
import { debug, DebugSession } from "vscode";
import { observable, action } from "mobx";
import { VsCodeDebugger } from "./VsCodeDebugger";
import { EnhancedDebugSession } from "./EnhancedDebugSession";
/**
* Tracks the currently active debug session and its currently active stack frame.
* This is currently all faked. A proper API would be nice.
*/
export class VsCodeDebuggerView {
public readonly dispose = Disposable.fn();
@observable private _activeDebugSession: EnhancedDebugSession | undefined;
public get activeDebugSession(): EnhancedDebugSession | undefined {
return this._activeDebugSession;
}
public get activeFrameId(): number | undefined {
if (!this._activeDebugSession) {
return undefined;
} else {
return this._activeDebugSession["activeStackFrame"];
}
}
constructor(private vsCodeDebugger: VsCodeDebugger) {
this.dispose.track(
debug.onDidChangeActiveDebugSession(activeSession => {
this.updateActiveDebugSession(activeSession);
})
);
this.updateActiveDebugSession(debug.activeDebugSession);
}
@action
private updateActiveDebugSession(activeSession: DebugSession | undefined) {
if (!activeSession) {
this._activeDebugSession = undefined;
} else {
const s = this.vsCodeDebugger.getEnhancedDebugSession(
activeSession
);
this._activeDebugSession = s;
}
}
}

View File

@@ -15,15 +15,16 @@ registerUpdateReconciler(module);
import { InternalWebviewManager } from "./webview/InternalWebviewManager";
import { WebviewServer } from "./webview/WebviewServer";
import { Config } from "./Config";
import { VsCodeDebugger } from "./debugger/VsCodeDebugger";
import { VsCodeDebuggerView } from "./debugger/VsCodeDebuggerView";
import { EvaluationWatchServiceImpl } from "./EvaluationWatchService";
import { DebuggerProxy } from "./proxies/DebuggerProxy";
import { DebuggerViewProxy } from "./proxies/DebuggerViewProxy";
import { VisualizationWatchModelImpl } from "./VisualizationWatchModel";
import {
ComposedEvaluationEngine,
ComposedVisualizationSupport,
JsEvaluationEngine,
GenericEvaluationEngine,
ConfiguredEvaluationEngine,
} from "./EvaluationWatchService/EvaluationEngine";
GenericVisualizationSupport,
ConfigurableVisualizationSupport,
} from "./VisualizationBackend";
import { DispatchingVisualizationBackend } from "./VisualizationBackend/DispatchingVisualizationBackend";
export function activate(context: ExtensionContext) {
context.subscriptions.push(
@@ -37,18 +38,23 @@ export class Extension {
public readonly dispose = Disposable.fn();
private readonly config = new Config();
private readonly debugger = this.dispose.track(new VsCodeDebugger());
private readonly debugger = this.dispose.track(new DebuggerProxy());
private readonly debuggerView = this.dispose.track(
new VsCodeDebuggerView(this.debugger)
new DebuggerViewProxy(this.debugger)
);
public readonly dataSource = new EvaluationWatchServiceImpl(
this.debuggerView,
new ComposedEvaluationEngine([
new ConfiguredEvaluationEngine(this.config),
new JsEvaluationEngine(),
new GenericEvaluationEngine(),
])
public readonly dataSource = new VisualizationWatchModelImpl(
new DispatchingVisualizationBackend(
new ComposedVisualizationSupport([
new ConfigurableVisualizationSupport(
this.config,
this.debuggerView
),
new JsEvaluationEngine(this.debuggerView, this.config),
new GenericVisualizationSupport(this.debuggerView),
]),
this.debuggerView
)
);
private readonly server = new WebviewServer(this.dataSource, this.config);

View File

@@ -2,8 +2,8 @@ import { DebugSession } from "vscode";
import { CompletionItem } from "../webviewContract";
import { observable } from "mobx";
export class EnhancedDebugSession {
@observable protected activeStackFrame: number | undefined;
export class DebugSessionProxy {
@observable private _activeStackFrameId: number | undefined;
constructor(public readonly session: DebugSession) {}

View File

@@ -2,26 +2,24 @@ import { Disposable } from "@hediet/std/disposable";
import { debug, DebugSession } from "vscode";
import { EventEmitter } from "@hediet/std/events";
import { runInAction } from "mobx";
import { EnhancedDebugSession } from "./EnhancedDebugSession";
import { DebugSessionProxy } from "./DebugSessionProxy";
/**
* Decorates DebugSession instances and emits an event for new debug sessions.
*/
export class VsCodeDebugger {
export class DebuggerProxy {
public readonly dispose = Disposable.fn();
private readonly sessions = new Map<DebugSession, EnhancedDebugSession>();
private readonly sessions = new Map<DebugSession, DebugSessionProxy>();
private readonly _onDidStartDebugSession = new EventEmitter<{
session: EnhancedDebugSession;
session: DebugSessionProxy;
}>();
public readonly onDidStartDebugSession = this._onDidStartDebugSession.asEvent();
public getEnhancedDebugSession(
session: DebugSession
): EnhancedDebugSession {
public getDebugSessionProxy(session: DebugSession): DebugSessionProxy {
let result = this.sessions.get(session);
if (!result) {
result = new EnhancedDebugSession(session);
result = new DebugSessionProxy(session);
this.sessions.set(session, result);
}
return result;
@@ -38,9 +36,7 @@ export class VsCodeDebugger {
}),
debug.registerDebugAdapterTrackerFactory("*", {
createDebugAdapterTracker: session => {
const extendedSession = this.getEnhancedDebugSession(
session
);
const extendedSession = this.getDebugSessionProxy(session);
return {
onDidSendMessage: async msg => {
type Message =
@@ -90,7 +86,7 @@ export class VsCodeDebugger {
startFrame: 0,
levels: 1,
});
extendedSession["activeStackFrame"] =
extendedSession["_activeStackFrameId"] =
r.stackFrames.length > 0
? r.stackFrames[0].id
: undefined;
@@ -103,7 +99,7 @@ export class VsCodeDebugger {
m.command === "stepOut"
) {
extendedSession[
"activeStackFrame"
"_activeStackFrameId"
] = undefined;
}
}

View File

@@ -0,0 +1,41 @@
import { Disposable } from "@hediet/std/disposable";
import { debug, DebugSession } from "vscode";
import { observable, action } from "mobx";
import { DebuggerProxy } from "./DebuggerProxy";
import { DebugSessionProxy } from "./DebugSessionProxy";
/**
* Tracks the currently active debug session and its currently active stack frame.
* This is currently all faked. A proper API would be nice.
*/
export class DebuggerViewProxy {
public readonly dispose = Disposable.fn();
@observable private _activeDebugSession: DebugSessionProxy | undefined;
public get activeDebugSession(): DebugSessionProxy | undefined {
return this._activeDebugSession;
}
public getActiveStackFrameId(
session: DebugSessionProxy
): number | undefined {
return session["_activeStackFrameId"];
}
constructor(private debuggerProxy: DebuggerProxy) {
this.dispose.track(
debug.onDidChangeActiveDebugSession(activeSession => {
this.updateActiveDebugSession(activeSession);
})
);
this.updateActiveDebugSession(debug.activeDebugSession);
}
@action
private updateActiveDebugSession(activeSession: DebugSession | undefined) {
this._activeDebugSession = activeSession
? this.debuggerProxy.getDebugSessionProxy(activeSession)
: undefined;
}
}

View File

@@ -112,11 +112,11 @@ function getDebugVisualizerWebviewHtml(
window.addEventListener('message', event => {
if (event.source === window.frames[0]) {
if (event.data.command === "setState") {
console.log("setState", event.data.state);
// console.log("setState", event.data.state);
api.setState(event.data.state);
}
if (event.data.command === "getState") {
console.log("getState, sent ", api.getState());
// console.log("getState, sent ", api.getState());
window.frames[0].postMessage({ command: "getStateResult", state: api.getState() }, "*");
}
}

View File

@@ -2,9 +2,9 @@ import { Disposable } from "@hediet/std/disposable";
import { webviewContract } from "../webviewContract";
import { ConsoleRpcLogger, RequestHandlingError } from "@hediet/typed-json-rpc";
import {
EvaluationWatcher,
EvaluationWatchService,
} from "../EvaluationWatchService";
VisualizationWatch,
VisualizationWatchModel,
} from "../VisualizationWatchModel";
import { WebSocketStream } from "@hediet/typed-json-rpc-websocket";
import { observable, autorun } from "mobx";
import { WebviewServer } from "./WebviewServer";
@@ -15,12 +15,12 @@ import { Config } from "../Config";
export class WebviewConnection {
public readonly dispose = Disposable.fn();
@observable
private watcher: EvaluationWatcher | undefined = undefined;
private watcher: VisualizationWatch | undefined = undefined;
private readonly client: typeof webviewContract["TClientInterface"];
constructor(
evaluationWatchService: EvaluationWatchService,
evaluationWatchService: VisualizationWatchModel,
stream: WebSocketStream,
server: WebviewServer,
config: Config,
@@ -55,19 +55,16 @@ export class WebviewConnection {
setExpression: async ({ newExpression }) => {
throwIfNotAuthenticated();
let oldPreferredDataExtractor: EvaluationWatcher["preferredDataExtractor"];
let oldPreferredDataExtractor: VisualizationWatch["preferredDataExtractor"];
if (this.watcher) {
oldPreferredDataExtractor = this.watcher
.preferredDataExtractor;
this.dispose.untrack(this.watcher).dispose();
}
this.watcher = this.dispose.track(
evaluationWatchService.createEvaluationWatcher(
newExpression,
{
preferredDataExtractor: oldPreferredDataExtractor,
}
)
evaluationWatchService.createWatch(newExpression, {
preferredDataExtractor: oldPreferredDataExtractor,
})
);
},
openInBrowser: async ({}) => {

View File

@@ -8,7 +8,7 @@ import * as serveStatic from "serve-static";
import { Config } from "../Config";
import cryptoRandomString = require("crypto-random-string");
import { distPath } from "debug-visualizer-webview";
import { EvaluationWatchService } from "../EvaluationWatchService/EvaluationWatchService";
import { VisualizationWatchModel } from "../VisualizationWatchModel/VisualizationWatchModel";
import { URLSearchParams } from "url";
import { autorun, reaction } from "mobx";
@@ -19,7 +19,7 @@ export class WebviewServer {
public readonly connections = new Set<WebviewConnection>();
constructor(
dataSource: EvaluationWatchService,
dataSource: VisualizationWatchModel,
private readonly config: Config
) {
const app = express();
@@ -28,11 +28,13 @@ export class WebviewServer {
this.server = app.listen();
/*
console.log(
`Serving "${distPath}" on port ${
(this.server.address() as AddressInfo).port
}`
);
*/
const wss = new WebSocket.Server({ server: this.server });
wss.on("connection", async ws => {

View File

@@ -17,7 +17,6 @@ export class App extends React.Component {
require("../vscode-dark.scss");
}
}
console.log(this.model.theme);
}
render() {

View File

@@ -108,7 +108,7 @@ export class ExpressionInput extends React.Component<{ model: Model }> {
@action.bound
submit() {
const val = this.model.getValue();
console.log(val);
//console.log(val);
this.props.model.setExpression(val);
}
}

View File

@@ -37,7 +37,6 @@ export class MonacoBridge {
);
for (const file of commonTypes.keys()) {
console.log(file);
this.dispose.track(
monaco.languages.typescript.javascriptDefaults.addExtraLib(
commonTypes(file).default,