From 85692ac720b8e844e94e92552ebef117fd07db42 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Mon, 5 Aug 2019 02:09:06 +0200 Subject: [PATCH] Improved typescript visualizer. Opens browser window in chrome appmode by default. --- data-extraction/src/CommonDataTypes.ts | 1 + .../js/Extractors/TypeScriptDataExtractors.ts | 44 ++++++++++++--- extension/package.json | 1 + extension/src/ConnectionHandler.ts | 16 +++++- extension/src/Server.ts | 7 ++- extension/src/WebViews.ts | 9 +--- extension/src/extension.ts | 14 +++-- extension/src/types.d.ts | 5 ++ .../src/Visualizers/TreeVisualizer/Views.tsx | 54 ++++++++++++++++++- .../Visualizers/TreeVisualizer/Visualizer.tsx | 10 +++- .../src/Visualizers/TreeVisualizer/style.scss | 21 ++++++++ extension/yarn.lock | 33 +++++++++++- 12 files changed, 187 insertions(+), 28 deletions(-) create mode 100644 extension/src/types.d.ts diff --git a/data-extraction/src/CommonDataTypes.ts b/data-extraction/src/CommonDataTypes.ts index b753c88..410fcf6 100644 --- a/data-extraction/src/CommonDataTypes.ts +++ b/data-extraction/src/CommonDataTypes.ts @@ -70,6 +70,7 @@ export interface TreeNode { id: string | undefined; name: string; value: string | undefined; + emphasizedValue: string | undefined; children: TreeNode[]; data: TExtraData; isMarked: boolean; diff --git a/data-extraction/src/js/Extractors/TypeScriptDataExtractors.ts b/data-extraction/src/js/Extractors/TypeScriptDataExtractors.ts index 63705d1..1407969 100644 --- a/data-extraction/src/js/Extractors/TypeScriptDataExtractors.ts +++ b/data-extraction/src/js/Extractors/TypeScriptDataExtractors.ts @@ -29,8 +29,9 @@ export class TypeScriptAstDataExtractor var member = object[key]; if (member === value) return key; - if (Array.isArray(member) && member.indexOf(value) !== -1) + if (Array.isArray(member) && member.indexOf(value) !== -1) { return key; + } } return null; @@ -39,14 +40,14 @@ export class TypeScriptAstDataExtractor function toTreeNode( node: ts.Node, memberName: string, - marked: Set + marked: Set, + emphasizedValueFn: (node: ts.Node) => string | undefined ): CommonDataTypes.AstData["root"] { const name = tsApi.SyntaxKind[node.kind]; const children = node .getChildren() .map((childNode, idx) => { let parentPropertyName = findKey(childNode, node) || ""; - if (childNode.kind == tsApi.SyntaxKind.SyntaxList) { childNode.getChildren().some(c => { parentPropertyName = findKey(c, node) || ""; @@ -60,7 +61,12 @@ export class TypeScriptAstDataExtractor parentPropertyName = "" + idx; } - return toTreeNode(childNode, parentPropertyName, marked); + return toTreeNode( + childNode, + parentPropertyName, + marked, + emphasizedValueFn + ); }) .filter(c => c !== null); @@ -68,6 +74,8 @@ export class TypeScriptAstDataExtractor if (tsApi.isIdentifier(node)) { value = node.text; + } else if (tsApi.isLiteralExpression(node)) { + value = node.text; } return { @@ -78,6 +86,7 @@ export class TypeScriptAstDataExtractor length: 0, position: 0, }, + emphasizedValue: emphasizedValueFn(node), isMarked: marked.has(node), value, // startPos: node.pos, @@ -89,15 +98,36 @@ export class TypeScriptAstDataExtractor return typeof node === "object" && (tsApi as any).isNode(node); } - if (isNode(data) || (Array.isArray(data) && data.every(isNode))) { + if ( + isNode(data) || + (Array.isArray(data) && data.every(isNode)) || + (typeof data === "object" && + data && + Object.entries(data).every(([k, v]) => k === "fn" || isNode(v))) + ) { let root: ts.SourceFile; let marked: Set; + let fn: (n: ts.Node) => string | undefined = (n: ts.Node) => + undefined; if (Array.isArray(data)) { root = (data[0] as ts.Node).getSourceFile(); marked = new Set(data); - } else { + } else if (isNode(data)) { root = data.getSourceFile(); marked = new Set([data]); + } else { + marked = new Set(); + const map = new Map(); + fn = (n: ts.Node) => map.get(n); + for (const [k, v] of Object.entries(data)) { + if (k === "fn") { + fn = v; + } else { + root = v.getSourceFile(); + marked.add(v); + map.set(v, k); + } + } } collector.addExtraction({ @@ -107,7 +137,7 @@ export class TypeScriptAstDataExtractor extractData() { return { kind: { text: true, tree: true, ast: true }, - root: toTreeNode(root, "root", marked), + root: toTreeNode(root, "root", marked, fn), text: root.text, fileType: "ts", }; diff --git a/extension/package.json b/extension/package.json index 5a8e571..1d6541c 100644 --- a/extension/package.json +++ b/extension/package.json @@ -40,6 +40,7 @@ "@hediet/typed-json-rpc-websocket-server": "^0.7.7", "@types/express": "^4.17.0", "@types/serve-static": "^1.13.2", + "chrome-launcher": "^0.11.2", "express": "^4.17.1", "open": "^6.4.0", "serve-static": "^1.14.1", diff --git a/extension/src/ConnectionHandler.ts b/extension/src/ConnectionHandler.ts index b181fb2..ecb3b7d 100644 --- a/extension/src/ConnectionHandler.ts +++ b/extension/src/ConnectionHandler.ts @@ -43,7 +43,11 @@ export class ConnectionHandler { ); }, openInBrowser: async ({}) => { - open(server.indexUrl); + try { + await launchChrome(server.indexUrl); + } catch (e) { + open(server.indexUrl); + } }, setPreferredDataExtractor: async ({ dataExtractorId }) => { if (this.watcher) { @@ -70,3 +74,13 @@ export class ConnectionHandler { }); } } + +import chromeLauncher = require("chrome-launcher"); + +async function launchChrome(url: string): Promise { + const chrome = await chromeLauncher.launch({ + startingUrl: url, + // `--window-size=${width},${height}` + chromeFlags: ["--app=" + url], + }); +} diff --git a/extension/src/Server.ts b/extension/src/Server.ts index 0417e00..f877b39 100644 --- a/extension/src/Server.ts +++ b/extension/src/Server.ts @@ -26,12 +26,11 @@ export class Server { } public get indexUrl(): string { - return `http://localhost:${this.port}/index.html?serverPort=${ - this.port - }`; + const port = process.env.USE_DEV_UI ? 8080 : this.port; + return `http://localhost:${port}/index.html?serverPort=${this.port}`; } - public get mainBundle(): string { + public get mainBundleUrl(): string { return `http://localhost:${this.port}/main.js`; } diff --git a/extension/src/WebViews.ts b/extension/src/WebViews.ts index db4c60f..2355c9d 100644 --- a/extension/src/WebViews.ts +++ b/extension/src/WebViews.ts @@ -53,9 +53,6 @@ export class WebViews { } export class WebView { - private expression: string | null = null; - private lastFrameId: number | null = null; - constructor(private readonly webviewPanel: WebviewPanel) {} } @@ -89,11 +86,9 @@ export function getHtml(server: Server) { ${ isDev - ? `` + ? `` : `` } diff --git a/extension/src/extension.ts b/extension/src/extension.ts index 3aaa075..87a9ac3 100644 --- a/extension/src/extension.ts +++ b/extension/src/extension.ts @@ -5,7 +5,11 @@ import { registerUpdateReconciler, getReloadCount, } from "@hediet/node-reload"; -enableHotReload({ entryModule: module, loggingEnabled: true }); + +if (process.env.USE_DEV_UI) { + enableHotReload({ entryModule: module, loggingEnabled: true }); +} + import { Disposable } from "@hediet/std/disposable"; import { createJsDebuggerSource } from "./DataSource/JsDebuggerSource"; @@ -27,9 +31,11 @@ export class Extension { private readonly views = this.dispose.track(new WebViews(this.server)); constructor() { - const i = this.dispose.track(window.createStatusBarItem()); - i.text = "reload" + getReloadCount(module); - i.show(); + if (getReloadCount(module) > 0) { + const i = this.dispose.track(window.createStatusBarItem()); + i.text = "reload" + getReloadCount(module); + i.show(); + } this.views.createNew(); } } diff --git a/extension/src/types.d.ts b/extension/src/types.d.ts new file mode 100644 index 0000000..f45acbb --- /dev/null +++ b/extension/src/types.d.ts @@ -0,0 +1,5 @@ +declare namespace NodeJS { + declare interface ProcessEnv { + USE_DEV_UI?: "true"; + } +} diff --git a/extension/ui/src/Visualizers/TreeVisualizer/Views.tsx b/extension/ui/src/Visualizers/TreeVisualizer/Views.tsx index a30bf40..d2b6671 100644 --- a/extension/ui/src/Visualizers/TreeVisualizer/Views.tsx +++ b/extension/ui/src/Visualizers/TreeVisualizer/Views.tsx @@ -92,6 +92,7 @@ export class TreeNodeViewModel { public readonly id: string | undefined, public readonly name: string, public readonly value: string | undefined, + public readonly emphasizedValue: string | undefined, public readonly children: TreeNodeViewModel[] ) {} @@ -167,6 +168,20 @@ export class TreeNodeViewModel { } } +const isValidFunctionName = (function() { + var validName = /^[$A-Z_][0-9A-Z_$]*$/i; + var reserved = { + abstract: true, + boolean: true, + // ... + with: true, + } as any; + return function(s: string) { + // Ensure a valid name and not reserved. + return validName.test(s) && !reserved[s]; + }; +})(); + @observer export class TreeWithPathView extends React.Component<{ model: TreeViewModel; @@ -178,7 +193,39 @@ export class TreeWithPathView extends React.Component<{
{model.selected ? ( - {model.selected.path.join(" / ")} + + {model.selected.path.reduce((acc, v) => { + acc = acc.slice(); + function add() { + acc.push( + + {v} + + ); + } + if (isValidFunctionName(v)) { + if (acc.length > 0) { + acc.push( + . + ); + } + add(); + } else { + acc.push( + [ + ); + add(); + acc.push( + ] + ); + } + + return acc; + }, new Array())} + ) : ( "(Nothing Selected)" )} @@ -428,6 +475,11 @@ export class TreeNodeView extends React.Component<{ {model.value && ( {model.value} )} + {model.emphasizedValue && ( + + {model.emphasizedValue} + + )}
diff --git a/extension/ui/src/Visualizers/TreeVisualizer/Visualizer.tsx b/extension/ui/src/Visualizers/TreeVisualizer/Visualizer.tsx index 4ba900c..cc98ce2 100644 --- a/extension/ui/src/Visualizers/TreeVisualizer/Visualizer.tsx +++ b/extension/ui/src/Visualizers/TreeVisualizer/Visualizer.tsx @@ -1,4 +1,9 @@ -import { TreeViewModel, TreeNodeViewModel, TreeView } from "./Views"; +import { + TreeViewModel, + TreeNodeViewModel, + TreeView, + TreeWithPathView, +} from "./Views"; import { CommonDataTypes, ExtractedData, @@ -31,6 +36,7 @@ export function createTreeViewModelFromTreeNodeData( node.id, node.name, node.value, + node.emphasizedValue, children ); model.isMarked = node.isMarked; @@ -53,7 +59,7 @@ export class TreeVisualizer extends VisualizationProvider { priority: 100, render() { const m = createTreeViewModelFromTreeNodeData(data.root); - return ; + return ; }, }); } diff --git a/extension/ui/src/Visualizers/TreeVisualizer/style.scss b/extension/ui/src/Visualizers/TreeVisualizer/style.scss index 3390756..9588557 100644 --- a/extension/ui/src/Visualizers/TreeVisualizer/style.scss +++ b/extension/ui/src/Visualizers/TreeVisualizer/style.scss @@ -58,6 +58,19 @@ } .part-value { + color: darkgreen; + text-decoration: underline; + + /*&::before, + &::after { + content: '"'; + }*/ + + padding: 1px 3px; + margin-left: 5px; + } + + .part-emphasized-value { color: darkred; border: black solid 1px; padding: 1px 3px; @@ -99,6 +112,14 @@ white-space: pre; padding: 5px 10px; border-bottom: solid gray 1px; + + color: lightgray; + + .part-path-item { + color: black; + margin-left: 1px; + margin-right: 1px; + } } .part-tree { overflow: auto; diff --git a/extension/yarn.lock b/extension/yarn.lock index 3782a00..2714e59 100644 --- a/extension/yarn.lock +++ b/extension/yarn.lock @@ -833,6 +833,17 @@ chownr@^1.1.1: resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.2.tgz#a18f1e0b269c8a6a5d3c86eb298beb14c3dd7bf6" integrity sha512-GkfeAQh+QNy3wquu9oIZr6SS5x7wGdSgNQvD10X3r+AZr1Oys22HW8kAmDMvNg2+Dm0TeGaEuO8gFwdBXxwO8A== +chrome-launcher@^0.11.2: + version "0.11.2" + resolved "https://registry.yarnpkg.com/chrome-launcher/-/chrome-launcher-0.11.2.tgz#c9a248dbccd3a08565553acf61adff879bcc982c" + integrity sha512-jx0kJDCXdB2ARcDMwNCtrf04oY1Up4rOmVu+fqJ5MTPOOIG8EhRcEU9NZfXZc6dMw9FU8o1r21PNp8V2M0zQ+g== + dependencies: + "@types/node" "*" + is-wsl "^2.1.0" + lighthouse-logger "^1.0.0" + mkdirp "0.5.1" + rimraf "^2.6.1" + chrome-trace-event@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz#234090ee97c7d4ad1a2c4beae27505deffc608a4" @@ -1059,7 +1070,7 @@ date-now@^0.1.4: resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b" integrity sha1-6vQ5/U1ISK105cx9vvIAZyueNFs= -debug@2.6.9, debug@^2.2.0, debug@^2.3.3: +debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.8: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== @@ -1981,6 +1992,11 @@ is-wsl@^1.1.0: resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d" integrity sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0= +is-wsl@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.1.0.tgz#94369bbeb2249ef07b831b1b08590e686330ccbb" + integrity sha512-pFTjpv/x5HRj8kbZ/Msxi9VrvtOMRBqaDi3OIcbwPI3OuH+r3lLxVWukLITBaOGJIbA/w2+M1eVmVa4XNQlAmQ== + isarray@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" @@ -2079,6 +2095,14 @@ lcid@^2.0.0: dependencies: invert-kv "^2.0.0" +lighthouse-logger@^1.0.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/lighthouse-logger/-/lighthouse-logger-1.2.0.tgz#b76d56935e9c137e86a04741f6bb9b2776e886ca" + integrity sha512-wzUvdIeJZhRsG6gpZfmSCfysaxNEr43i+QT+Hie94wvHDKFLi4n7C2GqZ4sTC+PH5b5iktmXJvU87rWvhP3lHw== + dependencies: + debug "^2.6.8" + marky "^1.2.0" + loader-runner@^2.3.0: version "2.4.0" resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.4.0.tgz#ed47066bfe534d7e84c4c7b9998c2a75607d9357" @@ -2150,6 +2174,11 @@ map-visit@^1.0.0: dependencies: object-visit "^1.0.0" +marky@^1.2.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/marky/-/marky-1.2.1.tgz#a3fcf82ffd357756b8b8affec9fdbf3a30dc1b02" + integrity sha512-md9k+Gxa3qLH6sUKpeC2CNkJK/Ld+bEz5X96nYwloqphQE0CKCVEKco/6jxEZixinqNdz5RFi/KaCyfbMDMAXQ== + md5.js@^1.3.4: version "1.3.5" resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f" @@ -2306,7 +2335,7 @@ mixin-deep@^1.2.0: for-in "^1.0.2" is-extendable "^1.0.1" -mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0: +mkdirp@0.5.1, mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0: version "0.5.1" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=