From ec664bad91f56670e480d25bd2938ed7636f78b1 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Wed, 7 Aug 2019 17:23:34 +0200 Subject: [PATCH] Some progress. --- data-extraction/src/CommonDataTypes.ts | 8 +- data-extraction/src/js/DataExtractorApi.ts | 1 + .../src/js/Extractors/AsIsDataExtractor.ts | 2 +- .../js/Extractors/TypeScriptDataExtractors.ts | 9 +- example-project/src/index.ts | 18 +- extension/src/ConnectionHandler.ts | 14 +- extension/src/DataSource/DataSource.ts | 6 +- extension/src/DataSource/JsDebuggerSource.ts | 35 +- .../ui/src/Components/ExpressionInput.tsx | 84 + extension/ui/src/Components/NoData.tsx | 47 + extension/ui/src/Components/SvgElements.tsx | 12 - extension/ui/src/Components/Visualizer.tsx | 36 + extension/ui/src/Components/index.tsx | 165 +- extension/ui/src/{ => Model}/Model.ts | 100 +- extension/ui/src/Model/MonacoBridge.ts | 104 + extension/ui/src/Model/VsCodeApi.ts | 75 + extension/ui/src/Model/lib.es5.d.ts.txt | 4312 +++++++++++++++++ .../src/Visualizers/MonacoTextVisualizer.tsx | 101 +- extension/ui/src/index.tsx | 2 +- extension/ui/src/style.scss | 29 +- shared/src/contract.ts | 69 +- 21 files changed, 4943 insertions(+), 286 deletions(-) create mode 100644 extension/ui/src/Components/ExpressionInput.tsx create mode 100644 extension/ui/src/Components/NoData.tsx create mode 100644 extension/ui/src/Components/Visualizer.tsx rename extension/ui/src/{ => Model}/Model.ts (57%) create mode 100644 extension/ui/src/Model/MonacoBridge.ts create mode 100644 extension/ui/src/Model/VsCodeApi.ts create mode 100644 extension/ui/src/Model/lib.es5.d.ts.txt diff --git a/data-extraction/src/CommonDataTypes.ts b/data-extraction/src/CommonDataTypes.ts index 410fcf6..e57ac05 100644 --- a/data-extraction/src/CommonDataTypes.ts +++ b/data-extraction/src/CommonDataTypes.ts @@ -24,11 +24,11 @@ export type CommonDataType = | CommonDataTypes.GraphData; export module CommonDataTypes { - export interface Text extends ExtractedData { + export interface Text { kind: { text: true }; text: string; - // what should be used here? File type (.ts, .txt) or mime type? - fileType?: string; + mimeType?: string; + fileName?: string; } export interface GraphData { @@ -52,7 +52,7 @@ export module CommonDataTypes { text: string; } - export interface TreeNodeData extends ExtractedData { + export interface TreeNodeData { kind: { tree: true }; root: TreeNode<{}>; } diff --git a/data-extraction/src/js/DataExtractorApi.ts b/data-extraction/src/js/DataExtractorApi.ts index 5e1c1d3..225864f 100644 --- a/data-extraction/src/js/DataExtractorApi.ts +++ b/data-extraction/src/js/DataExtractorApi.ts @@ -51,6 +51,7 @@ export function selfContainedInitDataExtractorApi(): boolean { .filter(([key, value]) => key.startsWith(prefix)) .map(([key, value]) => value as DataExtractor); } + obj["$asData"] = (x: unknown) => x; obj[key] = api = { registerExtractor(extractor) { obj[prefix + extractor.id] = extractor; diff --git a/data-extraction/src/js/Extractors/AsIsDataExtractor.ts b/data-extraction/src/js/Extractors/AsIsDataExtractor.ts index 61ee9cc..446d7d0 100644 --- a/data-extraction/src/js/Extractors/AsIsDataExtractor.ts +++ b/data-extraction/src/js/Extractors/AsIsDataExtractor.ts @@ -18,7 +18,7 @@ export class AsIsDataExtractor implements DataExtractor { extractionCollector.addExtraction({ id: "as-is", name: "As Is", - priority: 100, + priority: 500, extractData() { return obj; }, diff --git a/data-extraction/src/js/Extractors/TypeScriptDataExtractors.ts b/data-extraction/src/js/Extractors/TypeScriptDataExtractors.ts index 1407969..2cf9172 100644 --- a/data-extraction/src/js/Extractors/TypeScriptDataExtractors.ts +++ b/data-extraction/src/js/Extractors/TypeScriptDataExtractors.ts @@ -2,6 +2,7 @@ import * as ts from "typescript"; import { DataExtractor, ExtractionCollector } from "../DataExtractor"; import { CommonDataTypes } from "../../CommonDataTypes"; +// This class is self contained and can be injected into both nodejs and browser environments. export class TypeScriptAstDataExtractor implements DataExtractor { readonly id = "TypeScriptAst"; @@ -83,14 +84,12 @@ export class TypeScriptAstDataExtractor id: memberName, children: children, data: { - length: 0, - position: 0, + length: node.end - node.pos, + position: node.pos, }, emphasizedValue: emphasizedValueFn(node), isMarked: marked.has(node), value, - // startPos: node.pos, - // endPos: node.end }; } @@ -139,7 +138,7 @@ export class TypeScriptAstDataExtractor kind: { text: true, tree: true, ast: true }, root: toTreeNode(root, "root", marked, fn), text: root.text, - fileType: "ts", + fileName: "index.ts", }; }, }); diff --git a/example-project/src/index.ts b/example-project/src/index.ts index aa2b9d0..100dc27 100644 --- a/example-project/src/index.ts +++ b/example-project/src/index.ts @@ -10,18 +10,20 @@ import { } from "@hediet/debug-visualizer-data-extraction"; enableHotReload(); import * as ts from "typescript"; -import { liveLogId, liveLog } from "@hediet/live-debug"; +//import { liveLogId, liveLog } from "@hediet/live-debug"; registerUpdateReconciler(module); registerAll(); +/* let i = 0; setInterval(() => { i++; liveLog("bla" + i * 2); liveLog("test" + i); }, 1000); +*/ @hotClass(module) class Main { @@ -29,18 +31,10 @@ class Main { const sf = ts.createSourceFile( "test", ` -class Test { - public test() { - console.log("aaa"); - const x = e; - } -} -class Test { - public test() { - console.log("aaa"); - const x = e; - } +class uiae { + } + `, ts.ScriptTarget.Latest, true diff --git a/extension/src/ConnectionHandler.ts b/extension/src/ConnectionHandler.ts index ecb3b7d..143fd24 100644 --- a/extension/src/ConnectionHandler.ts +++ b/extension/src/ConnectionHandler.ts @@ -44,16 +44,24 @@ export class ConnectionHandler { }, openInBrowser: async ({}) => { try { - await launchChrome(server.indexUrl); - } catch (e) { + //await launchChrome(server.indexUrl); open(server.indexUrl); - } + } catch (e) {} }, setPreferredDataExtractor: async ({ dataExtractorId }) => { if (this.watcher) { this.watcher.setPreferredDataExtractor(dataExtractorId); } }, + getCompletions: async ({ text, column }) => { + const completions = await sources.jsSource.getCompletions( + text, + column + ); + return { + completions, + }; + }, } ); diff --git a/extension/src/DataSource/DataSource.ts b/extension/src/DataSource/DataSource.ts index 90f62b2..4dbf6f7 100644 --- a/extension/src/DataSource/DataSource.ts +++ b/extension/src/DataSource/DataSource.ts @@ -1,5 +1,8 @@ import { DataExtractorId } from "@hediet/debug-visualizer-data-extraction"; -import { DataExtractionState } from "@hediet/debug-visualizer-vscode-shared"; +import { + DataExtractionState, + CompletionItem, +} from "@hediet/debug-visualizer-vscode-shared"; export interface EvaluationWatcherOptions { preferredDataExtractor?: DataExtractorId | undefined; @@ -10,6 +13,7 @@ export interface DataSource { expression: string, options: EvaluationWatcherOptions ): EvaluationWatcher; + getCompletions(text: string, column: number): Promise; } export interface EvaluationWatcher { diff --git a/extension/src/DataSource/JsDebuggerSource.ts b/extension/src/DataSource/JsDebuggerSource.ts index 89bdb86..cc20bbd 100644 --- a/extension/src/DataSource/JsDebuggerSource.ts +++ b/extension/src/DataSource/JsDebuggerSource.ts @@ -13,7 +13,10 @@ import { ApiHasNotBeenInitializedCode, selfContainedInitDataExtractorApi, } from "@hediet/debug-visualizer-data-extraction"; -import { DataExtractionState } from "@hediet/debug-visualizer-vscode-shared"; +import { + DataExtractionState, + CompletionItem, +} from "@hediet/debug-visualizer-vscode-shared"; export function createJsDebuggerSource(): JsDataSource { return new JsDebuggerSourceImplementation(); @@ -50,6 +53,28 @@ class JsDebuggerSourceImplementation implements JsDataSource { registerDataExtractor(classExpression: JsCode): void {} + async getCompletions( + text: string, + column: number + ): Promise { + const session = vscode.debug.activeDebugSession; + if (!session) { + return []; + } + + try { + const reply = await session.customRequest("completions", { + text, + frameId: this.lastFrameId, + column, + }); + return reply.targets; + } catch (error) { + console.error(error); + return []; + } + } + public createEvaluationWatcher( expression: string, options: EvaluationWatcherOptions @@ -79,10 +104,10 @@ class JsDebuggerSourceImplementation implements JsDataSource { ? `"${w.preferredDataExtractor}"` : "undefined"; - const expression = `(${fnSrc})().getData(${ - w.expression - }, expr => eval(expr), ${preferredExtractor})`; - // uia + const expression = + `(${fnSrc})().getData(${w.expression},` + + `expr => eval(expr), ${preferredExtractor})`; + const reply = await session.customRequest("evaluate", { expression, frameId: this.lastFrameId, diff --git a/extension/ui/src/Components/ExpressionInput.tsx b/extension/ui/src/Components/ExpressionInput.tsx new file mode 100644 index 0000000..60be078 --- /dev/null +++ b/extension/ui/src/Components/ExpressionInput.tsx @@ -0,0 +1,84 @@ +import React = require("react"); +import { Model } from "../Model/Model"; +import { observer, disposeOnUnmount } from "mobx-react"; +import { observable, action, autorun } from "mobx"; + +import * as monaco from "monaco-editor"; + +export function getModel(): monaco.editor.ITextModel { + return monaco.editor.createModel( + "", + "javascript", + monaco.Uri.parse(`file:///main.ts`) + ); +} + +@observer +export class ExpressionInput extends React.Component<{ model: Model }> { + @action.bound + submit() { + const val = this.model.getValue(); + console.log(val); + this.props.model.setExpression(val); + } + @observable private editor: monaco.editor.IStandaloneCodeEditor | undefined; + + private model = getModel(); + + componentWillUnmount() { + if (this.editor) { + this.editor.dispose(); + } + this.model.dispose(); + } + + @disposeOnUnmount + private _updateText = autorun(() => { + const val = this.model.getValue(); + const newVal = this.props.model.expression; + if (val !== newVal) { + this.model.setValue(newVal); + } + }); + + private readonly setEditorDiv = (editorDiv: HTMLDivElement) => { + if (!editorDiv) { + return; + } + + this.editor = monaco.editor.create(editorDiv, { + model: this.model, + scrollBeyondLastLine: false, + minimap: { enabled: false }, + fixedOverflowWidgets: true, + lineNumbers: "off", + glyphMargin: false, + folding: false, + lineDecorationsWidth: 0, + lineNumbersMinChars: 0, + automaticLayout: true, + scrollbar: { + horizontal: "hidden", + vertical: "hidden", + horizontalScrollbarSize: 0, + verticalScrollbarSize: 0, + }, + }); + + this.editor.onKeyDown(e => { + if (e.keyCode == monaco.KeyCode.Enter) { + e.preventDefault(); + e.stopPropagation(); + this.submit(); + } + }); + }; + + render() { + return ( +
+
+
+ ); + } +} diff --git a/extension/ui/src/Components/NoData.tsx b/extension/ui/src/Components/NoData.tsx new file mode 100644 index 0000000..ad6b2b3 --- /dev/null +++ b/extension/ui/src/Components/NoData.tsx @@ -0,0 +1,47 @@ +import React = require("react"); +import { observer } from "mobx-react"; +import { observable } from "mobx"; +import Measure from "react-measure"; + +@observer +export class NoData extends React.Component<{ label: string }> { + @observable height = 0; + @observable width = 0; + + render() { + const { width, height } = this; + return ( + { + if (e.client) { + this.height = e.client.height; + this.width = e.client.width; + } + }} + > + {({ measureRef }) => ( +
+ + + + + + +
+ )} +
+ ); + } +} diff --git a/extension/ui/src/Components/SvgElements.tsx b/extension/ui/src/Components/SvgElements.tsx index d380dc2..fe557e4 100644 --- a/extension/ui/src/Components/SvgElements.tsx +++ b/extension/ui/src/Components/SvgElements.tsx @@ -88,15 +88,3 @@ export function SvgRect( /> ); } - -/* -export function Svg(props: { position: Point } & SvgAttributes) { - return ( - - ); -} -*/ diff --git a/extension/ui/src/Components/Visualizer.tsx b/extension/ui/src/Components/Visualizer.tsx new file mode 100644 index 0000000..5b5ce89 --- /dev/null +++ b/extension/ui/src/Components/Visualizer.tsx @@ -0,0 +1,36 @@ +import React = require("react"); +import { Model } from "../Model/Model"; +import { observer } from "mobx-react"; +import { NoData } from "./NoData"; + +@observer +export class Visualizer extends React.Component<{ model: Model }> { + render() { + return ( +
{this.renderContent()}
+ ); + } + + renderContent(): JSX.Element { + const s = this.props.model.state; + if (s.kind === "loading") { + return ; + } else if (s.kind === "error") { + return ; + } else if (s.kind === "noExpression") { + return ; + } else if (s.kind === "noDebugSession") { + return ; + } else if (s.kind === "data") { + const vis = this.props.model.visualizations; + if (!vis || !vis.visualization) { + return ; + } + + return vis.visualization.render(); + } else { + const nvr: never = s; + return
; + } + } +} diff --git a/extension/ui/src/Components/index.tsx b/extension/ui/src/Components/index.tsx index f407484..c189831 100644 --- a/extension/ui/src/Components/index.tsx +++ b/extension/ui/src/Components/index.tsx @@ -1,12 +1,13 @@ import React = require("react"); -import { Model } from "../Model"; +import { Model } from "../Model/Model"; import { observer } from "mobx-react"; -import { observable, action, computed } from "mobx"; -import { Popover, Icon, Button, Spinner, InputGroup } from "@blueprintjs/core"; +import { observable, computed } from "mobx"; +import { Popover, Button, Spinner } from "@blueprintjs/core"; import classnames = require("classnames"); -import Measure from "react-measure"; import { DataExtractorInfo } from "@hediet/debug-visualizer-data-extraction"; -import { knownVisualizations } from "../Visualizers"; +import { Visualizer } from "./Visualizer"; +import { ExpressionInput } from "./ExpressionInput"; + @observer export class GUI extends React.Component<{ model: Model }> { @observable text: string = "node"; @@ -24,15 +25,23 @@ export class GUI extends React.Component<{ model: Model }> { />
- - -